Linux系统提供了很多关于time的处理API,这些API各自的功能和使用场景都有所不同。对于初学者有时会混淆它们,对于API的具体含义理解不到位。本文总结各类time相关的API的使用方式。
分类的标准按照函数的具体功能进行划分。
函数原型:
time_t time(time_t *t);
基本功能描述
函数原型
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时,需要有几点注意的地方:
函数原型
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);
基本功能描述
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返回的时间字符串为当前时区的时间。
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().
函数原型
size_t strftime(char *s, size_t max, const char *format, const struct tm *tm);
strftime主要用于格式化日期和时间,其将保存在tm中的时间信息,按照format的参数进行格式化,并将格式化后的数据保存到大小为max的缓冲区中。format的具体参数比较多(参考man strftime(2)).
#include
#include
#include
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"
函数原型
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
#include
#include
#include
int
main(int argc, char *argv[])
{
clockid_t clockid;
struct timespec ts;
if (argc != 2) {
fprintf(stderr, "%s \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 |
函数原型
int adjtime(const struct timeval *delta, struct timeval *olddelta);
基本功能描述
adjtime用于校准系统时间,需要调整的时间保存在delta中,如果delta > 0,内核会通过一定的算法将系统时间缓慢的向上调整(例如,每一秒增加一些),直到时间调整结束;如果delta < 0,那么系统时间会缓慢的向下调整。
函数原型
int adjtimex(struct timex *buf);
基本功能描述
该函数用于调整内核时钟,其基于RFC1305所描述的算法实现。该系统调用实现较为复杂,而且功能强大,但一般应用不会使用。
函数原型
unsigned int sleep(unsigned int seconds);
基本功能描述
sleep会导致调用线程进入睡眠态,直至seconds消逝完,或者被信号打断。sleep返回零表示所要求的时间已经等待完成;如果其被信息打断而返回,那么返回值表示所剩的秒数。
注意事项
函数原型
int usleep(useconds_t usec);
基本功能描述
usleep阻塞当前调用线程至少usec。usleep执行成功返回0,失败时返回-1,如果其被信号打断,那错误码为EINTR。
注意事项
函数原型
int nanosleep(const struct timespec *req, struct timespec *rem);
基本功能描述
timesepc定义如下:
struct timespec {
time_t tv_sec; /* seconds */
long tv_nsec; /* nanoseconds */
};其中,nanoseconds的范围为:0 to 999999999;
注意事项
函数原型
int clock_nanosleep(clockid_t clock_id, int flags,
const struct timespec *request,
struct timespec *remain);
基本功能描述
注意事项
Linux系统提供了各种各样的time函数,不同的精度、不同clock_id、不同睡眠机制等,我们在实现具体功能时,需按照实际的需求来选择合适的函数。后续,还会总结Linux系统下timer的种类和使用方式。