☰
【linux内核专栏 08】时间与定时器
2026/10/4 8:05:20 网站建设 项目流程

本篇定位:定时器从裸机"一个 mtimecmp"跳到"hrtimer + clocksource + tick + NOHZ"多层。本篇把"Linux 时间子系统"对你已知的增量讲透。读完能写 hrtimer 驱动、能配 NOHZ 省电、能解释 FreeRTOS tick 和 Linux tick 的差异。

FreeRTOS tick 是 Linux 时间子系统的简化版
FreeRTOS tick:你配 mtimecmp,周期中断 →xTaskIncrementTick→ 调度。Linux 时间子系统复杂得多:clocksource(时钟源)+clock_event(时钟事件)+tick(调度心跳)+hrtimer(高精度定时器)+NOHZ(tickless 省电)。核心增量:① 多时钟源抽象;② 高精度定时器(纳秒级,不是固定 tick);③ tickless 省电(空闲时停 tick)。


一、时间子系统全景

┌─────────────────────────────────────┐ │ 硬件时钟源(每个 SoC 多个) │ │ - RISC-V: rdtime 寄存器(高精度计数)│ │ - ARM: Generic Timer(物理计数器) │ │ - HPET / TSC(x86) │ └────────────┬────────────────────────┘ │ ┌─────────▼──────────┐ │ clocksource │ ← 读时间(单调递增计数器) │ (只读,提供 ns) │ └─────────┬──────────┘ │ ┌─────────▼──────────┐ │ timekeeping │ ← 维护当前时间(墙上时间/单调时间) └─────────┬──────────┘ │ ┌─────────▼──────────┐ ┌──────────────────┐ │ tick 设备 │<───│ clock_event │ ← 触发下次中断 │ (周期/单次事件) │ │ (可编程中断源) │ └─────────┬──────────┘ └──────────────────┘ │ ┌─────────▼──────────┐ │ tick: 调度心跳 │ ← 周期中断,Scheduler_tick │ hrtimer: 高精度定时│ ← 红黑树,ns 级 └────────────────────┘

三个核心抽象

抽象作用硬件
clocksource读时间(单调递增计数器)RISC-V rdtime / ARM CNTVCT
clock_event_device触发中断(可编程)RISC-V sbi_set_timer / ARM CNTV_TVAL
tick_device基于 clock_event,周期或单次调度心跳

二、clocksource:读时间

2.1 clocksource 是什么

  • 一个单调递增的计数器,提供"当前 ns"
  • 只读,不触发中断
  • RISC-V:rdtime指令读timeCSR(你 [[05-中断系统 PLIC-CLINT-AIA]] 学的 CLINT mtime)

2.2 RISC-V clocksource

// arch/riscv/kernel/time.c(简化)staticu64riscv_clocksource_read(structclocksource*cs){returnget_cycles();// 读 rdtime}staticstructclocksourceriscv_clocksource={.name="riscv_clocksource",.rating=300,.read=riscv_clocksource_read,.mask=CLOCKSOURCE_MASK(64),.flags=CLOCK_SOURCE_IS_CONTINUOUS,};

2.3 多 clocksource 与 rating

  • 一个 SoC 可能有多个时钟源(TSC + HPET / rdtime + 外部 RTC)
  • 每个 clocksource 有rating(质量分),内核选最高的
  • 运行时可切换(若更高 rating 的可用)

2.4 timekeeping:维护墙上时间

  • 基于 clocksource,维护xtime(墙上时间,UTC)
  • 和monotonic(自启动单调时间)
  • NTP 调整频率
# 看当前 clocksourcecat/sys/devices/system/clocksource/clocksource0/current_clocksource# arch_sys_counter (RISC-V rdtime)# 可用 clocksourcecat/sys/devices/system/clocksource/clocksource0/available_clocksource

三、clock_event_device:触发中断

3.1 是什么

  • 可编程中断源:设"下次多久后中断"
  • 用于 tick(周期)或 hrtimer(单次精确)
  • RISC-V:SBI 调用设定时器(sbi_set_timer),底层写 mtimecmp

3.2 RISC-V timer 驱动

// drivers/clocksource/timer-riscv.c(简化)staticintriscv_timer_set_next_event(unsignedlongdelta,structclock_event_device*ce){// 设下次中断 = 当前 rdtime + deltasbi_set_timer(get_cycles()+delta);return0;}staticstructclock_event_deviceriscv_clock_event={.name="riscv_timer_clockevent",.features=CLOCK_EVT_FEAT_ONESHOT,// 单次模式.rating=100,.set_next_event=riscv_timer_set_next_event,};

