linux低精度定时器timer_base
- 数据结构
lock: 用于保护定时器数据结构的锁
running_timer: 当前正运行中的定时器
clk: 定时器时钟,表示当前时钟周期
next_expiry: 下一个到期定时器的时钟
cpu: 该定时器队列绑定的CPU编号
is_idle: 是否处于空闲状态
must_forward_clk: 时钟是否需要向前推进
pending_map: 位图,标识每个槽位是否存在待过期定时器
vectors: 多级队列中每个槽位的定时器链表头
有LVL_DEPTH个时间轮深度,每个时间轮深度有LVL_SIZE个槽位,即总共有WHEEL_SIZE=LVL_SIZE * LVL_DEPTH个槽位。
- 重要函数
- static int calc_wheel_index(unsigned long expires, unsigned long clk)
根据expires定时器到时时间和当前定时器时间clk,得到具体的槽位idx。
以1000HZ的jiffies为例,第0深度时间轮每个槽位间隔为1ms,能够定时的范围为0ms-63ms;第1深度时间轮每个槽位间隔时间为8ms,能够定时的范围为64ms-511ms;第2深度时间轮每个槽位间隔时间为64ms,能够定时的范围为512ms-4095ms;以此类推。
-
static int __collect_expired_timers(struct timer_base *base,struct hlist_head *heads)
其实功能为根据clk当前时间将不同深度时间轮中到期的槽位哈希链表移动到heads中。实现原理为首先将clk与上槽位掩码得到第0深度时间轮的idx值,再与第0深度时间轮中的标志位进行比较若存在定时槽位即将槽位中的哈希链表移动到heads哈希链表中,随后进入第二次循环时clk先左移LVL_CLK_SHIFT位(即将时间每个槽位间隔时间由1ms增加到8ms)得到转换后的clk再与上槽位掩码得到第1深度时间轮的idx值。随后过程以此类推。 -
static unsigned long __next_timer_interrupt(struct timer_base *base)
搜索定时器队列(分多个级别)中的第一个到期定时器。函数从最低级别(LVL0)开始遍历每个级别,寻找该级别中的第一个到期timer。对于每个级别,它先通过next_pending_bucket函数搜索当前时钟在该级别槽位中的下一个即将到期位置。如果找到,则用该位置值左移对应bit范围得到该级别的下一个超时时钟,跟目前搜索结果next做比较取最小值。遍历完所有级别后,next就是整个定时器队列中的第一个到期timer时钟。
算法的细节在于级别时钟的增加规则:当前级别bit低位是否为0决定是否让上一级别时钟增1以查找下一个槽数值。即clk & LVL_CLK_MASK若不为0则表示clk在当前深度的时间轮中存在不为0在进行下一深度的查找时clk需要进行+1。
- static int next_pending_bucket(struct timer_base *base, unsigned offset,unsigned clk)
这里查找pending_map两次的原因是:
第一次查找find_next_bit使用的范围是从当前时钟位置start开始向后查找。如果在这个范围内没有找到待过期的timer槽位,说明当前级别内没有即将过期的timer。
为了全面检查此级别是否有timer,还需要从当前级别起始位置offset开始向前查找。因为pending_map是一个bitmap,记录此级别每个槽位是否有timer。从起始位置offset向前查找可以检测更早的timer。
例如时钟在位置10,但实际第一个过期timer在位置5,如果只从10向后查找找不到。所以第二次从offset向前查找,可以覆盖掉从开始位置枚举所有槽位的情况。
即
第一次从当前时钟向后查找本级别
第二次从开始位置向前查找本级别
合起来就能真正把这个级别内的所有槽位扫描一遍,找到下一个待过期的timer。
使用总结
低精度和高精度定时器。低精度定时器基于硬件的周期性中断实现,其定时周期的粒度为1/HZms,例如,内核HZ为1000,那么低精度的定时器最小定时时间为1ms;高精度定时器可以实现ns级的定时,不过,实际的定时周期粒度与CPU的主频有关,比如,桌面级的CPU一般都是GHZ级别,那么,其定时粒度可以达到ns级别,而对于嵌入式CPU,其主频一般在百兆级别,那么定时粒度就只能达到us级别了。
api函数:
timer_setup():初始化定时器和回调函数
mod_timer():修改定时器延迟时间
add_timer():添加定时器到定时器结构中
del_timer():删除定时器
timer_pending():检查定时器是否在运行
定时器在run_timer_softirq软中断上下文中执行到期定时器的回调函数。add_timer添加到定时器base链表,在执行完回调函数后该定时器即从base链表中删除了,下次使用时需调用add_timer重新添加到base链表。