linux下时间操作1

本文是对我之前写的文章:C++时间操作 的更深入补充。之前那个文章就是一个快速入门的东西,后面力图把一些更深入的细节补充完整。

 

时间分类的基本介绍

在介绍一些时间相关的操作函数之前,先来介绍一下linux/UNIX下面的几种常用的时间。

在内核中维护了以下的时间:

从大类别上分类的话,主要分为硬件时钟与系统时钟。硬件时钟是指主机板上的时钟设备,也就是通常可在BIOS画面设定的时钟。系统时钟则是指kernel中 的时钟。所有Linux相关指令与函数都是读取系统时钟的设定。因为存在两种不同的时钟,那么它们之间就会存在差异。根据不同参数设置,hwclock命令既可以将硬件时钟同步到系统时钟,也可以将系统时钟同步到硬件时钟。可以通过hwclock命令来读取或设置硬件时钟。

-r, --show         读取并打印硬件时钟(read hardware clock and print result )
-s, --hctosys      将硬件时钟同步到系统时钟(set the system time from the hardware clock )
-w, --systohc     将系统时钟同步到硬件时钟(set the hardware clock to the current system time )

1、硬件时钟:

(1)RTC时间

在PC中,RTC时间又叫CMOS时间,它通常由一个专门的计时硬件来实现,软件可以读取该硬件来获得年月日、时分秒等时间信息,而在嵌入式系统中,有使用专门的RTC芯片,也有直接把RTC集成到Soc芯片中,读取Soc中的某个寄存器即可获取当前时间信息。一般来说,RTC是一种可持续计时的,也就是说,不管系统是否上电,RTC中的时间信息都不会丢失,计时会一直持续进行,硬件上通常使用一个后备电池对RTC硬件进行单独的供电。因为RTC硬件的多样性,开发者需要为每种RTC时钟硬件提供相应的驱动程序,内核和用户空间通过驱动程序访问RTC硬件来获取或设置时间信息。

 1 int rtc_test(void)
 2 {
 3     struct rtc_time rtc;
 4     int fd = -1;
 5     int ret = -1;
 6     fd = open("/dev/rtc0", O_RDWR);
 7     if (fd < 0){
 8         return -1;
 9     }
10     ret = ioctl(fd, RTC_RD_TIME, &rtc);
11     if (ret < 0){
12         return -1;
13     }
14     printf("\nCurrentRTC data/time is %d-%d-%d, %02d:%02d:%02d.\n", rtc.tm_mday, rtc.tm_mon + 1,
15     rtc.tm_year + 1900, rtc.tm_hour, rtc.tm_min, rtc.tm_sec);
16     ret = ioctl(fd, RTC_SET_TIME, &rtc);
17     if (ret < 0){
18         return -1;
19     }
20     return 0;
21 }

或者使用上面提到的hwclock命令来搞

1 system("hwclock -w");

 

2、系统时钟:

(1)wall时间

wall时间也称为xtime,取决于用于对xtime计时的clocksource,它的精度甚至可以达到纳秒级别。因为xtime实际上是一个内存中的变量,它的访问速度非常快,内核大部分时间都是使用xtime来获得当前时间信息。xtime记录的是自Epoch(1970-01-01 00:00:00 UTC)到当前时刻所经历的纳秒数。

(2)monotonic时间

该时间自系统开机后就一直单调地增加,它不像xtime可以因用户的调整时间而产生跳变,不过该时间不计算系统休眠的时间,也就是说,系统休眠时,monotoic时间不会递增。

(3)raw monotonic时间

该时间与monotonic时间类似,也是单调递增的时间,唯一的不同是:raw monotonic time“更纯净”,不会受到NTP时间调整的影响,它代表着系统独立时钟硬件对时间的统计。

(4)boot时间

与monotonic时间相同,不过会累加上系统休眠的时间,它代表着系统上电后的总时间。

时间种类精度(统计单位)访问速度累计休眠时间受NTP调整的影响
RTCYesYes
xtimeYesYes
monotonicNoYes
raw monotonicNoNo
boot timeYesYes

上面介绍的系统时间其实在linux系统调用的clock_gettime()函数可以充分体现出来。

1 int clock_gettime(clockid_t clk_id, struct timespec *tp);

