浅谈linux定时器模型

简介:

用户态的定时器设计

记得某段时间的工作中,经常会用到定时器。发现有些同学为了图方便,会这样实现定时器:
while(1) {
sleep_awhile();
while((timer = get_expired_timer()))
do_timer_handler(timer);
}
用一个线程,周期性地睡眠一段时间,然后起来看看有没有需要触发的定时任务。

这种定时器写起来确实很简单,但是也让人感觉很拙。一方面,周期性的睡眠与唤醒,占用了一定的调度开销,并且定时线程被唤醒之后,经常是无事可做的。另一方面,定时器的精度非常之低(依赖于定时线程的睡眠时间)。若想提高精度,只能缩短睡眠时间,从而造成更大的调度开销。
当然,在一些特定的场景下,这样的定时器也未必不能很好地满足需求。但是多数情况下,这还是不够理想的。

换一种思路感觉会更好一些,从timer本身出发,按需睡眠(timer需要等待多久就睡眠多久,而不是周期性地睡眠)。
这样的定时器实现起来会稍麻烦一些,需要将所有的timer按其expire_time增序排序。定时线程每次需要sleep的时间就是当前时间到第一个timer的expire_time的间隔时间。当新增一个排在第一位的timer、或者删除排在第一位的timer时,都需要调整定时线程的睡眠时间。
于是,定时线程每一次从睡眠中被唤醒,总是伴随着一个(或多个)timer需要被处理。并且定时的精度很高(依赖于操作系统提供的定时精度)。

内核态的定时器设计

linux内核里面的定时器似乎也面临着这样的问题,从《linux时钟浅析》一文中可以看到,内核管理的时钟和定时器实际上采用的是“周期性睡眠”的这种方案。时钟源周期性地发出中断(一般以1ms为周期),时钟中断处理程序每一次都会更新时间、更新一些统计信息、然后检查并触发定时器。
显然,时钟中断会周期性地打断CPU上正在执行的任务。同时,定时器的精度又因为依赖于时钟中断的周期,而受到限制。而减小时钟中断的周期又会带来更频繁的中断,造成更大的开销。

从第一次了解到linux的时钟机制开始,我就在想,是否这种机制也能改造成“按需睡眠”(按需触发时钟中断)的方案呢?从而减小时钟中断的开销、增加定时器的精度。

但是注意到,时钟中断处理程序需要做的事情并不限于处理定时器(比这个要复杂得多)。“按需睡眠”的方案可能会非常复杂,甚至可能会增加时钟与其他子系统之间的耦合度。
比如说,调度程序依赖周期性的时钟中断来更新当前进程的执行时间,从而不断更新进程的动态优先级,从而决定当前进程是否应该被替换掉(见《linux进程调度浅析》)。如果改用“按需睡眠”,则调度程序必须在当前进程被调度运行时,就预料到该进程将在运行多久时间之后被取代,从而设置相应的定时器。并且,当当前进程状态改变、或者某些中断使某些进程状态改变、等情况发生时,这个定时器可能需要更新。
再比如,由于“按需睡眠”,系统时间就不能保持周期性的更新了。应用程序读系统时间时,可能就要有两种做法:如果需要高精度的时间,只能通过读硬件时钟源设备来解决;对于低精度的(比如只需要精确到秒),则可以通过设置较长的周期性时钟中断来解决(比如设置每秒一次的时钟中断,这样的开销几乎是可以忽略的)。
再比如,当应用程序需要去读各种统计信息时,可能就只能实时去计算了,而不能直接得到已经由周期性时钟中断更新好的统计结果。
此外,受影响的地方应该还有很多……

可能是由于太过复杂,以至于可能得不偿失(各种复杂情况综合下来,“按需睡眠”可能并不比“周期性睡眠”开销小),“按需睡眠”的方案似乎没有被人们怎么考虑过。
然而,高精度的定时器却是很有需求的,于是linux内核中就有了hrtimer(High-resolution Timer)。

