文章目录
Linux内核时间子系统
低精度定时器
时钟源为jiffies,即精度为1/HZ
eg:HZ为1000时,时间精度为1ms
基于HZ的定时器机制被称为时间轮(Time Wheel)
虽然从2.6.16内核
后出现了高精度定时器hirtime
但其只是一个内核可选配置项,故而低精度定时器任然广泛使用
时钟源 clocksource
晶振 -> 分频 -> RTC计时 -> xtime
->在系统初始化时,通过读取硬件时钟RTC模块得到当前时间
->将当前时间写入xtime
中,其中tick_nsec
保存当前HZ换算的ns值 eg:1000.15HZ = 999848ns 作为时间最小单位
->此刻tick
启动,每经过HZ
时间将jiffies
加1
->当jiffies
增加时调用update_wall_time
更新xtime
即 xtime+tick_nsec
自此系统时间 xtime 代替了硬件时间RTC,当系统结束时将系统时间写入RTC
而维护系统时间的就是tick
而tick
是怎么知道过了HZ
时间的呢?
clocksource
-
系统拥有不同的硬件时钟源,这些时钟源一般可以通过精度来分成五级
-
内核中为了保证时钟源精度划分的稳定性,内核启用了一个定时器
clocksource watchdog
来0.5s
周期监控是否大于WATCHDOG_THRESHOLD
大于时即将精度调为最差等级
它通常实现为一个由固定时钟频率驱动的计数器
时间轮Time Wheel
在 Jiffies 计数正常后 也就代表着低精度定时器的计时基本单位完成
在一个系统中,拥有的定时器是成百上千的,我们怎么才能精准的定位到到期的定时器从而去执行它对应的函数呢?
Time Wheel
不同的cpu管理自身的
tevs_base
结构体,在其中根据定时器到期的jiffies
值和当前cpu的jiffies值
timer_jiffies 进行比较 ,产生的差值记为idx
,将定时器划分为tv1-tv5
五个组
定时器链表组别 | idx |
---|---|
tv1 | 0-255(2^8) |
tv2 | 256–16383(2^14) |
tv3 | 16384–1048575(2^20) |
tv4 | 1048576–67108863(2^26) |
tv5 | 67108864–4294967295(2^32) |
从上可知tv1-tv5
占据一个数的32位
,tv1为低八位,其次递增六位
所以在内核中使用超时时间expires
作为索引值
当expires = 257时,定时器为tv2,将expires的
bit8-bit13
作为数组索引下标,即定时器链接到了tv2[1]
我们现在知道了定时的存放规则,那么它这么存放的意义是什么呢?
为了实现时间轮的调度算法
我们按idx存放定时器,那tv[0]是马上要到期的定时器吗?
不是,
idx
是定时器到期的jiffies值
和当前cpu的jiffies值
timer_jiffies进行比较产生的差值,其中timer_jiffies是动态的增加的假设当前的`timer_jiffies = 0x12345678 那么马上到期的为tv1[78],现在加入一个定时器 expires = 0x12345680 ,根据存放原则,此定时器按expires低八位存放,即存tv1[80],那么再运行两个tick 后 timer_jiffies = 0x12345680 即tv1[80]到期
那什么时候处理tv2-tv5的定时器?
每当expires的低八位为零时,就代表对expires的bit8-bit13存在进位,此时我们将expires的bit8-bit13作为下标去tv2里面寻找对应的定时器,将其从tv2移除,再重新加入定时器,因其timer_jiffies动态增加,此时的idx已经处在tv1的范围,从而加入tv1中等待到期调用,tv3-tv5同理
- 总结
Time Wheel 实现了O(1)的快速查找能力,使256个tick处理一次迁移操作,如同五个齿轮,低级齿轮转动一圈,高一级齿轮转动一齿
时间维护 timekeeper
以上都是描述内核系统运行的时间,对于用户而言需要一个确切的时间,而不是jiffies计数
所以内核为了满足不同的需求,提供了不同的时间线
时间线 | 意义 |
---|---|
RTC时间 | 硬件时间,一般而言精度为s |
wall time | 系统时间,xtime,精度可达ns |
monotonic time | 系统运行时间(不算休眠,受时间同步影响) eg:当前是1990+7年,NTP后变成1990+5年,那么monotonic time 改变 |
raw monotonic time | 系统运行时间(不算休眠,不会受时间同步影响) |
boot time | 系统开机时间(包括休眠) |
这些不同的时间线的推进方式都不一样,所以为了方便管理,内核采用了 timekeeper 结构体来进行不同时间线的增加、读取等操作
当然 时间线必然有一个是最先出现的
RTC时间 用来初始化各类时间线,接着获取对应的clocksource
自此系统就脱离了 RTC 利用自身的clocksource 进行各类时间线的推进
timekeeper为内核提供了许多读取不同时间线的接口
API接口
步骤 | 函数 | 参数 | 意义 |
---|---|---|---|
静态初始化 | DEFINE_TIMER | _name, _function, _expires, _data | 定义一个timer_list 结构体,超时函数、超时值、函数数据初始化到结构体 |
动态初始化 | init_timer | struct timer_list *timer | |
激活定时器 | add_timer | struct timer_list *timer | 激活一个定时器 |
修改定时器 | mod_timer | struct timer_list *timer, unsigned long expires | 修改到期定时器 |
删除定时器 | del_timer_sync | struct timer_list *timer | 等待timer处理完后删除定时器 |
其中动态初始化后还需对timer_list
结构体中超时函数、超时值、函数数据单独赋值
timer.expires = expires;--------------超时值
timer.fuction = function;-------------超时函数
timer.data = data;---------------------超时函数参数
mod_timer
常用来给到期的定时器重新赋超时值,用作周期性触发
高精度定时器
随着对内核定时器精度要求不断提高,内核需要支持高精度时钟的定时器,因种种原因,与低精度定时器的代码不重用,独立实现
其实现架构为红黑树 rbtree
clock_event_device
跟clocksource相比较,clock_event_device是一种可以编程、产生事件的时钟设备接口,其精度跟硬件定时器精度息息相关,一般能达到ns级
开启高精度定时器模式
make menuconfig==>
General setup==>
Timers subsystem==>
[*] High Resolution Timer Support
当为高精度模式时,其tick由高精度定时器系统实现,精度达到ns,同时为了支持低精度时钟,内核会单独开辟一个hirtimer 模拟jiffies的产生
hirtimer工作原理
hrtimer系统需要通过timekeeper获取当前的时间
计算与到期时间的差值,并根据该差值,设定该cpu的tick_device(clock_event_device)的下一次的到期时间,时间一到
在clock_event_device的事件回调函数中处理到期的hrtimer。
API接口
函数 | 参数 | 意义 |
---|---|---|
hrtimer_init | struct hrtimer *timer, clockid_t which_clock,enum hrtimer_mode mode | 初始化定时器 |
hrtimer_start | struct hrtimer *timer, ktime_t tim,const enum hrtimer_mode mode | 激活定时器 |
hrtimer_start_range_ns | struct hrtimer *timer, ktime_t unsigned long range_ns, const enum hrtimer_mode modetim, | 指定到期时间并激活定时器 |
hrtimer_cancel | struct hrtimer *timer | 取消定时器 |