3.3 对照你 RISC-V mtimecmp

你裸机Linux RISC-V timer
写 mtimecmp = mtime + deltasbi_set_timer(rdtime + delta)
中断 → 你 handler中断 → hrtimer/tick 框架
直接寄存器经 SBI(因为 Linux 在 S 态,mtimecmp 在 M 态)

Linux RISC-V timer 走 SBI
你裸机在 M 态直接写 mtimecmp。Linux 在 S 态,mtimecmp 是 M 态寄存器,不能直接写——经 SBI 调用(ecall to M),OpenSBI 替你写 mtimecmp。这是RISC-V 专题(S 态不能直接碰 M)在时间子系统的体现。


四、tick:调度心跳

4.1 tick 是什么

  • 周期性中断(默认 100/250/1000 Hz)
  • 每次 tick:① 更新时间统计;②scheduler_tick(检查时间片/调度);③ 处理过期定时器

4.2 tick 频率

# 看 CONFIG_HZcat/proc/interrupts|grepLOC# LOC 是 local timer(tick)# 数每秒增长 = HZ
HZ周期用途
10010ms服务器(吞吐,少切换)
2504ms桌面(默认)
10001ms嵌入式/低延迟
300~3.3ms某些

4.3 scheduler_tick

每次 tick(本核):

  1. 更新当前进程时间统计(utime/stime)
  2. 更新 CFS vruntime
  3. 检查时间片到没 → 设 need_resched
  4. 触发 RCU 回收检查

4.4 对照 FreeRTOS tick

FreeRTOS tickLinux tick
触发mtimecmp 周期中断clock_event 周期中断
做什么xTaskIncrementTick + 调度scheduler_tick + timekeeping
频率configTICK_RATE_HZ(常 1000)HZ(100~1000)
时间片优先级 + 抢占CFS vruntime
你配port.c 配 mtimecmp内核自动(你选 HZ)

嵌入式视角:你 port 层配的就是 Linux tick 底层
FreeRTOS V11.1 port 层配 mtimecmp 周期中断 → Linux RISC-V 的 clock_event 设 mtimecmp(经 SBI)→ 同一硬件机制。增量:Linux 在上面建了 clocksource/timekeeping/hrtimer 多层;FreeRTOS 直接 tick 调度。你的 port 层经验直接迁移。


五、hrtimer:高精度定时器 ⭐

5.1 为什么需要 hrtimer

  • 传统定时器(timer_list)基于 tick,精度 = tick 周期(10ms/4ms/1ms)
  • 有些场景要 ns 级精度(音视频/网络/实时)
  • hrtimer(High-Resolution Timer)独立于 tick,基于 clocksource,ns 级

5.2 hrtimer 数据结构

  • 每 CPU 一个 hrtimer 红黑树(按到期时间排序)
  • 最左节点 = 最近到期
  • clock_event 设单次中断,到点触发 → 遍历过期 hrtimer → 回调

5.3 hrtimer 用法

structhrtimermy_timer;// 回调enumhrtimer_restartmy_callback(structhrtimer*t){printk("Timer fired!\n");// 若要重复,return HRTIMER_RESTART 并重新 forwardreturnHRTIMER_NORESTART;}// 初始化 + 启动hrtimer_init(&my_timer,CLOCK_MONOTONIC,HRTIMER_MODE_REL);my_timer.function=my_callback;hrtimer_start(&my_timer,ms_to_ktime(100),HRTIMER_MODE_REL);// 100ms 后// 取消hrtimer_cancel(&my_timer);

5.4 周期 hrtimer