在这里我们就简单的看一下,后面再详细介绍时间相关的函数。所以这里就只关注第一个参数clk_id,它支持的值就对应了上面我们提到的系统时间,而且比提到的稍微还多了一些时间支持。下面引用一下man手册里面对于clk_id的选项:

CLOCK_REALTIME:系统实时时间,从Epoch计时,可以被用户更改以及adjtime和NTP影响。
CLOCK_REALTIME_COARSE:系统实时时间,比起CLOCK_REALTIME有更快的获取速度,更低一些的精确度。(since Linux 2.6.32; Linux-specific)
CLOCK_MONOTONIC:从系统启动这一刻开始计时,即使系统时间被用户改变,也不受影响。系统休眠时不会计时。受adjtime和NTP影响。
CLOCK_MONOTONIC_COARSE:如同CLOCK_MONOTONIC,但有更快的获取速度和更低一些的精确度。受NTP影响。(since Linux 2.6.32; Linux-specific)
CLOCK_MONOTONIC_RAW:与CLOCK_MONOTONIC一样,系统开启时计时,不受NTP和adjtime影响。(since Linux 2.6.28; Linux-specific)
CLOCK_BOOTTIME: 从系统启动这一刻开始计时,包括休眠时间,受到settimeofday的影响。(since Linux 2.6.39; Linux-specific)
CLOCK_PROCESS_CPUTIME_ID: 本进程开始到此刻调用的时间。
CLOCK_THREAD_CPUTIME_ID: 本线程开始到此刻调用的时间。

内核新增的这些选项在以前不支持的时候只能通过某些系统调用syscall去搞,比如syscall(SYS_clock_gettime, CLOCK_MONOTONIC_RAW, &monotonic_time)

 

update 2017-12-19:

