zoukankan      html  css  js  c++  java
  • TLPI读书笔记第10章-时间

    程序可能会关注两种时间类型。

    1.真实时间:度量这一时间的起点有二:一为某个标准点;二为进程生命周期内的某个固定时点(通常为程序启动)。前者为日历( calendar)时间,适用于需要对数据库记录或文件打上时间戳的程序;后者则称之为流逝( elapsed)时间或挂钟( wall clock)时间,主要针对需要周期性操作或定期从外部输入设备进行度量的程序。

    2.进程时间:一个进程所使用的 CPU 时间总量,适用于对程序、算法性能的检查或优化。大多数计算机体系结构都内置有硬件时钟,使内核得以计算真实时间和进程时间。本章将介绍系统调用对这两种时间的处理,以及在可读时间和机器时间之间互相转换的库函数。

    由于可读时间的表现形式与地理位置、语言和文化习俗有关,讨论这一话题自然引出对时区和地区的研究

    10.1 日历时间(Calendar Time)

    无论地理位置如何, UNIX 系统内部对时间的表示方式均是以自 Epoch 以来的秒数来度量的, Epoch 亦即通用协调时间( UTC,以前也称为格林威治标准时间,或 GMT)的 1970 年 1月 1 日早晨零点。这也是 UNIX 系统问世的大致日期。日历时间存储于类型为 time_t 的变量中,此类型是由 SUSv3 定义的整数类型。

    系统调用 gettimeofday(),可于 tv 指向的缓冲区中返回日历时间。

    #include<sys/time.h>
    int gettimeofday(struct timeval *tv,struct timezone *tz);
    struct timeval{
       time_t tv_sec;      /*1970年1月1日零点以来的秒数*/
       suseconds_t tv_usec;/*微秒*/
    }
    /* tz 是个历史产物。早期的 UNIX 实现用其来获取系统的时区信息,目前已遭废弃,应始终将其置为 NULL*/

    虽然 tv_usec 字段能提供微秒级精度,但其返回值的准确性则由依赖于构架的具体实现来决定。 tv_usec 中的 u 源于与之形似的希腊字母 μ(读音“ mu”),在公制系统中表示百万分之一。在现代 X86-32 系统上,gettimeofday()的确可以提供微秒级的准确度

    time()系统调用返回自 Epoch 以来的秒数(和函数 gettimeofday()所返回的 tv 参数中 tv_sec字段的数值相同)。

    #include<time.h>
    time_t time(time_t *timep);
    t=time(NULL);

    如果 timep 参数不为 NULL,那么还会将自 Epoch 以来的秒数置于 timep 所指向的位置。由于 time()会以两种方式返回相同的值,而使用时唯一可能出错的地方是赋予 timep 参数一个无效地址( EFAULT),因此往往会简单地采用如下调用(不做错误检查)

    之所以存在两个本质上目的相同的系统调用( time()和 gettimeofday()),自有其历史原因。早期的 UNIX 实现提供了 time()。而 4.3BSD 又补充了更为精确的 gettimeofday()系统调用。这时,再将 time()作为系统调用就显得多余,可以将其实现为一个调用 gettimeofday()的库函数。

    10.2 时间转换函数

    10.2.1 将 time_t 转换为可打印格式

    为了将 time_t 转换为可打印格式, ctime()函数提供了一个简单方法。

    #include<time.h>
    char *ctime(const time_t *timep);

    把一个指向 time_t 的指针作为 timep 参数传入函数 ctime(),将返回一个长达 26 字节的字符串,内含标准格式的日期和时间,如下例所示:

    Wed Jun 8 14:22:34 2011

    该字符串包含换行符和终止空字节各一。 ctime()函数在进行转换时,会自动对本地时区和 DST 设置加以考虑。返回的字符串经由静态分配,下一次对 ctime()的调用会将其覆盖。 SUSv3 规定,调用 ctime()、 gmtime()、 localTime()或 asctime()中的任一函数,都可能会覆盖由其他函数返回,且经静态分配的数据结构。换言之,这些函数可以共享返回的字符数组和 tm 结构体,某些版本的 glibc 也正是这样实现的。如果有意在对这些函数的多次调用间维护返回的信息,那么必须将其保存在本地副本中

    10.2.2 time_t 和分解时间之间的转换

    函数 gmtime()和 localtime()可将一 time_t值转换为一个所谓的分解时间( broken-downtime)。分解时间被置于一个经由静态分配的结构中,其地址则作为函数结果返回。

    #include<time.h>
    struct tm *gmtime(const time_t *timep);/*函数gmtime()能够把日历时间转换为对应于UTC的分解时间*/
    struct tm *localtime(const time_t *timep);/*返回对应于系统本地时间的一个分解时间*/

    函数 gmtime()能够把日历时间转换为一个对应于 UTC 的分解时间。 (字母 GM 源于格林威治标准时间)。相形之下,函数 localtime()需要考虑时区和夏令时设置,返回对应于系统本地时间的一个分解时间。

    在这些函数所返回的 tm 结构中,日期和时间被分解为多个独立字段,其形式如下:

    struct tm{
       int tm_sec;  /*秒*/
       int tm_min;  /*分*/
       int tm_hour; /*时*/
       int tm_mday; /*天*/
       int tm_mon;  /*月*/
       int tm_year; /*年*/
       int tm_wday; /*一星期中的天*/
       int tm_yday; /*一年中的天*/
       int tm_isdst;/**/
    }

    将字段 tm_sec 的上限设为 60(而非 59)以考虑闰秒,偶尔会用其将人类日历调整至精确的天文年(所谓的回归年)。

    函数 mktime() 将一个本地时区的分解时间翻译为 time_t值,并将其作为函数结果返回。调用者将分解时间置于一个 tm 结构,再以 timeptr 指针指向该结构。这一转换会忽略输入 tm结构中的 tm_wday 和 tm_yday 字段

    #include<time.h>
    time_t mktime(struct tm *timeptr);/*函数mktime()将一个本地时区的分解时间翻译为time_t值*/

    函数 mktime()可能会修改 timeptr 所指向的结构体,至少会确保对 tm_wday 和 tm_yday 字段值的设置,会与其他输入字段的值能对应起来。 此外, mktime()不要求 tm 结构体的其他字段受到前述范围的限制。任何一个字段的值超出范围, mktime()都会将其调整回有效范围之内,并适当调整其他字段。所有这些调整,均发生于 mktime()更新 tm_wday 和 tm_yday 字段并计算返回值 time_t 之前。

    例如,如果输入字段 tm_sec 的值为 123,那么在返回时此字段的值将为 3,且 tm_min 字段值会在其之前值的基础上加 2。(如果这一改动造成 tm_min 溢出, 那么将调整 tm_min 的值,并且递增 tm_hour 字段,以此类推。 )这些调整甚至适用于字段负值。

    例如,指定 tm_sec 为-1 即意味着前一分钟的第 59 秒。此功能允许以分解时间来计算日期和时间,故而非常有用。 mktime()在进行转换时会对时区进行设置。此外, DST 设置的使用与否取决于输入字段tm_isdst 的值。

    1.若 tm_isdst 为 0,则将这一时间视为标准间(即忽略夏令时)。

    2.若 tm_isdst 大于 0,则将这一时间视为夏令时(即,夏令时生效,即使每年的此时不处于夏令时阶段)。

    3.若 tm_isdst 小于 0,则试图判定 DTS 在每年的这一时间是否生效。这往往是众望所归的设置。

    (无论 tm_isdst 的初始设置如何)在转换完成时, 如果针对给定的时间, DST 生效, mktime()会将 tm_isdst 字段置为正值,若 DST 未生效,则将 tm_isdst 置为 0。

    10.2.3 分解时间和打印格式之间的转换

    本节会介绍将分解时间和打印格式相互进行转换的函数。

    从分解时间转换为打印格式

    在参数 tm 中提供一个指向分解时间结构的指针, asctime()则会返回一指针,指向经由静态分配的字符串,内含时间,格式则与 ctime ()相同。

    #include<time.h>
    char *asctime(const struct tm *ptr);

    相形于 ctime(),本地时区设置对 asctime()没有影响,因为其所转换的是一个分解时间,该时间通常要么已然通过 localtime()作了本地化处理,要么早已经由 gmtime()转换成了 UTC。如同 ctime()一样, asctime()也无法控制其所生成字符串的格式。

    当把一个分解时间转换成打印格式时, 函数 strftime()可以提供更为精确的控制。 令 timeptr指向分解时间, strftime()会将以 null 结尾、由日期和时间组成的相应字符串置于 outstr 所指向的缓冲区中。

    #include<sys/time.h>
    size_t strftime(char *outstr,size_t max_size,const char *format,const struct tm *timeptr)

    outstr 中返回的字符串按照 format 参数定义的格式做了格式化。 Maxsize 参数指定 outstr 的最大长度。不同于 ctime()和 asctime(), strftime()不会在字符串的结尾包括换行符(除非 format 中定义有换行符)。

    如果成功, strftime()返回 outstr 所指缓冲区的字节长度,且不包括终止空字节。如果结果字符串的总长度,含终止空字节,超过了 maxsize 参数,那么 strftime()会返回 0 以示出错,且此时无法确定 outstr 的内容。

    strftime()的 format 参数是一字符串,与赋予 printf()的参数相类似。冠以百分号( %)的字符序列是对转换的定义,函数会将百分号后的说明符字符一一替换为日期和时间的组成部分。这是一套相当丰富的转换说明符,表 10-1 中所列的是其一个子集。(完整的列表可见诸于strftime(3)手册页。 )除非特别注明,所有这些转换说明符都符合 SUSv3 标准。

    %U 和%W 说明符都生成一年中的周数。 %U 的周数按以下方法计算。含有星期日的第一周编号为 1,此周的前一周编号为 0。如果星期天恰巧是当年的第一天,那么就没有第 0 周, 当年的最后一天则属于第 53 周。 %W 的周数编号以同样的方式来计算,只不过计算对象是周一而非周日。

    通常情况下,我们希望在本书的各种示范程序中显示当前时间。为此,本书提供了函数currTime(),其返回一字符串,内含 strftime()按 format 参数格式化的当前时间。

    #include "curr_time.h"
    char *currTime(const char *format);

     

    将打印格式时间转换为分解时间

    函数 strptime()是 strftime()的逆向函数,将包含日期和时间的字符串转换成一分解时间。

    #include<time.h>
    char *strptime(const char *str,const char *format,struct tm *timeptr);

    函数 strptime()按照参数 format 内的格式要求, 对由日期和时间组成的字符串 str 加以解析,并将转换后的分解时间置于指针 timeptr 所指向的结构体中。

    如果成功, strptime()返回一指针,指向 str 中下一个未经处理的字符。 如果无法匹配整个格式字符串,strptime()返回 NULL,以示出现错误。 strptime()的格式规范类似于 scanf(3),包含以下类型的字符。

    1.转换字符串冠以一个百分号( %)字符。

    2.如包含空格字符,则意味着其可匹配零个或多个空格。

    3.( %之外的)非空格字符必须和输入字符串中的相同字符严格匹配。

    转换说明类似于之前为 strftime()给出的内容(表 10-1)。主要的区别在于,此处的说明符更为通用。例如,不拘于星期名称的全称或简称, %a 和%A 都可接受,而且%d 和%e 均可用于读取月中的个位天数,无论该数字前面是否有 0。此外,不区分大小写,例如, May 和 MAY是相同的月份名称。使用字符串%%来匹配输入字符串中的百分号字符。 strptime(3)手册页提供有更多的细节。

    glibc 在实现 strptime()时,并不修改 tm 结构体中那些未获 format 说明符初始化的字段。这也意味着可以根据多个字符串, 例如, 一个日期字符串和一个时间字符串, 发起多次 strptime()调用,来创建一个 tm 结构体。 SUSv3 虽然允许这一行为,但并不强制要求实现,因此在其他UNIX 实现上不能对其有所依赖。要保证应用的可移植性,就必须确保,要么 str 和 format 中所含输入信息足以设置最终 tm 结构的所有字段,要么在调用 strptime()之前对 tm 结构体已经做了适当的初始化处理。在大多数情况下,用 memset()把整个结构体置为 0 也就足够了,但要留心,在 glibc 和许多其他时间转换函数的实现中, m_mday 字段值为 0,意为上月的最后一天。最后还要注意, strptime()从不设置 tm 结构体的 tm_isdst 字段。

    10.5 更新系统时钟

    我们现在来看两个更新系统时钟的接口: settimeofday()和 adjtime()。这些接口都很少被应用程序使用,因为系统时间通常是由工具软件维护,如网络时间协议( Network Time Protocol)守护进程,并且它们需要调用者已被授权( CAP_SYS_TIME)。 系统调用 settimeofday()是 gettimeofday()的逆向操作(这是我们在 10.1 节中描述的)。它将 tv 指向 timeval 结构体里的秒数和微秒数,设置到系统的日历时间。

    #include<sys/time.h>
    int settimeofday(const struct timeval *tv,const struct timezone *tz);
    /*tz 参数已被废弃,这个参数应该始终指定为 NULL*/

    settimeofday()调用所造成的那种系统时间的突然变化, 可能会对依赖于系统时钟单调递增的应用造成有害的影响(例如, make(1),数据库系统使用的时间戳或包含时间戳记的日志文件)。 出于这个原因, 当对时间做微小调整时(几秒钟误差), 通常是推荐使用库函数 adjtime(),它将系统时钟逐步调整到正确的时间。

    #include<sys/time.h>
    int adjtime(struct timeval *delta,struct timeval *olddelta);

    delta 参数指向一个 timeval 结构体, 指定需要改变时间的秒和微秒数。 如果这个值是正数,那么每秒系统时间都会额外拨快一点点,直到增加完所需的时间。如果 delta 值为负时,时钟以类似的方式减慢

    在 adjtime()函数执行的时间里,它可能无法完成时钟调整。在这种情况下,剩余未经调整的时间存放在 olddelta 指向的 timeval 结构体内。如果我们不关心这个值,我们可以指定olddelta 为 NULL。相反,如果我们只关心当前未完成时间校正的信息,而并不想改变它,我们可以指定 delta 参数为 NULL。

    虽然 SUSv3 未定义 adjtime(),可大多数 UNIX 实现提供了这个函数。

    10.7 进程时间

    进程时间是进程创建后使用的 CPU 时间数量。出于记录的目的,内核把 CPU 时间分成以下两部分。

    1.用户CPU 时间是在用户模式下执行所花费的时间数量。有时也称为虚拟时间( virtual time),这对于程序来说,是它已经得到 CPU 的时间。

    2.系统 CPU 时间是在内核模式中执行所花费的时间数量。这是内核用于执行系统调用或代表程序执行的其他任务(例如,服务页错误)的时间。

    有时候,进程时间是指处理过程中所消耗的总 CPU 时间。 当我们运行一个 shell 程序, 我们可以使用的 time(1)命令, 同时获得这两个部分的时间值,以及运行程序所需的实际时间

    time ./prog

    系统调用 times(),检索进程时间信息,并把结果通过 buf 指向的结构体返回。

    #include<sys/times.h>
    clock_t times(struct tms *buf);
    /*buf 指向的 TMS 结构体有下列格式:*/
    struct tms{
       clock_t tms_utime; /*用户CPU时间*/
       clock_t tms_stime; /*系统CPU时间*/
       clock_t tms_cutime;/*子进程用户CPU时间*/
       clock_t tms_cstime;/*子进程系统CPU时间*/
    }

    clock_t clock(void);/*进程使用的总CPU时间*/

    如果成功, times()返回自过去的任意点流逝的以时钟计时单元为单位的(真实的) 时间。 SUSv3特别未定义这点是什么,只是说,这将是在调用进程的生命周期内的一个固定点。因此,这个返回值唯一的用法是通过计算一对 times()调用返回的值的差,来计算进程执行消耗的时间。然而,即使是这种用法, times()的返回值仍然不可靠的, 因为它可能会溢出clock_t的有效范围, 这时times()的返回值将再次从 0 开始计算(也就是说,一个稍后的 times()的调用返回的数值可能会低于一个更早的 times()调用)。可靠的测量经过时间的方法是使用函数 gettimeofday()(10.1 节所述)。 在 Linux 上,我们可以指定 buf 参数为 NULL。在这种情况下, times()只是简单地返回一个函数结果。然而,这是没有意义的。 SUSv3 并未定义 buf 可以使用 NULL,因此许多其他UNIX 实现需要这个参数必须为一个非 NULL 值。

    time()的返回值的计量单位是 CLOCKS_PER_SEC,所以我们必须除以这个值来获得进程所使用的 CPU 时间秒数。在 POSIX.1, CLOCKS_PER_SEC 是常量 10000,无论底层软件时钟( 10.6 节)的分辨率是多少。 clock()的精度最终仍然受限于软件时钟的分辨率。 即使 CLOCKS_PER_SEC 是常量 10000, SUSv3 注明,这个常量在不兼容 XSI( non-XSIconformant)的系统上可以为整型变量,所以,我们不能简单地把它作为一个编译时常量(即,我们不能够使用# ifdef 预处理表达式)。它可能会被定义为一个长整数(即 1000000L),我们总是将这个常量转换为 long,因此我们可以简单地用 printf() 把它打印输出(见 3.6.2 节)。 SUSv3 描述 clock()应该返回“进程所使用的处理器时间”时有不同的解释。在一些 UNIX的实现中, clock()返回的时间包含所有等待子进程使用的 CPU 时间。而在 Linux 上,它不包括。

    10.8 总结

    真实时间对应于时间定义的每一天。当真实时间通过一些标准点计算的时候,我们称它为日历时间。和经过的时间相对,它是度量一个进程生命周期中的一些点(通常是开始)。 进程时间是由一个进程使用的 CPU 时间量,并划分为用户时间和系统时间。 多种系统调用允许我们获取和设置系统时钟值(即日历时间, 以秒为单位从 Epoch 计算),以及一系列的库函数能够完成从日历时间到其他时间格式之间的转换,包括分解时间和具有可读性字符串。描述这种转换把我们引入了地区和国际化的讨论。 使用和显示时间和日期是许多应用程序的一个重要组成部分,我们会在这本书后面的章节中经常使用到本节描述的功能。我们也会在第 23 章更多地介绍时间的度量。

  • 相关阅读:
    linux一切皆文件之tcp socket描述符(三)
    linux一切皆文件之Unix domain socket描述符(二)
    linux一切皆文件之文件描述符(一)
    k8s之使用secret获取私有仓库镜像
    https、ssl、tls协议学习
    k8s网络之calico学习
    泛型的原理、应用、约束、缓存
    C#中Unity对象的注册方式与生命周期解析
    监听EF执行的sql语句及状态
    递归一个List<T>,可自己根据需要改造为通用型。
  • 原文地址:https://www.cnblogs.com/wangbin2188/p/14836399.html
Copyright © 2011-2022 走看看