Linux 计时体系结构

Linux必定执行与定时相关的操作。例如,内核周期性地:

  1. 更新自系统启动以来所经过的时间。

  2. 更新时间和日期。

  3. 确定当前进程在每CPU上已经运行了多长时间,如果已经超过分配给它的时间,则抢占它。时间片的分配将在第七章讨论。

  4. 更新资源使用统计数

  5. 检查每个软定时器的时间间隔是否已到。

Linux的计时体系结构是一组与时间流相关的内核数据结构和函数。实际上,基于80x86多处理器所具有的计时体系结构与单处理器机器所具有的稍有不同:

  1. 在单处理器系统上,所有的计时活动都是由全局定时器产生的中断触发的。

  2. 在多处理器系统上,所有普通的活动都是由全局定时器产生的中断触发的,而具体CPU的活动是由本地APIC定时器产生的中断触发的。

可惜,以上两种情况的区别有点模糊。例如,某些早期基于Intel80486处理器的SMP系统不拥有本地APIC。即使到了今天,还有一些SMP主板是有问题的,因此本地时钟中断是不稳定。在这些情况下,SMP内核体系必须采用单处理器系统的计时体系结构。另一方面,新近的单处理器系统拥有本地APIC,因此UP内核通常可以使用SMP的计时体系结构。不过,为了简化我们的讨论,我们不打算讨论这些混杂的情况,而集中于两种”纯”的计时体系结构。

Linux的计时体系结构还依赖于时间戳计数器、ACPI电源管理定时器、高精度事件定时器的可用性。内核使用两个基本的计时函数:一个保持当前最新的时间,另一个计算在当前秒内走过的纳秒数。有几种不同的方式获得后一个值:如果CPUTSCHPET,就可以用一些更精确的方法;在其它情况下,使用精确性差一些方法。

计时体系结构的数据结构

Linux2.6的计时体系结构使用了大量的数据结构。与以往一样,我们将描述80x86体系结构下最重要的变量。

定时器对象

为了使用一种统一的方法来处理可能存在的定时器,内核使用了”定时器对象”,它是timer_opts类型的一个描述符,该类型由定时器名称和四个标准的方法组成。如表6-1所示。

定时器对象中最重要的方法是mark_offsetget_offsetmark_offset方法由时钟中断处理程序调用,并以适当的数据结构记录每个节拍到来时的准确时间。get_offset方法使用已记录的值来计算自上一次时钟中断以来经过的时间。由于这两种方法,使得Linux计时体系结构能够达到子节拍的分辨度,也就是说,内核能够以比节拍周期更高的精度来测定当前的时间。这种操作被称作”定时插补”。

变量cur_timer存放了某个定时器对象的地址,该定时器是系统可利用的定时器资源中”最好的”。最初,cur_timer指向timer_none,这个timer_none是一个虚拟的定时器资源对象,内核在初始化的时候使用它。在内核初始化期间,select_timer()函数设置cur_timer指向适当定时器对象的地址。表6-2以优先级顺序列出了80x86体系结构中最常用的定时器对象。正如你所看到的,select_timer()将优先选择HPET;否则,将选择ACPI电源管理定时器;再次之时TSC,作为最后的方案,select_timer()选择总是存在的PIT。“定时插补”一列列出了定时器对象的mark_offset方法和get_offset方法所使用的定时器源,“延迟”一列列出了delay方法使用的定时器源。

注意,本地APIC定时器没有对应的定时器对象。因为本地APIC定时器仅用来产生周期性中断而从不用来获得子节拍的分辨度。


Jiffies变量

jiffies变量是一个计数器,用来记录自系统启动以来产生的节拍总数。每次时钟中断发生时它便加1。在80x86体系结构中,jiffies是一个32位的变量,因此每隔大约50天它的值会回到0,这对Linux服务器来说是一个相对较短的时间间隔。不过,由于使用了time_aftertime_after_eqtime_beforetime_before_eq四个宏,内核干净利索地处理了jiffies变量的溢出。

你可能猜想jiffies在系统启动的时候被初始化为0。实际上,事实并非如此:jiffies被初始化为0xfffb6c20,它是一个32位的有符号值,正好等于-300000。因此,计数器将会在系统启动后的5分钟内处于溢出状态。这样做是有目的的,使那些不对jiffies作溢出检测的内核代码在开发阶段被及时地发现,从而不不再出现在稳定的内核版本中。

但是在某些情况下,不管jiffies是否溢出,内核需要自系统启动以来产生的系统节拍的真实数目。因此,在80x86系统中,jiffies变量通过连接器被换算成一个64位计数器的低32位,这个64位的计数器被称作jiffies_64。在1ms为一个节拍的情况下,jiffies_64变量将会在数十亿年后才发生回绕,所以我们可以放心使用。

你可能要问为什么在80x86体系结构中jiffies不直接被声明成64位无符号的长整型数。答案是:在32位的体系结构中不能自动地对64位的变量进行访问。因此,在每次执行对64位数的读操作时,需要一些同步机制来保证当两个32位的计数器的值在被读取时这个64位的计数器不会被更新,结果是,每个64位的读操作明显比32位的读操作更慢。

xtime_lock顺序锁用来保护64位的读操作:该函数一直读jiffies_64变量直到确认该变量并没有同时被其它内核控制路径更新时才读取jiffies_64变量。

相反,当在临界区增加jiffies_64变量的值时必须使用write_seqlock(&xtime_lock)write_sequnlock(&xtime_lock)进行保护。注意++jiffies_64操作同时也会增加32位的jiffies变量的值,因为后者对应着jiffies_64的低32位。


Xtime变量

xtime变量存放当前时间和日期;它是一个timespec类型的数据结构,该结构有两个字段:

tv_sec

存放自197011日午夜以来经过的秒数

tv_nsec

存放自上一秒开始经过的纳秒数

xtime变量通常是每个节拍更新一次,也就是说,大约每秒更新1000次。正如我们将在后面的“与定时测量相关的系统调用”一节看到的那样,用户程序从xtime变量获得当前时间和日期。内核也经常引用它,例如,在更新节点时间戳时引用。

xtime_lock顺序锁消除了对xtime变量的同时访问而可能发生的竞争条件。记住xtime_lock同样也保护jiffies_64变量。一般而言,这个顺序锁用来定义计时体系结构中的一些临界区。



  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值