enumhrtimer_restartmy_periodic(structhrtimer*t){// 重新设下次hrtimer_forward_now(t,ms_to_ktime(100));// 再 100msdo_work();returnHRTIMER_RESTART;}

5.5 hrtimer 回调的约束

  • 在硬中断上下文(或 softirq,看模式)
  • 不能睡
  • 要快(影响精度)
  • 要睡用 hrtimer 的 softirq 模式或 workqueue 推迟

[!important] hrtimer 是你写定时驱动的首选
你裸机用 mtimecmp 做周期任务。Linux 用hrtimer——ns 级精度,红黑树管理,API 清晰。写驱动要周期任务(轮询/超时),优先 hrtimer。要睡的活从回调里 schedule_work 推迟。


六、timer_list(低精度定时器)

6.1 老接口,简单但精度低

structtimer_listmy_timer;timer_setup(&my_timer,my_fn,0);my_timer.expires=jiffies+msecs_to_jiffies(100);// 100ms 后(对齐 tick)add_timer(&my_timer);// 或mod_timer(&my_timer,jiffies+HZ);// 改到期del_timer(&my_timer);
  • 基于 jiffies(tick 计数),精度 = tick
  • 新代码优先 hrtimer,老代码常用 timer_list

七、jiffies vs ktime

7.1 jiffies

  • 内核启动以来的 tick 数
  • HZ次/秒,即jiffies / HZ= 秒
  • 简单但精度低(tick 级)
unsignedlongtimeout=jiffies+HZ;// 1 秒后if(time_after(jiffies,timeout)){...}// 超时

7.2 ktime_t

  • 64 位 ns 级时间
  • hrtimer 用
ktime_tkt=ktime_get();// 当前单调 nsktime_tt=ms_to_ktime(100);// 100ms 转 ktimes64 ns=ktime_to_ns(kt);u64 us=ktime_get_ns()/1000;// 微秒

7.3 选哪个

场景用
tick 级够(timer_list/超时判断)jiffies
ns 级(hrtimer/精确测量)ktime
墙上时间(用户态)gettimeofday/clock_gettime

八、延时:忙等 vs 睡眠

8.1 忙等(短延时,不能睡时用)

udelay(10);// 10 微秒忙等mdelay(5);// 5 毫秒忙等(不要长,占 CPU)ndelay(100);// 100 纳秒
  • 基于 CPU 自旋计数,不准但简单
  • 中断上下文/持锁时用(不能睡)
  • 别长(浪费 CPU)

8.2 睡眠(可睡时用)

msleep(100);// 100ms 睡(精度 tick)msleep_interruptible(100);// 可被信号打断usleep_range(100,200);// 100-200us 睡(更省电,允许调度器合并)schedule_timeout(timeout);// 通用
  • 让出 CPU(不忙等)
  • 精度取决于调度(tick 级,usleep_range 稍好)
  • 进程上下文用

8.3 决策

能睡? 是 ──> msleep / usleep_range / schedule_timeout 否(中断/持锁) ──> udelay / mdelay(短)

中断里只能 udelay,不能 msleep
[[05-中断与下半部]] 学的"中断不能睡"——中断里要延时只能udelay(忙等)。msleep 会 schedule,中断里 schedule = panic。持自旋锁同理只能 udelay。


九、NOHZ(tickless,省电)⭐

9.1 为什么 NOHZ

  • tick 每 HZ 次中断,即使 CPU 空闲也打断
  • 空闲时 tick 浪费电(尤其嵌入式/移动)
  • NOHZ:CPU 空闲时停 tick,直到下次 hrtimer 或中断

9.2 NOHZ_IDLE(空闲 tickless)

  • CPU 进 idle 时关 tick
  • 下次中断(若有 hrtimer/IO)才唤醒
  • 省电,嵌入式必开

9.3 NOHZ_FULL(全 tickless)

  • 即使 CPU 跑任务也不 tick(除非该 CPU 只有任务)
  • 用于实时/HPC(减少 tick 干扰)
  • 需至少一个 CPU 保留做 timekeeping(dedicated housekeeping CPU)

9.4 配置

# 看 NOHZcat/proc/cmdline|grepnohz# nohz=on / nohz_full=1-3(CPU1-3 全 tickless,CPU0 做 housekeeping)# 看是否启用cat/sys/devices/system/cpu/vulnerabilities/...# 不相关,看 NOHZ:zcat /proc/config.gz|grepNOHZ# CONFIG_NO_HZ_IDLE=y / CONFIG_NO_HZ_FULL=y

9.5 对照 FreeRTOS tickless

FreeRTOS ticklessLinux NOHZ_IDLE
空闲时configUSE_TICKLESS_IDLENOHZ_IDLE
停 tickportSUPPRESS_TICKS_AND_SLEEP内核自动
配 mtimecmp 重设你 port 层实现clock_event 自动
省电是是

嵌入式视角:你 FreeRTOS tickless 就是 Linux NOHZ
FreeRTOS tickless idle(port 层portSUPPRESS_TICKS_AND_SLEEP)——停 tick,设下次 mtimecmp,进 WFI。Linux NOHZ_IDLE 同构——idle 时停 tick,设下次 clock_event,进 idle(WFI)。你 port 层实现的 tickless,就是 Linux NOHZ 的简化版。增量:Linux 多了 NOHZ_FULL(任务也停 tick)。


十、用户态时间 API

10.1 时间获取

structtimespects;clock_gettime(CLOCK_MONOTONIC,&ts);// 单调时间(自启动)clock_gettime(CLOCK_REALTIME,&ts);// 墙上时间(可被 NTP 调)gettimeofday(&tv,NULL);// 墙上时间(老接口)// 高精度structtimespects;clock_gettime(CLOCK_MONOTONIC_RAW,&ts);
  • CLOCK_MONOTONIC:单调(不回退),适合测耗时
  • CLOCK_REALTIME:墙上(可被改),适合时间戳

10.2 VDSO 加速

gettimeofday/clock_gettime(CLOCK_MONOTONIC|REALTIME)走 VDSO(06 篇),不进内核。

10.3 命令行

date# 墙上时间uptime# 启动以来time./cmd# 测耗时

十一、延时与定时速查

需求API上下文
ns 级定时器hrtimer_start回调在硬中断
tick 级定时器timer_list / add_timer回调在 softirq
us 短延时udelay任意(忙等)
ms 短延时mdelay任意(忙等,别长)
ms 睡眠msleep进程
us 睡眠usleep_range进程
超时等待schedule_timeout进程
用户态时间clock_gettime用户
当前 nsktime_get内核
当前 jiffiesjiffies内核

十二、对照总表

概念FreeRTOSRISC-V bare-metalLinux
时钟源mtimerdtime/mtimeclocksource(rdtime)
周期中断tick(mtimecmp)你配 mtimecmptick(clock_event)
高精度定时软定时器(基于 tick)你写hrtimer(ns 级)
ticklesstickless idle你写 WFINOHZ_IDLE/FULL
延时忙等N/A你写udelay/mdelay
延时睡眠vTaskDelayN/Amsleep/usleep_range
时间表示tick countrdtime 值jiffies / ktime
设置定时器port 层写 mtimecmpSBI 调用(经 M)

十三、本篇小结

  • 时间子系统三抽象:clocksource(读时间)+clock_event(触发中断)+tick_device(周期/单次)
  • RISC-V:clocksource 用 rdtime,clock_event 经 SBI 写 mtimecmp(Linux 在 S 态,不能直接写 M 态)
  • tick:周期中断,scheduler_tick驱动调度;HZ=100~1000
  • hrtimer:ns 级高精度定时器,红黑树,新代码首选(回调在硬中断,不能睡)
  • timer_list:tick 级低精度,老接口
  • jiffies(tick 计数)vs ktime(ns),按精度选
  • 延时:udelay/mdelay 忙等(中断/持锁用),msleep/usleep_range 睡眠(进程用)
  • NOHZ:空闲停 tick 省电(NOHZ_IDLE),全 tickless(NOHZ_FULL,实时/HPC)
  • 用户态 clock_gettime 走 VDSO,不进内核
  • FreeRTOS tickless idle = Linux NOHZ_IDLE 同构

速查表

想干啥用什么
读当前 nsktime_get_ns()
读当前 jiffiesjiffies
100ms 后的 jiffiesjiffies + msecs_to_jiffies(100)
ns 级定时器hrtimer_start(t, ns, MODE_REL)
tick 级定时器timer_setup + add_timer
中断里延时udelay(us)
进程里睡眠msleep(ms) / usleep_range(min,max)
超时等待schedule_timeout(jiffies)
看当前 clocksourcecat /sys/devices/system/clocksource/clocksource0/current_clocksource
看 HZzcat /proc/config.gz | grep CONFIG_HZ
配 NOHZcmdline nohz=on / nohz_full=CPUs
用户态测耗时clock_gettime(CLOCK_MONOTONIC)
看定时器cat /proc/timer_list

💡技术之路漫漫,分享是为了更好地交流。如果本文的内容对你有启发,希望能得到你的点赞 👍和收藏 ⭐。

如果你在调试过程中遇到了其他问题,欢迎在评论区 💬留言,我们一起探讨。也欢迎关注 👀我,一起交流底层开发的那些事儿。


需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询