上面的这些参数实际上可以从linux的uapi中(http://elixir.free-electrons.com/linux/latest/source/include/uapi/linux/time.h)找到出处:

 1 /*
 2  * The IDs of the various system clocks (for POSIX.1b interval timers):
 3  */
 4 #define CLOCK_REALTIME            0
 5 #define CLOCK_MONOTONIC            1
 6 #define CLOCK_PROCESS_CPUTIME_ID    2
 7 #define CLOCK_THREAD_CPUTIME_ID        3
 8 #define CLOCK_MONOTONIC_RAW        4
 9 #define CLOCK_REALTIME_COARSE        5
10 #define CLOCK_MONOTONIC_COARSE        6
11 #define CLOCK_BOOTTIME            7
12 #define CLOCK_REALTIME_ALARM        8
13 #define CLOCK_BOOTTIME_ALARM        9

为了使开发的时间库可以通用,还是需要用syscall先去判断一下,而不要用clock_gettime函数直接去搞

1 clockid_t get_monotonic_clockid() {
2     const clockid_t MY_CLOCK_MONOTONIC_RAW = 4;
3 
4     timespec ts;
5     if (0 == syscall(SYS_clock_gettime, MY_CLOCK_MONOTONIC_RAW, &ts)) {
6         return MY_CLOCK_MONOTONIC_RAW;
7     }
8     return CLOCK_MONOTONIC;
9 }

 

时间相关的结构体说明

在我们介绍时间相关的那一坨函数之前,先来看看相应的一些结构体再说。

1、time_t

在time.h中的定义如下:

1 typedef __time_t time_t;

然后再看bits/types.h中的定义:

1 __STD_TYPE __TIME_T_TYPE __time_t;  /* Seconds since the Epoch.  */

再看bits/typesizes.h中的定义:

1 #define __TIME_T_TYPE               __SLONGWORD_TYPE

最后在bits/types.h中看到:

1 #define __SLONGWORD_TYPE       long int

绕了一大圈其实就是long int类型……

2、timeval

在bits/time.h中的定义如下:

1 /* A time value that is accurate to the nearest
2    microsecond but also has a range of years.  */
3 struct timeval
4   {
5     __time_t tv_sec;        /* Seconds.  */
6     __suseconds_t tv_usec;  /* Microseconds.  */
7   }; 

上面的__suseconds_t用追__time_t的方法追进去其实也是long int类型……

3、timespec

在time.h中的定义如下:

1 /* POSIX.1b structure for a time value.  This is like a `struct timeval' but
2    has nanoseconds instead of microseconds.  */
3 struct timespec
4   {
5     __time_t tv_sec;        /* Seconds.  */
6     long int tv_nsec;       /* Nanoseconds.  */
7   };  

其实有了上面介绍的3个类型,我们可以对获取时间的函数做出一个取舍了,这个我们后面再说。

4、tm

在time.h中的定义如下:

 1 struct tm
 2 {
 3   int tm_sec;           /* Seconds. [0-60] (1 leap second) */
 4   int tm_min;           /* Minutes. [0-59] */
 5   int tm_hour;          /* Hours.   [0-23] */
 6   int tm_mday;          /* Day.     [1-31] */
 7   int tm_mon;           /* Month.   [0-11] */
 8   int tm_year;          /* Year - 1900.  */
 9   int tm_wday;          /* Day of week. [0-6] */
10   int tm_yday;          /* Days in year.[0-365] */
11   int tm_isdst;         /* DST.     [-1/0/1]*/
12 
13 #ifdef  __USE_BSD
14   long int tm_gmtoff;       /* Seconds east of UTC.  */
15   __const char *tm_zone;    /* Timezone abbreviation.  */
16 #else
17   long int __tm_gmtoff;     /* Seconds east of UTC.  */
18   __const char *__tm_zone;  /* Timezone abbreviation.  */
19 #endif
20 };

好了,下面可以来看具体的函数了。注意编译的时候都要加上-lrt选项。

 

时间获取函数

1、time

1 time_t time(time_t*tloc);

需要包含头文件:<time.h>

返回的是自Epoch时间以来的秒数。

例子:

 1 #include <stdio.h>
 2 #include <time.h>
 3 
 4 int main(int argc, char* argv[])
 5 {
 6      time_t t = time(NULL);
 7      printf("time: %d\n", static_cast<int>(t));
 8 
 9      return 0;
10 }

运行结果:

time: 1513419614

 

2、gettimeofday

1 int gettimeofday(struct timeval* restrict tp, void* restrict tzp);

需要包含头文件:<sys/time.h>

其中第2个参数tzp的唯一合法值是NULL,其他值将产生不确定的结果。某些平台支持用tzp说明时区,但这完全依实现而定,Single UNIX Specification对此并没有定义。

因为返回的值存于结构体timeval中,所以是时间表示为秒和微秒。

例子:

 1 #include <stdio.h>
 2 #include <sys/time.h>
 3 
 4 int main(int argc, char* argv[])
 5 {
 6      timeval tp;
 7      int ret = gettimeofday(&tp, NULL);
 8      printf("ret: %d\t sec: %d\t usec: %d\n", ret, tp.tv_sec, tp.tv_usec);
 9 
10      return 0;
11 }

运行结果:

ret: 0   sec: 1513418627         usec: 881813

 

3、clock_gettime

1 int clock_gettime(clockid_t clk_id, struct timespec* tp);

需要包含头文件:<sys/time.h>

里面关于第1个参数已经在上面详细介绍过了,这里就不说了。

因为返回的值存于结构体timespec中,所以是时间表示为秒和纳秒。

例子:

 1 #include <time.h>
 2 #include <stdio.h>
 3 
 4 int main()
 5 {
 6 
 7     struct timespec time1 = {0, 0};  
 8     
 9     clock_gettime(CLOCK_REALTIME, &time1);  
10     printf("CLOCK_REALTIME: %d, %d\n", time1.tv_sec, time1.tv_nsec);  
11     
12     clock_gettime(CLOCK_REALTIME_COARSE, &time1);  
13     printf("CLOCK_REALTIME_COARSE: %d, %d\n", time1.tv_sec, time1.tv_nsec);  
14     
15     clock_gettime(CLOCK_MONOTONIC, &time1);  
16     printf("CLOCK_MONOTONIC: %d, %d\n", time1.tv_sec, time1.tv_nsec);  
17     
18     clock_gettime(CLOCK_MONOTONIC_COARSE, &time1);  
19     printf("CLOCK_MONOTONIC_COARSE: %d, %d\n", time1.tv_sec, time1.tv_nsec);  
20     
21     clock_gettime(CLOCK_MONOTONIC_RAW, &time1);  
22     printf("CLOCK_MONOTONIC_RAW: %d, %d\n", time1.tv_sec, time1.tv_nsec);  
23     
24     clock_gettime(CLOCK_BOOTTIME, &time1);  
25     printf("CLOCK_BOOTTIME: %d, %d\n", time1.tv_sec, time1.tv_nsec);  
26     
27     clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &time1);  
28     printf("CLOCK_PROCESS_CPUTIME_ID: %d, %d\n", time1.tv_sec, time1.tv_nsec);  
29     
30     clock_gettime(CLOCK_THREAD_CPUTIME_ID, &time1);  
31     printf("CLOCK_THREAD_CPUTIME_ID: %d, %d\n", time1.tv_sec, time1.tv_nsec);  
32 
33     return 0;
34 }

