ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

【linux内核专栏 08】时间与定时器

【linux内核专栏 08】时间与定时器 本篇定位:定时器从裸机一个 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 CNTVCTclock_event_device触发中断(可编程)RISC-V sbi_set_timer / ARM CNTV_TVALtick_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{.nameriscv_clocksource,.rating300,.readriscv_clocksource_read,.maskCLOCKSOURCE_MASK(64),.flagsCLOCK_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),底层写 mtimecmp3.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{.nameriscv_timer_clockevent,.featuresCLOCK_EVT_FEAT_ONESHOT,// 单次模式.rating100,.set_next_eventriscv_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)# 数每秒增长 HZHZ周期用途10010ms服务器(吞吐,少切换)2504ms桌面(默认)10001ms嵌入式/低延迟300~3.3ms某些4.3 scheduler_tick每次 tick(本核):更新当前进程时间统计(utime/stime)更新 CFS vruntime检查时间片到没 → 设 need_resched触发 RCU 回收检查4.4 对照 FreeRTOS tickFreeRTOS 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.functionmy_callback;hrtimer_start(my_timer,ms_to_ktime(100),HRTIMER_MODE_REL);// 100ms 后// 取消hrtimer_cancel(my_timer);5.4 周期 hrtimerenumhrtimer_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.expiresjiffiesmsecs_to_jiffies(100);// 100ms 后(对齐 tick)add_timer(my_timer);// 或mod_timer(my_timer,jiffiesHZ);// 改到期del_timer(my_timer);基于 jiffies(tick 计数),精度 tick新代码优先 hrtimer,老代码常用 timer_list七、jiffies vs ktime7.1 jiffies内核启动以来的 tick 数HZ次/秒,即jiffies / HZ 秒简单但精度低(tick 级)unsignedlongtimeoutjiffiesHZ;// 1 秒后if(time_after(jiffies,timeout)){...}// 超时7.2 ktime_t64 位 ns 级时间hrtimer 用ktime_tktktime_get();// 当前单调 nsktime_ttms_to_ktime(100);// 100ms 转 ktimes64 nsktime_to_ns(kt);u64 usktime_get_ns()/1000;// 微秒7.3 选哪个场景用tick 级够(timer_list/超时判断)jiffiesns 级(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 为什么 NOHZtick 每 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# nohzon / nohz_full1-3(CPU1-3 全 tickless,CPU0 做 housekeeping)# 看是否启用cat/sys/devices/system/cpu/vulnerabilities/...# 不相关,看 NOHZ:zcat /proc/config.gz|grepNOHZ# CONFIG_NO_HZ_IDLEy / CONFIG_NO_HZ_FULLy9.5 对照 FreeRTOS ticklessFreeRTOS ticklessLinux NOHZ_IDLE空闲时configUSE_TICKLESS_IDLENOHZ_IDLE停 tickportSUPPRESS_TICKS_AND_SLEEP内核自动配 mtimecmp 重设你 port 层实现clock_event 自动省电是是嵌入式视角:你 FreeRTOS tickless 就是 Linux NOHZFreeRTOS 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)。十、用户态时间 API10.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回调在 softirqus 短延时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驱动调度;HZ100~1000hrtimer: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()读当前 jiffiesjiffies100ms 后的 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 nohzon / nohz_fullCPUs用户态测耗时clock_gettime(CLOCK_MONOTONIC)看定时器cat /proc/timer_list技术之路漫漫分享是为了更好地交流。如果本文的内容对你有启发希望能得到你的点赞 和收藏 ⭐。如果你在调试过程中遇到了其他问题欢迎在评论区 留言我们一起探讨。也欢迎关注 我一起交流底层开发的那些事儿。
返回列表