Linux内核时间子系统

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更新xtimextime+tick_nsec

自此系统时间 xtime 代替了硬件时间RTC,当系统结束时将系统时间写入RTC

而维护系统时间的就是ticktick 是怎么知道过了HZ时间的呢?

clocksource

  • 系统拥有不同的硬件时钟源,这些时钟源一般可以通过精度来分成五级

  • 内核中为了保证时钟源精度划分的稳定性,内核启用了一个定时器 clocksource watchdog0.5s周期监控是否大于WATCHDOG_THRESHOLD大于时即将精度调为最差等级

它通常实现为一个由固定时钟频率驱动的计数器

时间轮Time Wheel

在 Jiffies 计数正常后 也就代表着低精度定时器的计时基本单位完成

在一个系统中,拥有的定时器是成百上千的,我们怎么才能精准的定位到到期的定时器从而去执行它对应的函数呢?

Time Wheel

不同的cpu管理自身的tevs_base结构体,在其中根据定时器到期的jiffies值和当前cpu的jiffies值timer_jiffies 进行比较 ,产生的差值记为 idx,将定时器划分为tv1-tv5 五个组

定时器链表组别idx
tv10-255(2^8)
tv2256–16383(2^14)
tv316384–1048575(2^20)
tv41048576–67108863(2^26)
tv567108864–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_timerstruct timer_list *timer
激活定时器add_timerstruct timer_list *timer激活一个定时器
修改定时器mod_timerstruct timer_list *timer, unsigned long expires修改到期定时器
删除定时器del_timer_syncstruct 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_initstruct hrtimer *timer, clockid_t which_clock,enum hrtimer_mode mode初始化定时器
hrtimer_startstruct hrtimer *timer, ktime_t tim,const enum hrtimer_mode mode激活定时器
hrtimer_start_range_nsstruct hrtimer *timer, ktime_t unsigned long range_ns, const enum hrtimer_mode modetim,指定到期时间并激活定时器
hrtimer_cancelstruct hrtimer *timer取消定时器
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值