TLPI读书笔记第23章:定时器与休眠2
POSIX 时钟所提供的时钟访问 API 可以支持纳秒级的时间精度,其中表示纳秒级时间值的 timespec 结构同样也用于 nanosleep()( 23.4.2 节)调用。 Linux 中,调用此 API 的程序必须以-lrt 选项进行编译,从而与 librt(realtime,实时)函数库相链接。 POSIX 时钟 API 的主要系统调用包括获取时钟当前值的 clock_gettime()、 返回时钟分辨率的 clock_getres(),以及更新时钟的 clock_settime()。
23.5.1 获取时钟的值: clock_gettime()
系统调用 clock_gettime()针对参数 clockid 所指定的时钟返回时间。
返回的时间值置于 tp 指针所指向的 timespec 结构中。虽然 timespec 结构提供了纳秒级精度,但 clock_gettime()返回的时间值粒度可能还是要更大一点。系统调用 clock_getres()在参数 res中返回指向 timespec 结构的指针,机构中包含了由 clockid 所指定时钟的分辨率。 clockid_t 是一种由 SUSv3 定义的数据类型,用于表示时钟标识符。表 23-1 中第 1 列值即可用于设定 clockid
CLOCK_REALTIME时钟是一种系统级时钟,用于度量真实时间。与CLOCK_MONOTONIC 时钟不同,它的设置是可以变更的。 SUSv3 规定, CLOCK_MONOTONIC 时钟对时间的度量始于“未予规范的过去某一时点”, 系统启动后就不会发生改变。 该时钟适用于那些无法容忍系统时钟发生跳跃性变化(例如:手工改变了系统时间)的应用程序。 Linux 上,这种时钟对时间的测量始于系统启动。 CLOCK_PROCESS_CPUTIME_ID时钟测量调用进程所消耗的用户和系统 CPU 时间。 CLOCK_THREAD_CPUTIME_ID时钟的功用与之相类似,不过测量对象是进程中的单条线程。 SUSv3 规范了表 23-1 中的所有时钟,但强制要求实现的仅有 CLOCK_REALTIME一种,这同时也是受到 UNIX 实现广泛支持的时钟类型。
23.5.2 设置时钟的值: clock_settime()
系统调用 clock_settime()利用参数 tp 所指向缓冲区中的时间来设置由 clockid 指定的时钟。
如果由 tp 指定的时间并非由 clock_getres()所返回时钟分辨率的整数倍,时间会向下取整。特权级(CAP_SYS_TIME)进程可以设置 CLOCK_REALTIME 时钟。该时钟的初始值通常是自 Epoch(1970 年 1 月 1 日 0 点 0 分 0 秒)以来的时间。表 23-1 中的其他时钟均不可更改。
23.5.3 获取特定进程或线程的时钟 ID
要测量特定进程或线程所消耗的 CPU 时间,首先可借助本节所描述的函数来获取其时钟ID。接着再以此返回 id 去调用 clock_gettime(),从而获得进程或线程耗费的 CPU 时间。 函数 clock_getcpuclockid()会将隶属于 pid 进程的 CPU 时间时钟的标识符置于 clockid 指针所指向的缓冲区中。
参数 pid 为 0 时, clock_getcpuclockid()返回调用进程的 CPU 时间时钟 ID。 函数 pthread_getcpuclockid()是 clock_getcpuclockid()的 POSIX 线程版,返回的标识符所标识的时钟用于度量调用进程中指定线程消耗的 CPU 时间。
参数 thread 是 POSIX 线程 ID,用于指定希望获取的 CPU 时钟 ID 所从属的线程。返回的时钟 ID 存放于 clockid 指针所指向的缓冲区中。
23.5.4 高分辨率休眠的改进版: clock_nanosleep()
类似于 nanosleep(), Linux 特有的 clock_nanosleep()系统调用也可以暂停调用进程,直到历经一段指定的时间间隔后,亦或是收到信号才恢复运行。本节将讨论两者间的差异。 参数 request 及 remain 同 nanosleep()中的对应参数目的相似。 默认情况下(即 flags 为 0),由 request 指定的休眠间隔时间是相对时间(类似于 nanosleep())。 不过,如果在 flags(参考程序清单 23-4)中设定了 TIMER_ABSTIME, request 则表示 clockid时钟所测量的绝对时间。这一特性对于那些需要精确休眠一段指定时间的应用程序至关重要。 如果只是先获取当前时间,计算与目标时间的差距,再以相对时间进行休眠,进程可能执行到一半就被占先了1,结果休眠时间比预期的要久。 如 23.4.2 节所述,对于那些被信号处理器函数中断并使用循环重启休眠的进程来说,“嗜睡(oversleeping)”问题尤其明显。如果以高频率接收信号,那么按相对时间休眠(nanosleep()所执行的类型)的进程在休眠时间上会有较大误差。但可以通过如下方式来避免嗜睡问题: 先调用 clock_gettime()获取时间,加上期望休眠的时间量,再以 TIMER_ABSTIME 标志调用clock_nanosleep()函数(并且,如果被信号处理器中断,则会重启系统调用)。
指定 TIMER_ABSTIME 时,不再(且不需要)使用参数 remain。如果信号处理器程序中断了 clock_nanosleep()调用,再次调用该函数来重启休眠时, request 参数不变。 将 clock_nanosleep()与 nanosleep()区分开来的另一特性在于,可以选择不同的时钟来测量休眠间隔时间。可在 clockid 中指定所期望的时钟 CLOCK_REALTIME、 CLOCK_ MONOTONIC或 CLOCK_PROCESS_CPUTIME_ID。请参考表 23-1 对这些时钟的描述。 程序清单 23-4 演示了 clock_nanosleep()的用法:针对 CLOCK_REALTIME 时钟,以绝对时间休眠 20 秒。