hrtimer就是按“按需睡眠”的方案去实现的,通过红黑树去维护一组按expire_time排好序的timer。然后将时钟源设备设置为非周期性触发中断的模式,并总是设置下一次中断触发的时间就是第一个timer的expire_time。(当然,这些都需要硬件的支持。)当需要使用到高精度的定时器时,就可以向这棵红黑树添加timer。
那么,原来的那一套“周期性睡眠”的机制怎么办呢?hrtimer工作在原先那套机制的底层,内核会向hrtimer注册周期性的timer,从而获得周期性的时钟中断,原来的“周期性睡眠”这一套东西完全不用变。
(关于hrtimer,详见《Linux 时钟管理》。)

hrtimer通过将时钟的底层从“周期性睡眠”改为“按需睡眠”,实现了高精度的定时器(精度依赖于硬件)。而将来“按需睡眠”的层次是否会得到提升(使得“周期性睡眠”的模式被修改),从而减少时钟中断的次数,以降低开销呢?

另外,如果你读了上面那篇《Linux 时钟管理》,可以发现,linux内核已经有了nohz这种模式。开启nohz模式后,就不存在周期性的时钟中断了。
但是这种模式出现的原因并不是为了实现我们这里说的“按需睡眠”,而是为了让CPU在长期没有任务的情况下能够逐渐进入休眠,以达到节能的目的(周期性的时钟中断会不断给CPU制造任务,使其不得安宁)。于是,内核总是试图在CPU进入空闲时(进入空闲,代表已经没有任务需要执行了),开启nohz模式;在退出空闲时,停止nohz模式。
不过既然周期性的时钟中断没有了,很多需要依赖它的部分也需要做修改。就比如之前举例说明的那些,只是仅为支持nohz还不需要改得那么彻底。那么,今后这些修改又会否更加彻底,从而实现“按需睡眠”呢?


目录
相关文章
|
1月前
|
Linux C语言 SoC
嵌入式linux总线设备驱动模型分析
嵌入式linux总线设备驱动模型分析
32 1
|
2月前
|
监控 网络协议 Linux
Linux多路转接or多路复用模型
【2月更文挑战第5天】
36 1
|
4月前
|
缓存 网络协议 Unix
Linux(UNIX)五种网络I/O模型与IO多路复用
Linux(UNIX)五种网络I/O模型与IO多路复用
107 0
|
3月前
|
Linux
Linux 下的五种 IO 模型详细介绍
根据上述定义,我们的前4种模型——阻塞式I/O模型、非阻塞式I/O模型、I/O复用模型和信号驱动式I/O模型都是同步I/O模型,因为其中真正的I/O操作(recvfrom )将阻塞进程。异步请求:A调用B,B的处理是异步的,B在接到请求后先告诉A我已经接到请求了,然后异步去处理,处理完之后通过回调等方式再通知A。和上面的阻塞IO模型相比,非阻塞IO模型在内核数据没准备好,需要进程阻塞的时候,就返回一个错误,以使得进程不被阻塞。阻塞请求:A调用B,A一直等着B的返回,别的事情什么也不干。
35 0
Linux 下的五种 IO 模型详细介绍
|
23天前
|
监控 Linux 编译器
Linux C++ 定时器任务接口深度解析: 从理论到实践
Linux C++ 定时器任务接口深度解析: 从理论到实践
67 2
|
2月前
|
Linux
Linux 驱动开发基础知识——总线设备驱动模型(八)
Linux 驱动开发基础知识——总线设备驱动模型(八)
37 0
Linux 驱动开发基础知识——总线设备驱动模型(八)
|
2月前
|
Linux
Linux 驱动开发基础知识——总线设备驱动模型(七)
Linux 驱动开发基础知识——总线设备驱动模型(七)
50 0
Linux 驱动开发基础知识——总线设备驱动模型(七)
|
2月前
|
网络协议 Linux
Linux下的网络编程——B/S模型HTTP(四)
Linux下的网络编程——B/S模型HTTP(四)
34 0
|
2月前
|
网络协议 大数据 Linux
Linux下的网络编程——C/S模型 UDP(三)
Linux下的网络编程——C/S模型 UDP(三)
47 0
Linux下的网络编程——C/S模型 UDP(三)