运行结果:

CLOCK_REALTIME: 1513420159, 235041261
CLOCK_REALTIME_COARSE: 1513420159, 234034596
CLOCK_MONOTONIC: 39739363, 412544528
CLOCK_MONOTONIC_COARSE: 39739363, 411488462
CLOCK_MONOTONIC_RAW: 39739427, 821184328
CLOCK_BOOTTIME: 39739363, 412569480
CLOCK_PROCESS_CPUTIME_ID: 0, 1418885
CLOCK_THREAD_CPUTIME_ID: 0, 1423199

 

综合上面的分析,在计时的时候,只使用 gettimeofday 来获取当前时间,主要原因如下:

(1)time 的精度太低,ftime 已被废弃,clock_gettime 精度最高,但是它系统调用的开销比 gettimeofday 大。

(2)在 x86-64 平台上,gettimeofday 不是系统调用,而是在用户态实现的(搜 vsyscall),没有上下文切换和陷入内核的开销。

(3)gettimeofday 的分辨率 (resolution) 是 1 微秒,足以满足日常计时的需要。

 

不过在使用gettimeofday的时候有个坑是需要注意的,那就是用gettimeofday取毫秒会有溢出的问题。

1 inline long getCurrentTime()
2 {
3     struct timeval tv;
4     gettimeofday(&tv, NULL);
5     return tv.tv_sec * 1000 + tv.tv_usec / 1000;
6 }

这样写的话在32位机器上是有坑的,因为tv.tv_sec * 1000后会溢出的。比如上面gettimeofday的例子中我们获取的值为1,513,418,627,乘以1000为1,513,418,627,000,早就远大于long在32位的机器上的范围为−2,147,483,648 ~ 2,147,483,647了。那么解决这个问题需要把long类型转换为long long,或者不支持long long类型的时候转为double。

1 inline long long getCurrentTime()
2 {
3     struct timeval tv;
4     gettimeofday(&tv, NULL);
5     long long ms = tv.tv_sec;
6     return ms * 1000 + tv.tv_usec / 1000;
7 }

这就完美了。解决完这个bug,不禁想到当unix时间戳到了2,147,483,647会是怎么办。这个问题早就有人考虑到了,叫做2038年问题。也就是到了2038年1月19日3时14分07秒后,如果在32位设备上用long类型再表示unix时间戳就溢出了。

溢出

 

时间打印函数

有很多这种函数,直接都写在一起得了。

 1 char* asctime(const struct tm* tm);
 2 char* asctime_r(const struct tm* tm, char* buf); // buf: 26 bytes at least
 3 
 4 char* ctime(const time_t* timep);
 5 char* ctime_r(const time_t* timep, char* buf);   // buf: 26 bytes at least
 6 
 7 struct tm* gmtime(const time_t* timep);
 8 struct tm* gmtime_r(const time_t* timep, struct tm* result);
 9 
10 struct tm* localtime(const time_t* timep);
11 struct tm* localtime_r(const time_t* timep, struct tm* result);
12 
13 time_t mktime(struct tm* tm);

需要包含头文件:<time.h>

 

asctime/asctime_r      : 把tm所表示的日期和时间转为字符串, 且会自动转为本地时区
ctime/ctime_r          : 把日期和时间转为字符串
gmtime/gmtime_r        : 把time_t转换为tm,未经时区转换
localtime/localtime_r  : 把time_t转换为tm,转为本地时区
mktime                 : 将tm转换为time_t,UTC

