1. Linux中time相关概念
1.1 real time
指的是实际流逝的时间,又称为Wall Clock Time(墙上时间)。
比如,time命令统计出的real time指的是该进程从开始运行到运行结束所消耗的时间。在这段时间内不仅仅执行了该进程,其他进程的时间片也得到了轮转。
1.2 process time
指的是执行某进程所消耗的CPU time。CPU time指的是执行该进程有关代码所花的时间,分为User CPU time和System CPU time两部分。
User CPU time指的是在用户态执行该进程的代码所花费的时间,不统计该进程阻塞花费的时间,也不统计其他进程的时间片。
System CPU time指的是在内核态执行该进程的代码所花费的时间,不统计该进程阻塞花费的时间,也不统计其他进程的时间片。。
其实,我觉得“该进程阻塞花费的时间”和“其他进程的时间片” 是一个意思。因为,进程阻塞时,内核会调度其他进程执行,所以我觉得这两个是一个意思。
需要注意,Real time != User CPU time + System CPU time。两个原因:
对于多核处理器上跑的多线程程序,会出现real time < User CPU time + System CPU time的情况。
毕竟计算机上不止一个进程在跑,real time中还统计了其他进程的时间片。
1.3 hardware clock
指计算机中电池供电的硬件时钟,记录了当前的墙上时间,又被称为RTC(Real Time Clock)。内核在启动时会读取该硬件时钟,来初始化内核中的软件时钟(Software Clock)。
1.4 software clock, HZ, and jiffy
software clock指的是内核维护的软件时钟。需要设置timeout的系统调用(例如,select,sigtimedwait)以及测量cpu time的系统调用(例如,getrusage)的准确度(accuracy)由软件时钟的精度(precision / resolution)决定。
Linux内核维护的软件时钟的精度是jiffy,也就是说软件时钟用jiffy衡量时间。jiffy对应的real time由内核中的常量HZ决定,jiffy = 1 / HZ。HZ的值可人为调节,可取的值在不同的内核版本和不同的硬件平台下也不一样。在i386平台下,内核版本2.6.0后,HZ的取值可以达到1000,意味着jiffy对应0.001秒。
那么为什么设置timeout的系统调用和测量cpu time的系统调用的准确度受jiffy的限制呢?原因如下。
cpu里面有可编程间隔定时器PIT,(Programmable interval timer),目前x86-64/arm/8051-based的绝大多数cpu/mcu都是内置PIT的,PIT以一个可调节的时间间隔,即jiffy,触发时钟中断,使得操作系统的时钟中断处理程序可以可调节地周期性运行。时钟中断处理程序负责维护所有的软件定时器,在当前进程的时间片用光,或有定时器触发时执行进程调度(线程调度)。时钟中断处理程序还负责维护软件时钟。因此,软件时钟的精度以及timer相关系统调用的精度都由jiffy限制。
1.5 High-resolution timers
在内核版本2.6.21之前,timer and sleep system calls 的准确度由jiffy限制。自从内核版本2.6.21之开始,Linux开始支持高精度定时器(High-resolution timers, HRTs)。
在支持HRTs的系统上,timer and sleep system calls 的准确度不再受jiffy限制,可以达到硬件级别的准确度。可以通过clock_getres()返回的时钟的精度判断系统是否支持高精度定时器。
sleep system calls 包括:
1.6 Epoch
Unix系统使用从1970-01-01 00:00:00 +0000 (UTC)到现在的秒数表示时间。1970-01-01 00:00:00 +0000 (UTC)这个时间点称为Epoch。
2. 获取时间
2.1 结构体
目前所知,表示时间只有两种结构体timeval和timespec,它们的区别主要是精度不同,有些函数的参数使用timeval,有些使用timespec。
struct timeval {
time_t tv_sec; /* seconds */
suseconds_t tv_usec; /* microseconds */
};
struct timespec {
time_t tv_sec; /* seconds */
long tv_nsec; /* nanoseconds */
};
2.2 函数
Linux中获取时间的函数有多种,这里我只提gettimeofday()和clock_gettime()。
2.2.1 gettimeofday()
int gettimeofday(struct timeval *tv, struct timezone *tz);
gettimeofday()是系统调用,用于获取从Epoch开始到现在的时间,精度是微秒。
各种资料表明,gettimeofday()读取的是内核中的xtime变量的值,xtime每个时钟中断更新一次,其精度受限于jiffy。既然是这样,那么为什么gettimeofday()的精度是微秒呢?经过我查阅资料,jiffy虽然会影响软件时钟的精度,但是gettimeofday()不是简单地读取xtime,gettimeofday()的实现仍然会依赖于硬件时钟。更具体的解释请查看[7]。
我个人认为,gettimeofday()的精度和内核是否支持高精度定时器并没有关系。一个是读取时间,一个是定时器,两者是不同的概念。查阅资料,没发现有人明确讲这个,以后遇到了再做补充吧。
2.2.2 clock_gettime()
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);
通过上面的接口可以访问多个时钟,这些时钟表示不同的含义。上面的接口中,用户需要通过clk_id指定对哪个时钟进行操作。其中clock_gettime()可以获取指定时钟的时间,clock_getres()可以获取指定时钟的精度。这些时钟的更新原理并不清楚,但是可以肯定的是,其精度和内核是否支持高精度定时器密切相关。因为,多个资料提到可以使用clock_getres()得到的精度判断内核是否支持高精度定时器。
clk_id的值:
CLOCK_REALTIME 墙上时间,真实的时间,但是受系统时钟(system clock)改变的影响,例如用户调用adjtime函数改变了系统时钟,那么该墙上时间就会随之改变。
CLOCK_MONOTONIC 该时钟用于测量相对的real time,该时钟和实际的时间相同的速度流逝,并且不被系统时钟(system clock)的手动/自动改变所影响。
CLOCK_PROCESS_CPUTIME_ID 用于测量进程所用的CPU time。
CLOCK_THREAD_CPUTIME_ID 用于测量线程所用的CPU time。
int timer_create (clockid_t clockid, struct sigevent *evp, timer_t *timerid);
该系统调用用于创建定时器,参数clk_id用于指定该定时器使用的时钟。该系统调用创建的定时器是高精度定时器。因此,我们使用clock_getres()得到的精度肯定应该小于jiffy。这就印证了之前说的,可以通过clock_getres()查看时钟的精度来判断内核是否支持高精度定时器。我自己使用clock_getres()测试的各时钟的精度是1ns,表示内核支持高精度定时器。
3. 某些系统调用的time accuracy
sleep的参数是秒,accuracy可以达到1秒。Linux下其由nanosleep实现,
usleep的参数是微秒,但是有人说其accuracy不是微秒,说它仍是由时间中断实现的,本人还没有找到资料证明usleep究竟有没有使用HRTs。
nanosleep的参数是纳秒,其由HRTs实现,使用CLOCK_MONOTONIC测量时间,不受jiffy限制,accuracy具体可以达到多少不清楚,但是应该可以达到微秒级别。
select的参数是微秒,别人测试accuracy可以达到微秒,有人说其由HRTs实现,但是本人未找到更具体的资料。
因此,选择延时函数应该一步到位直接用nanosleep。
4. Reference