Linux时钟精度:毫秒?微妙?纳秒?


扫盲:1秒=1000毫秒=1000000微妙=1000000000纳秒

首先:linux有一个很重要的概念——节拍,它的单位是(次/秒)。2.6内核这个值是1000,系统中用一个HZ的宏表征这个值。同时有全局的jiffies变量,表征从开机以来通过的节拍次数(这里面还有故事,后面说,先记住这个)。固然还有wall_jiffies的墙上jiffies来表示从 07-01-1970 到如今的节拍数。每一个节拍里面执行一次时钟中断。就是说,它的精度是毫秒

接着:内核中还有一个变量xtime表征系统的实际时间(墙上时间),定义以下。其中xtime.tv_sec以秒为单位,存放从Unix祖宗定的纪元时间(19700701)到如今的秒数。xtime.tv_nsec以纳秒为单位,记录从上一秒开始通过的纳秒数。就是说,它的精度是纳秒

struct timespec xtime;

struct timespec{
    time_t tv_sec;   //秒
    long tv_nsec;    //纳秒
};

最后:linux提供一个gettimeofday的系统调用,它会返回一个timeval的结构体,定义以下。解释同上,tv_sec表明墙上时间的秒,tv_usec表示从上一秒到如今通过的微秒数。就是说,它的精度是微妙

struct timeval{
    long tv_sec;     //秒
    long tv_usec;    //微妙
};

精彩的来了:
1. 内核中的xtime会在每一个时钟中断的时候被更新一次,也就是每一个节拍更新一次。你妹!!每毫秒更新一次怎么能冒出来纳秒的精度??并且,内核还有可能丢失节拍。怎么能是纳秒??
2. 各类书上说,gettimeofday系统调用是读取的xtime的值。日,为啥读出来以后精度丢了?变成微妙了?

寻寻觅觅终于理清了故事:
针对问题1:在linux启动的时候,一个节拍的时间长度还会以纳秒为单位初始化到tick_nsec中,初始化值为999848ns,坑爹啊!不到一毫秒!节拍大约为1000.15Hz。靠!实际的节拍居然不是准确的1000!因此在每一个时钟中断经过wall_jiffies去更新xtime的时候获得的就是一个以纳秒为最小单位的的值。因此!xtime的粒度应该是不到1毫秒,也就是精度是不到1毫秒。

针对问题2:gettimeday系统调用的读xtime代码部分以下:
do{
    unsigned long lost;
    seq = read_seqbegin(&xtime_lock);

    usec = timer->get_offset();    //在计时器中取从上一次时钟中断到如今的微秒数
    lost = jiffies - wall_jiffies;
    if(lost)
        usec += lost*(1000000/HZ); //HZ是节拍宏,值1000
    sec = xtime.tv_sec;
    usec += (xtime.tv_nsec/1000);  //由纳秒转为微妙

}while(read_seqretry(&xtime_lock, seq))

while部分使用了seg锁,只看中间的就行了。加了注释以后就很清晰了。因为节拍可能会丢失,因此lost是丢失的节拍数(不会不少)。至于计时器就比较麻烦了,timer可能有下面四种状况。

a. 若是cur_timer指向timer_hpet对象,该方法使用HPET定时器——Inter与Microsoft开发的高精度定时器频率至少10MHz,也就是说此时可提供真正的微妙级精度。
b. 若是cur_timer指向timer_pmtmr对象,该方法使用ACPI PMT计时器(电源管理定时器)平率大约3.58MHz,也就是说也能够提供真正的微妙级精度。
c. 若是cur_timer指向timer_tsc对象,该方法使用时间戳计数器,内置在全部8086处理器,每一个CPU时钟,计数器增长一次,频率就是CPU频率,因此timer精度最高。彻底能够胜任微妙级的精度。
d. 若是cur_timer指向timer_pit对象,该方法使用PIT计数器,也便是最开始提到的节拍计数,频率大概是1000Hz,此时显然不能提供精度达到微妙的时间。因此只有这种状况是假毫秒精度!

综上:若是使用gettimeofday系统调用,只要不要使用节拍计数器就能够保证达到微妙精度的时间(刨除进程上下文时间偏差)。至于网上说的能够拿到纳秒精度的时间,看起来都是错的。除非经过修改内核,使用时间戳计数器实现。Over!


最后最后说一个事情:jiffies的定义的是4字节,你可能猜测它初始值是0。实际上,事实并不是如此!linux中jiffies被初始化为0xfffb6c20,它是一个32位有符号数,正好等于-300 000。所以,计数器会在系统启动5分钟内溢出。这是为了使对jiffies溢出处理有缺陷的内核代码在开发阶段被发现,避免此类问题出如今稳定版本中。



参考《深刻理解linux内核》