时间获取、设置
time
函数原型:
time_t time(time_t *t);
基本功能描述
- time()的基本功能为返回相对于UTC(1970-01-01 00:00:00 +0000)所经过的秒数。注意,如果t不为NULL的话,t必须有效的内存地址。一般情况下,t设置NULL。
get/set time
函数原型
int gettimeofday(struct timeval *tv, struct timezone *tz); int settimeofday(const struct timeval *tv, const struct timezone *tz);
truct timeval { time_t tv_sec; /* seconds */ suseconds_t tv_usec; /* microseconds */ };
基本功能描述
gettimeofday()和settimeofday()的基本功能为获取-设置系统时间,设置或者获取时间都保存在timeval结构中。其中,gettimeofday返回时间与time(2)的类似,都是相对于UTC所经过的
秒数(tv_sec),不过它更为精确,还包括一个毫秒(tv_usec)。
注意事项
使用gettimeofday时,需要有几点注意的地方:
- 当tv或者tz为NULL, 相应的结构不会被设置或返回。
- timezone结构已废弃,所以tz应该被设置为NULL(PS:经测试Linux系统不处理settimeofday中的tz参数)。
- 对于tv的算书运算宏可以参考(man 2 timeradd)
格式化时间、日期
ctime函数族
函数原型
char *asctime(const struct tm *tm); char *asctime_r(const struct tm *tm, char *buf); char *ctime(const time_t *timep); char *ctime_r(const time_t *timep, char *buf); struct tm *gmtime(const time_t *timep); struct tm *gmtime_r(const time_t *timep, struct tm *result); struct tm *localtime(const time_t *timep); struct tm *localtime_r(const time_t *timep, struct tm *result); time_t mktime(struct tm *tm);
基本功能描述
- ctime函数族的主要功能是将时间和日期转换为struct tm结构或者ASCII字符串。
- ctime、gmtime、localtime的参数为time_t,该值一般表示日历时间,一般通过time(2)获得该值。
- asctime、mktime的参数为struct tm结构,该结构的定义如下:
tm_sec The number of seconds after the minute, normally in the range 0 to 59, but can be up to 60 to allow for leap seconds. tm_min The number of minutes after the hour, in the range 0 to 59. tm_hour The number of hours past midnight, in the range 0 to 23. tm_mday The day of the month, in the range 1 to 31. tm_mon The number of months since January, in the range 0 to 11. tm_year The number of years since 1900. tm_wday The number of days since Sunday, in the range 0 to 6. tm_yday The number of days since January 1, in the range 0 to 365. tm_isdst A flag that indicates whether daylight saving time is in effect at the time described. The value is positive if daylight saving time is in effect, zero if it is not, and negative if the information is not available.
注意,week的0-6代表"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", and "Sat";
- ctime(t)与asctime(localtime(t))的效果一样,都是将日历时间转换为类似于下面的C String:
'Wed Jan 9 15:08:05 2019\n'
注意,ctime返回的时间字符串为当前时区的时间。
ctime_r为ctime的线程安全版本,参数buf的大小至少为26字节。
- gmtime将日历时间转换为tm结构,注意该值为UTC时间(PS:所以gmtime以GMT开头)。gmtime的返回值指向一块静态内存,所以该值会被其他时间设置函数设置的值所覆盖。
为了避免该问题,我们可以使用线程安全的gmtime_r,该函数会将转换结果保存到result缓存中。
- localtime同样将日历时间转换为tm结构,该值为相对于用户本地时区的值。localtime线程不安全,多线程环境下,建议使用localtime_r。
- asctime将tm中保存的信息转换为C String, 其会简单的将tm中的信息拼接成字符串返回。asctime县城不安全,多线程环境下,建议使用asctime_r。
- mktime将保存这本地时间的tm转换为日历时间(UTC时间)。
注意事项
- POSIX.1-2001. C89 and C99 定义了 asctime(), ctime(), gmtime(), localtime(), and mktime().
POSIX.1-2008 标注 asctime(), asctime_r(), ctime(), and ctime_r() 已废弃, 推荐使用strftime.
- 按照POSIX.1-2004的说明,localtime()使用之前需要调用一下tzset(),localtime_r()没有这个要求,为了可移植性,建议再使用localtime()之前调用tzset().
strftime函数
函数原型
size_t strftime(char *s, size_t max, const char *format, const struct tm *tm);
strftime主要用于格式化日期和时间,其将保存在tm中的时间信息,按照format的参数进行格式化,并将格式化后的数据保存到大小为max的缓冲区中。format的具体参数比较多(参考man strftime(2)).
#include <time.h> #include <stdio.h> #include <stdlib.h> int main(int argc, char *argv[]) { char outstr[200]; time_t t; struct tm *tmp; t = time(NULL); tmp = localtime(&t); if (tmp == NULL) { perror("localtime"); exit(EXIT_FAILURE); } if (strftime(outstr, sizeof(outstr), argv[1], tmp) == 0) { fprintf(stderr, "strftime returned 0"); exit(EXIT_FAILURE); } printf("Result string is \"%s\"\n", outstr); exit(EXIT_SUCCESS); } $./a.out "%F %T" Result string is "2019-01-09 16:51:25"
clock获取、设置
函数原型
int clock_getres(clockid_t clk_id, struct timespec *res); int clock_gettime(clockid_t clk_id, struct timespec *tp); int clock_settime(clockid_t clk_id, const struct timespec *tp);
基本功能描述
- clock_getres返回clk_id的时间精度,如果res不为NULL,那查询到的结果将保存到res中。
- clock_gettime和clock_settime获取、设置特定clk_id的时间。res、tp的数据结构如下:
struct timespec { time_t tv_sec; /* seconds */ long tv_nsec; /* nanoseconds */ };
- clk_id代表系统中特定的时钟。时钟既可以是系统全局并且所有进程都可见的,或者,其测量的时间只针对于某一个进程。CLOCK_REALTIME在所有的实现中,都是系统可见的,它表示相对于UTC所经过的秒数和纳秒。
当系统时间改变时,对于基于相对时间的定时器,其时间不会受影响,但是对于基于绝对时间的定时器,其时间将受到影响。
- 下面为glic和Linux kernel所支持的clocks:
- CLOCK_REALTIME: 系统范围内的实时时钟。配置该clock下需要root权限。该clock会受到系统时间不连续跳跃的影响(例如,系统管理员手动修改系统时间),同时也会因为adjtime、NTP校准时间而受到影响。
所以对于定时任务,不应该通过该clock获取基础时间。前面所说的gettimeofday、time等函数所依据的clock就是CLOCK_REALTIME。
- CLOCK_REALTIME_COARSE(Linux特有属性,Linux 2.6.32开始加入): 相对于CLOCK_REALTIME,该clock更快,但精度较低。常用于能够快速获取时间但对精度要求不高的timestamp。
- CLOCK_MONOTONIC: 无法设置的clock,表示从某个未指定的起始点开始的单调时间.该clock不受系统时间不连续跳转的影响(例如,系统管理员手动修改clock),但会受到adjtime(3)和NTP增量校准时间的影响。 对于定时任务,如果精度要求不高可以选用该clock获取基准时间,对时间精度要求较高的定时任务会受到adjtime和NTP校准时间的影响。
- CLOCK_MONOTONIC_COARSE(Linux特有属性,Linux 2.6.32开始加入): 与CLOCK_REALTIME_COARSE类似,其性能较好,但精度较差。
- CLOCK_MONOTONIC_RAW(Linux特有属性,Linux 2.6.32开始加入): 与CLOCK_MONOTONIC类似,但是其时间基于硬件时钟,所以,其不会受到adjtime(3)和NTP增量校准时间的影响。 定时任务基准时间可以通过该clock过获取。
- CLOCK_BOOTTIME(Linux特有属性,Linux 2.6.39开始加入): 与CLOCK_MONOTONIC类似,而且该clock记录的时间包括系统挂起过程中所经过的时间。该clock为那些想要获取包括系统挂起时间而且不用为CLOCK_REALTIME(会受到settimeofday影响)所带来的并发症 所困扰的应用,提供了解决方案。定时任务可以通过该clock获取基础时间。
- CLOCK_PROCESS_CPUTIME_ID(Linux 2.6.12开始加入): 来自CPU的定时器,高分辨率、进程独有。用于统计单个进程所使用的CPU时间。
- CLOCK_THREAD_CPUTIME_ID(Linux 2.6.12开始加入): 线程相关的CPU-time clock。 用于统计单个线程所使用的CPU时间。
CLOCK_PROCESS_CPUTIME_ID和CLOCK_THREAD_CPUTIME_ID用于当我们进行系统中各个应用程序的性能分析和统计的时候。下面的程序用于获取制定进程的CPU-TIME:
#define _XOPEN_SOURCE 600 #include <stdio.h> #include <unistd.h> #include <stdlib.h> #include <time.h> int main(int argc, char *argv[]) { clockid_t clockid; struct timespec ts; if (argc != 2) { fprintf(stderr, "%s <process-ID>\n", argv[0]); exit(EXIT_FAILURE); } if (clock_getcpuclockid(atoi(argv[1]), &clockid) != 0) { perror("clock_getcpuclockid"); exit(EXIT_FAILURE); } if (clock_gettime(clockid, &ts) == -1) { perror("clock_gettime"); exit(EXIT_FAILURE); } printf("CPU-time clock for PID %s is %ld.%09ld seconds\n", argv[1], (long) ts.tv_sec, (long) ts.tv_nsec); exit(EXIT_SUCCESS); }
下面为Linux kernel和glic所支持的clock的比较表:
clock_id | clock_scope | can_set | settimeofday-unaffected | adjtime/NTP-unaffected | performance | precise |
CLOCK_REALTIME | system-wide | YES | NO | NO | lower | higher |
CLOCK_REALTIME_COARSE | system-wide | YES | NO | NO | higher | lower |
CLOCK_MONOTONIC | system-wide | NO | YES | NO | lower | higher |
CLOCK_MONOTONIC_COARSE | system-wide | NO | YES | NO | higher | lower |
CLOCK_MONOTONIC_RAW | system-wide | NO | YES | YES | lower | higher |
CLOCK_BOOTTIME | system-wide | NO | YES | NO | lower | higher |
时间调整
adjtime
函数原型
int adjtime(const struct timeval *delta, struct timeval *olddelta);
基本功能描述
adjtime用于校准系统时间,需要调整的时间保存在delta中,如果delta > 0,内核会通过一定的算法将系统时间缓慢的向上调整(例如,每一秒增加一些),直到时间调整结束;如果delta < 0,那么系统时间会缓慢的向下调整。
adjtimex
函数原型
int adjtimex(struct timex *buf);
基本功能描述
该函数用于调整内核时钟,其基于RFC1305所描述的算法实现。该系统调用实现较为复杂,而且功能强大,但一般应用不会使用。
睡眠
sleep
函数原型
unsigned int sleep(unsigned int seconds);
基本功能描述
sleep会导致调用线程进入睡眠态,直至seconds消逝完,或者被信号打断。sleep返回零表示所要求的时间已经等待完成;如果其被信息打断而返回,那么返回值表示所剩的秒数。
注意事项
- sleep有可能基于SIGALRM信号实现,所以混合使用alarm(2)和sleep会导致问题。
- sleep过程中,如果在信号处理函数调用longjmp或者修改SIGALRM的信号处理过程会导致未知的结果。
usleep
函数原型
int usleep(useconds_t usec);
基本功能描述
usleep阻塞当前调用线程至少usec。usleep执行成功返回0,失败时返回-1,如果其被信号打断,那错误码为EINTR。
注意事项
- useconds_t的取值范围为[0,1000000].
- 该函数会和SIGALARM存在交互,所以和其他定时器函数例如:alarm(2), sleep(3), nanosleep(2), setitimer(2), timer_create(2), timer_delete(2), timer_getoverrun(2), timer_gettime(2), timer_settime(2), ualarm(3)一起使用,其行为都是为定义的。
- POSIX.1-2001已经表明usleep函数已经废弃,代之使用nanosleep。
nanosleep
函数原型
int nanosleep(const struct timespec *req, struct timespec *rem);
基本功能描述
- nanosleep阻塞当前线程,知道rem消逝完,或者有信号递送给当前线程或进程处理。如果nanosleep被信号打断而返回,其会返回-1,剩余的时间会保存到rem(rem非空)中。可以继续将rem作为nanosleep的参数,使其继续 完成睡眠过程。
timesepc定义如下: struct timespec { time_t tv_sec; /* seconds */ long tv_nsec; /* nanoseconds */ };其中,nanoseconds的范围为:0 to 999999999;
- 相对于sleep(3)、usleep(3),nanosleep存在如下优势:
(1)提供更高的精度用来定义睡眠间隔; (2)POSIX.1明确的定义了其不于信号进行交互; (3)其可以更为方便的恢复一个被中断的任务继续睡眠;
注意事项
- 如果调用在被信号中断后多次重新启动,nanosleep()睡眠的相对间隔可能会有问题,因为在调用的中断和重新启动之间的时间会导致在睡眠最终完成时出现漂移。使用带绝对时间值的clock_nanosleep(2)可以避免这个问题。
- 如果req中指定的间隔不是底层时钟粒度的精确倍数(参见time(7),通过sysconf(_SC_CLK_TCK)获得该值),然后将这个区间四舍五入到下一个倍数;此外,在休眠完成之后,在CPU空闲下来再次执行调用线程之前,可能仍然会有一个延迟
clock_nanosleep
函数原型
int clock_nanosleep(clockid_t clock_id, int flags, const struct timespec *request, struct timespec *remain);
基本功能描述
- clock_nanosleep提供高分辨率、可定制clock的sleep。clock_nanosleep与nanosleep类似,区别在于clock_nanosleep允许选择测量sleep间隔的clock,同时,它还允许sleep间隔配置成相对值或者绝对值。可供选择的clock_id包括:CLOCK_REALTIME、CLOCK_MONOTONIC、CLOCK_PROCESS_CPUTIME_ID,具体可参见上一章的介绍。
2. flag为0,表示request为相对于当前clock的间隔;flag为TIMER_ABSTIME, request表示为与clock_id之间的绝对值。如果request <= 当前的clock,那么clock_nanosleep会立即返回。
3. 与nanosleep相似,clock_nanosleep同样会被信号中断,所不同的是,如果clock_nanosleep被中断,其返回值表示错误码。其若被中断,那么其返回EINTR,如果remain不为NULL,并且flag不为TIMER_ABSTIME, 那么剩 余的时间被保存在remain中。然后可以使用这个值再次调用clock_nanosleep()并完成(相对)睡眠。
注意事项
- 使用绝对计时器有助于防止nanosleep(2)中描述的那种计时器漂移问题(这些问题在试图重启被信号反复打断的相对睡眠的程序中更加严重)。要避免这些问题,可以为所需的时钟调用clock_gettime(2),将所需的时间间隔添加到返回的时间值,然后使用TIMER_ABSTIME标记调用clock_nanosleep().
- clock_nanosleep()在被信号处理程序中断后永远不会重新启动,尽管使用sigaction(2) SA_RESTART标志.
- flag为TIMER_ABSTIME时,remain参数没有任何用处.
- POSIX.1指定在通过clock_settime(2)更改CLOCK_REALTIME时钟的值之后,新的时钟值应该作为是否唤醒clock_nanosleep新的标准.如果新的时钟值超过睡眠间隔,那么clock_nanosleep()调用将立即返回。
- POSIX.1指定通过clock_settime(2)更改CLOCK_REALTIME时钟的值不会对相对clock_nanosleep()上阻塞的线程产生影响.
总结
Linux系统提供了各种各样的time函数,不同的精度、不同clock_id、不同睡眠机制等,我们在实现具体功能时,需按照实际的需求来选择合适的函数。后续,还会总结Linux系统下timer的种类和使用方式。