• 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("
    CurrentRTC data/time is %d-%d-%d, %02d:%02d:%02d.
    ", 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调整的影响
    RTC Yes Yes
    xtime Yes Yes
    monotonic No Yes
    raw monotonic No No
    boot time Yes Yes

    上面介绍的系统时间其实在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
    ", 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	 sec: %d	 usec: %d
    ", 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
    ", time1.tv_sec, time1.tv_nsec);  
    11     
    12     clock_gettime(CLOCK_REALTIME_COARSE, &time1);  
    13     printf("CLOCK_REALTIME_COARSE: %d, %d
    ", time1.tv_sec, time1.tv_nsec);  
    14     
    15     clock_gettime(CLOCK_MONOTONIC, &time1);  
    16     printf("CLOCK_MONOTONIC: %d, %d
    ", time1.tv_sec, time1.tv_nsec);  
    17     
    18     clock_gettime(CLOCK_MONOTONIC_COARSE, &time1);  
    19     printf("CLOCK_MONOTONIC_COARSE: %d, %d
    ", time1.tv_sec, time1.tv_nsec);  
    20     
    21     clock_gettime(CLOCK_MONOTONIC_RAW, &time1);  
    22     printf("CLOCK_MONOTONIC_RAW: %d, %d
    ", time1.tv_sec, time1.tv_nsec);  
    23     
    24     clock_gettime(CLOCK_BOOTTIME, &time1);  
    25     printf("CLOCK_BOOTTIME: %d, %d
    ", time1.tv_sec, time1.tv_nsec);  
    26     
    27     clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &time1);  
    28     printf("CLOCK_PROCESS_CPUTIME_ID: %d, %d
    ", time1.tv_sec, time1.tv_nsec);  
    29     
    30     clock_gettime(CLOCK_THREAD_CPUTIME_ID, &time1);  
    31     printf("CLOCK_THREAD_CPUTIME_ID: %d, %d
    ", 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:
    ", desc);
     8      printf("%d-%02d-%02d %02d:%02d:%02d
    ", 
     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 	 wday: %d 	 isdst: %d
    
    ", 
    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
    
    ", static_cast<int>(t));
    19 
    20      // ctime and ctime_r
    21      char* c1 = ctime(&t);
    22      printf("ctime: c1: %s
    ", c1);
    23 
    24      char c20[64];
    25      char* c2 = ctime_r(&t, c20);
    26      printf("ctime_r: c2: %s 	 c20: %s 
    
    ", 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
    ", asc1);
    49 
    50      char asc20[128];
    51      char* asc2 = asctime_r(t1, asc20);
    52      printf("asctime-gmtime: asc2: %s 	 asc20: %s
    ", asc2, asc20);
    53 
    54      char* asc3 = asctime(t3);
    55      printf("asctime-localtime: %s
    ", asc3);
    56 
    57      char asc40[128];
    58      char* asc4 = asctime_r(t3, asc40);
    59      printf("asctime-localtime: asc4: %s 	 asc40: %s
    ", asc4, asc40);
    60 
    61      // mktime
    62      time_t mkt1 = mktime(t1);
    63      printf("mktime-gmtime: %d
    
    ", static_cast<int>(mkt1));
    64 
    65      time_t mkt2 = mktime(t3);
    66      printf("mktime-localtime: %d
    ", 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

  • 相关阅读:
    团队项目-典型用户及用户场景分析
    课堂小练习-找“水王”
    课堂小练习—电梯
    团队项目—用户需求调研报告
    课堂小练习
    团队项目的NABC
    梦断代码—随笔三
    梦断代码—随笔二
    结对开发5_循环二维数组最大字数组
    结对开发4_循环数组的最大值
  • 原文地址:https://www.cnblogs.com/abc-begin/p/8046451.html
Copyright © 2020-2023  润新知