第6章 定时测量
1. 作用
Linux内核的“定时测量”主要有两个作用:① 获取时间日期 ② 定时人物。
2. 时钟和定时器电路
① 实时时钟 RTC
- 独立电源(蓄电池)供电;Linux用来获取时间和日期
② 时间戳计数器 TSC
- 和CPU频率绑定,每个时钟信号到来时+1
- 系统初始化期间算出CPU实际频率
- 便于测量间隔极小的时间
- CPU 动态调频时(比如节省功耗),TSC频率会发生改变,造成时间偏差或其他不良效果
③ 可编程定时间隔 PIT
- 永远以固定频率发出中断,比如1ms(1000Hz)
- 主要用来定时,精度低,但是总是存在
④ CPU 本地定时器 APIC
类似PIT,区别是:
- APIC计数器位数更多,可以产生更加低频的中断(定时最大间隔更大)
- APIC定时器把中断发给本地CPU,PIT为全局中断
- APIC 基于总线时钟信号机型定时器计数,PIT 内部有时钟振荡器
⑤ 高精度时间定时器 HPET
- Intel和MS联合研发,频率至少为10MHz,功能更全,精度更高,将会取代PIT
- 可以“把寄存器映射到内存空间”来对HPET芯片编程
⑥ ACPI 电源管理定时器
- 动态调频时,TSC不准,可以使用ACPI替代,精度还可以
- 也可以被HPET完全替代
3. Linux计时体系结构
使用上述硬件资源搭建的计时体系结构;
3.1 计时体系结构的数据结构
① 定时器对象 struct timer_opts
- mark_offset:记录上一个节拍的准确时间;时钟中断irq调用
- get_offset:自上一个节拍开始所经过的时间(us)
- monotonic_clock:自内核初始化开始所经过的ns
- 依托定时器优先级顺序:HPET > ACPI > TSC > PIT
② jiffies 变量
- 记录自系统启动以来产生的节拍数
- 32位变量,50天溢出
- 被初始化一个很大的值,5min溢出,以检查内核代码是否可以正常处理溢出
- 80x86系统中被声明为64位计数器(jiffies_64)的低32位,不用担心溢出
- 因为jiffies_64是由两个32位计数器组成,因此get_jiffies_64()需要xtime_lock顺序锁(seqlock)来保护
- 更新(写)jiffies_64时也需要xtime_lock保护
③ xtime 变量
- tv_sec:自1970.1.1 00:00(UTC)以来经过的秒数
- tv_nsec:自上一秒经过的ns数
- 大约1ms更新一次
- 内部同样需要xtime_lock保护
④ cur_timer
- 系统可利用的最好的定时器资源
3.2 初始化阶段相关定时器操作
- 从实时时钟RTC获取时间,填充给xtime
- 按照HPET > PIT的优先级,出发1000Hz的定时器中断IRQ 0
- 使能定时器中断处理程序timer_interrupt,并周期性调用
3.3 时钟中断处理程序 timer_interrupt()
- 在xtime_lock顺序锁上产生一个write_seqlock(),保护定时器变量
- 调用do_timer_interrupt():jiffies_64++;update_times()更新系统日期时间;update_process_times()更新本地cpu统计数;
- write_sequnlock()释放顺序锁,并退出中断
3.4 更新时间和日期
也就是上面在中断处理程序timer_interrupt() -> do_timer_interrupt() 中调用的update_times();:
① wall_jiffies <- jiffies: 更新wall_jiffies
② update_wall_times(ticks): 更新xtime
4. 软定时器和延迟函数
4.1 动态定时器
① struct timer_list
动态定时器放在timer_list中,它们一一对应;
- struct list_head entry:该动态定时器所在的链表,用于将定时器插入双向循环链表中,并分组存放
- unsigned long expires:定时器到期时间,和jiffies比较判断是否到期
- void (*function) (unsigned long):定时器到期执行的函数
- unsigned long data:定时器回调函数的入参
- tvec_base_t *base:用于动态定时器分组的结构体;分组后的定时器可以更快的找到即将执行的定时器
- spinlock_t lock
② 动态定时器用法
- 创建一个新的timer_list对象t
- init_timer(&t)初始化:① 把t.base置为NULL ② 把t.lock打开
- 填充function和data
- 如果定时器没有插入链表:给expires赋值,并把定时器插入链表
- 如果定时器插入了链表:修改合适的expires
- 定时器到期:内核自动把t从链表中删除,或干脆直接删除定时器
- 动态定时器到期之前,可以被撤销
③ 动态定时器与竞争条件
- 动态定时器作用的资源被丢弃:丢弃资源前,撤销定时器
- 动态定时器删除时,正在其他CPU上运行:改用del_timer_sync()
④ 动态定时器应用之一:nanosleep()系统调用
- 借助struct timer_list timer,通过schedule_timeout实现
4.2 延迟函数
- 包括udelay()和ndelay()
- 通过紧凑指令循环的loops此循环实现
- 依赖cur_timer定时器对象的delay方法,指向某个硬件计数器
5. 与定时测量相关的系统调用
5.1 gettimeofday()系统调用
① 返回自1970.1.1 00:00(UTC)以来经过的秒数,以及前1秒内走过的us数,存放在timeval结构体中
② struct timeval
- tv_sec
- tv_usec
和struct xtime非常接近,但是又不能直接获取,原因可以继续往下看。
③ 执行步骤
- 获取xtime_lock
- 通过get_offset()获取自上一次中断以来走过的us:
usec = cur_timer->getoffset()
- 如果定时器中断丢失,则加上延迟:
usec += (jiffies - wall_jiffies) * 1000
至此usec还是表示上次中断以来走过的us数 - 位usec加上前一秒内走过的us数:
usec += (xtime.tv_nsec / 1000)
- 赋值timeval结构体:
tv->tv_sec = xtime->tv_sec
tv->usec = usec
- xtime_lock解锁
- 检查tv_sec是否溢出(前面的usec有可能溢出):
while (tv->tv_usec >= 1000 000) { tv->tv_usec -= 1000 000; tv->tv_sec++; }
“如果定时器中断丢失,则加上延迟:usec += (jiffies - wall_jiffies) * 1000”:
- 中断中会更新mark_offset,这个值很精确,但是保不准中断会丢失;
- 每次中断都会更新wall_jiffies = jiffies;jiffies记录着分辨率更高(精度或许一般?)的值,用来解决上次中断到现在的用时
④ settimeofday()
- 修改系统时间日期
- 没有改变RTC时钟值,重启会失效
本文来自博客园,作者:moonのsun,转载请注明原文链接:https://www.cnblogs.com/moon-sun-blog/p/18626677
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!