例子:

 1 #include <stdio.h>
 2 #include <time.h>
 3 #include <sys/time.h>
 4 
 5 static void ShowTM(const char* desc, const tm* t)
 6 {
 7      printf("%s:\n", desc);
 8      printf("%d-%02d-%02d %02d:%02d:%02d\n", 
 9                     t->tm_year + 1900, t->tm_mon + 1, t->tm_mday, t->tm_hour, t->tm_min, t->tm_sec);
10      printf("yday: %d \t wday: %d \t isdst: %d\n\n", 
11                     t->tm_yday, t->tm_wday, t->tm_isdst);
12 }
13 
14 int main(int argc, char* argv[])
15 {
16      // time
17      time_t t = time(NULL);
18      printf("time: %d\n\n", static_cast<int>(t));
19 
20      // ctime and ctime_r
21      char* c1 = ctime(&t);
22      printf("ctime: c1: %s\n", c1);
23 
24      char c20[64];
25      char* c2 = ctime_r(&t, c20);
26      printf("ctime_r: c2: %s \t c20: %s \n\n", c2, c20);
27 
28      // gmtime and gmtime_r
29      tm* t1 = gmtime(&t);
30      ShowTM("gmtime-t1", t1);
31 
32      tm t20;
33      tm* t2 = gmtime_r(&t, &t20);
34      ShowTM("gmtime_r-t2", t2);
35      ShowTM("gmtime_r-t20", &t20);
36 
37      // localtime and localtime_r
38      tm* t3 = localtime(&t);
39      ShowTM("localtime-t3", t3);
40 
41      tm t40;
42      tm* t4 = localtime_r(&t, &t40);
43      ShowTM("localtime_r-t4", t4);
44      ShowTM("localtime_r-t40", &t40);
45 
46      // asctime and asctime_r
47      char* asc1 = asctime(t1);
48      printf("asctime-gmtime: %s\n", asc1);
49 
50      char asc20[128];
51      char* asc2 = asctime_r(t1, asc20);
52      printf("asctime-gmtime: asc2: %s \t asc20: %s\n", asc2, asc20);
53 
54      char* asc3 = asctime(t3);
55      printf("asctime-localtime: %s\n", asc3);
56 
57      char asc40[128];
58      char* asc4 = asctime_r(t3, asc40);
59      printf("asctime-localtime: asc4: %s \t asc40: %s\n", asc4, asc40);
60 
61      // mktime
62      time_t mkt1 = mktime(t1);
63      printf("mktime-gmtime: %d\n\n", static_cast<int>(mkt1));
64 
65      time_t mkt2 = mktime(t3);
66      printf("mktime-localtime: %d\n", static_cast<int>(mkt2));
67 
68      return 0;
69 }

 

运行结果:

time: 1513421843

ctime: c1: Sat Dec 16 18:57:23 2017

ctime_r: c2: Sat Dec 16 18:57:23 2017
         c20: Sat Dec 16 18:57:23 2017
 

gmtime-t1:
2017-12-16 10:57:23
yday: 349        wday: 6         isdst: 0

gmtime_r-t2:
2017-12-16 10:57:23
yday: 349        wday: 6         isdst: 0

gmtime_r-t20:
2017-12-16 10:57:23
yday: 349        wday: 6         isdst: 0

localtime-t3:
2017-12-16 18:57:23
yday: 349        wday: 6         isdst: 0

localtime_r-t4:
2017-12-16 18:57:23
yday: 349        wday: 6         isdst: 0

localtime_r-t40:
2017-12-16 18:57:23
yday: 349        wday: 6         isdst: 0

asctime-gmtime: Sat Dec 16 18:57:23 2017

asctime-gmtime: asc2: Sat Dec 16 18:57:23 2017
         asc20: Sat Dec 16 18:57:23 2017

asctime-localtime: Sat Dec 16 18:57:23 2017

asctime-localtime: asc4: Sat Dec 16 18:57:23 2017
         asc40: Sat Dec 16 18:57:23 2017

mktime-gmtime: 1513421843

mktime-localtime: 1513421843

 

 

本文参考自:

《APUE》第3版

http://elixir.free-electrons.com/linux/latest/source/include/uapi/linux/time.h

https://linux.die.net/man/2/clock_gettime

http://blog.csdn.net/droidphone/article/details/7989566

http://blog.chinaunix.net/uid-20662820-id-3880162.html

http://wuzhiwei.net/one_overflow_issue/ 

http://www.cnblogs.com/Solstice/archive/2011/02/06/1949555.html

 

转载于:https://www.cnblogs.com/abc-begin/p/8046451.html

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值