第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时钟值,重启会失效

 

posted @   moonのsun  阅读(28)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!
点击右上角即可分享
微信分享提示