Linux 内核时间体系入门教程——从 CONFIG_HZ 到 hrtimer
2026/9/2 17:29:39 网站建设 项目流程

Linux 内核时间体系入门教程——从 CONFIG_HZ 到 hrtimer

读者对象:刚接触 Linux 内核的嵌入式开发者。读这份文档不需要任何内核时间子系统的前置知识,需要的预备知识已全部放在第 1 章。

环境背景:本文所有代码、结论、实验均基于 RK3588 + Linux 5.10.198(android12-5.10 分支)+ busybox 用户态,rockchip_linux_defconfig(CONFIG_HZ=300)。其他平台/版本结论基本通用,个别数值会不同。

如何读:新手按章节顺序读;有基础的可以直接跳第 4、6 章。每章开头有一句话导读。


第 1 章 预备知识

导读:这一章补齐理解后文必需的四个概念。已经熟悉的读者可以快速跳过,但建议至少扫一遍 1.2 的"上下文"概念,它是理解第 6 章的钥匙。

1.1 用户态与内核态:CPU 的两种"身份"

CPU 有特权级之分(ARM 上叫 EL0/EL1,x86 上叫 Ring3/Ring0):

  • 用户态:你的应用程序跑的地方。权限受限,不能直接碰硬件、不能直接读别的进程的内存。
  • 内核态:操作系统内核跑的地方。权限全开,直接操作硬件。

应用程序想做事(读文件、发网络包、睡觉),必须"申请"内核代劳,这个动作叫系统调用(syscall)

你的应用 内核 │ write(fd, buf, n) │ │ ──────── syscall ────→ │ ext4 文件系统 → 块层 → eMMC 驱动 │ ←────── 返回 ────────── │

这个区分直接引出后文的 utime/stime(第 5 章):CPU 跑你的代码的时间记 utime,CPU 在内核里替你干活的时间记 stime

1.2 中断:内核的"门铃"

想象两个查快递的方式:

  • 轮询:每 5 分钟跑下楼看一次有没有快递。简单,但大部分白跑。
  • 中断:在家干活,快递员按门铃你才下去。高效,事件驱动。

硬件设备(网卡、触摸屏、定时器)都用中断通知 CPU。中断处理分两级:

  • 硬中断(hardirq):门铃响了先冲下去签个字——必须,会打断当前正在跑的一切代码,期间不能睡眠、不能再被普通代码打断。
  • 软中断(softirq):签字后发现的复杂事情(拆包裹、整理发票)记到小本本上,等"稍后安全的时候"再处理。这个"稍后"就是软中断上下文。

关键概念"上下文(context)"——指"当前这段代码是以什么身份在跑":

上下文谁触发的能否睡眠
进程上下文系统调用、内核线程可以睡
硬中断上下文硬件中断绝对不能
软中断上下文硬中断"记账"延后的活不能

"睡眠"指主动让出 CPU 等待某个条件(比如等 100ms)。理解了这张表,第 6 章"回调跑在哪个上下文"的讨论就不再是天书。

1.3 定时器与"闹钟"

  • 硬件定时器:芯片里真实存在的电路,能"数数",也能"到点打铃"(产生中断)。就像一个自带闹钟功能的电子表。
  • 软件定时器:内核里登记的"备忘录"——“到 X 时刻请帮我执行函数 F”。它本身不是硬件,到期还是要靠硬件闹钟提醒。

记住这句话,第 6 章会反复用到:内核里所有定时器,最终都靠同一颗硬件闹钟提醒;"软件"只是登记和回调的代码

1.4 Kconfig 与 defconfig:内核的配置系统

内核有几千个功能开关,全部以CONFIG_开头:

CONFIG_HZ_300=y # 打开"300Hz 选项" # CONFIG_HZ_1000 is not set # 关闭"1000Hz 选项" CONFIG_HIGH_RES_TIMERS=y # 打开高精度定时器支持

这些开关定义在Kconfig文件里(kernel/Kconfig.hzkernel/time/Kconfig等),编译前由make xxx_defconfig生成最终的.config。改配置 → 重新编译 → 烧写内核,是内核开发的基本循环。


第 2 章 一切的源头:ARM 通用定时器

导读:先认识硬件,软件的一切都是围绕它搭建的。

RK3588 用的是ARM 通用定时器(arch_timer):全芯片共享一个系统计数器,每个 CPU 核各带一个自己的比较器(这正是第 7 章"hrtimer 柜台每个 CPU 一套"的硬件根基)。它有两个角色:

  1. 计数器(counter):一个硬件电路在永不停止地数数,纳秒级步进,不可写、不可清零。相当于一块一直在走的表
  2. 比较器(comparator):可编程的闹钟——设一个目标值,计数器数到它,就产生一个中断。

在内核里,这两个功能有两个专业名字:

硬件功能内核术语用途
计数器(读数)clocksource(时钟源)回答"现在几点了"
比较器(打铃)clockevent(时钟事件设备)回答"到时候叫我"

后面所有内容——jiffies、hrtimer、tick——都是这两个角色的不同"租户"。


第 3 章 两套时间世界

导读:内核的时间管理分成粗、细两套体系。理解这张图,后面的章节都只是展开它。

硬件:ARM Generic Timer (arch_timer) ┌───────────────────────────────────────────┐ │ ① 免跑计数器 (counter):硬件一直在数 │ │ ② 比较器 (comparator):到点能打中断 │ └──────┬───────────────────────┬────────────┘ 读①│ 配②│ ▼ ▼ clocksource clockevent (oneshot) │ │ ▼ ├────────────────┐ timekeeper(时间管家) ▼ ▼ 管墙钟/单调时间, ns tick hrtimer │ jiffies_64++ (纳秒, 红黑树) │ 跑到期的 timer │ ▼ scheduler tick ├─ usleep_range() ┌───────────────────┐ (HZ=300 → 3.33ms) ├─ clock_nanosleep │ ktime_get() │ ├─ poll/epoll/select 超时 │ ktime_get_real_* │ ← /proc/uptime └─ POSIX timers │ ktime_get_boottime│ ← printk [ 123.456789 ] │ ktime_get_raw │ ← clock_gettime() └───────────────────┘ ══ 粗粒度世界 ══ ══ 细粒度世界 ══ timer wheel (timer.c) hrtimer (hrtimer.c) mod_timer / msleep hrtimer_start / usleep_range 粒度 = 1 拍 = 3.33ms 精度 = 纳秒 └─── 汇率换算: msecs_to_jiffies() ───┘

逐块解读

  • 左边一条线:读计数器① → timekeeper 算出"现在是几点"(纳秒级)→ 提供给用户态的clock_gettime()/proc/uptime等。
  • 右边一条线:编程比较器②让它到点打中断 → 中断里既喂"心跳"(jiffies++)也喂"高精度闹钟"(hrtimer)。
  • 两个世界由同一颗硬件喂,所以不会各走各的漂移。

3.1 粗粒度世界:心跳计数器 jiffies

jiffies 是什么:内核的全局心跳计数器。定时器硬件每打一次铃(称为一次tick,“一拍”),它就 +1。它记录的是"开机以来总共跳了多少拍",单位是拍,不是秒。

它朴素得惊人,总共三段代码:

/* include/linux/jiffies.h —— 声明 */externu64 __cacheline_aligned_in_smp jiffies_64;externunsignedlongvolatile__cacheline_aligned_in_smp __jiffy_arch_data jiffies;/* kernel/time/timer.c —— 定义 */__visible u64 jiffies_64 __cacheline_aligned_in_smp=INITIAL_JIFFIES;/* kernel/time/timekeeping.c —— 每次时钟中断自增 */voiddo_timer(unsignedlongticks){jiffies_64+=ticks;/* 一次 tick,加 1 */calc_global_load();}

三个要点:

  • jiffies不是另一个独立变量:链接脚本把jiffies符号直接指到jiffies_64的地址上。32 位平台上unsigned long只有 32 位,jiffies恰好盖住jiffies_64的低 32 位;在 arm64 这样的 64 位平台上,两者就是同一个 64 位变量(get_jiffies_64()在 64 位平台直接返回jiffies)。
  • volatile是必须的——它被中断异步改写,编译器不许把对它的读操作缓存到寄存器里。
  • 它是个计数器,不是时间。想知道过了多久,永远是"现在读一次,减去之前读一次"。

换算:HZ 是"每秒拍数"(本文环境 HZ=300):

msecs_to_jiffies(1000)300/* 1 秒 */msecs_to_jiffies(5)2/* 注意!5/3.33=1.5 → 向上取整 */jiffies_to_msecs(1)4ms/* 1 拍 ≈ 3.33ms,换算同样向上取整 */

彩蛋:故意开机 5 分钟就"溢出"(include/linux/jiffies.h):

/* * Have the 32 bit jiffies value wrap 5 minutes after boot * so jiffies wrap bugs show up earlier. */#defineINITIAL_JIFFIES((unsignedlong)(unsignedint)(-300*HZ))

jiffies的类型是unsigned long,宽度跟着平台走:32 位平台上它就是 32 位数,数到顶会回绕(wrap)归零。归零后5 + 大数用普通的>比较会得出荒谬结论,所以内核规定必须用time_after()这类专门处理回绕的宏。内核开发者还故意把初始值设成"离回绕只差 5 分钟"——在 32 位平台上,任何没用time_after()的 bug 开机 5 分钟内就会暴露,相当于给全内核打了疫苗。

rk3588平台的实情:arm64 是 64 位平台,jiffiesjiffies_64同体、都是 64 位,数到宇宙热寂也回绕不了,低 32 位那场"5 分钟翻转"在这里翻不起浪,疫苗只是保留了传统。但time_after()依旧是规定写法——代码要能移植到 32 位平台,而且它的语义(比较相对拍数差)也更明确。

驱动里最经典的超时写法:

unsignedlongtimeout=jiffies+msecs_to_jiffies(100);/* 记下"100ms 后的拍号" */while(hardware_not_ready()){if(time_after(jiffies,timeout))/* 当前拍号超过截止拍号? */return-ETIMEDOUT;udelay(10);}

容易认错的"假 jiffies"

东西是不是 jiffies真身
/proc/uptime不是ktime(纳秒级,休眠也计时)
printk 时间戳[ 123.456789]不是local_clock(),纳秒级
times()返回值单位像用户态 ABI 固定 CLK_TCK=100,与内核 HZ 无关
loadavg 采样每 5 秒(LOAD_FREQ = 5×HZ+1 拍)采一次
驱动超时time_after(jiffies, ...)真玩家

最重要的一句:jiffies 是"相对心跳",不是墙钟时间。它不知道现在是几点,系统休眠时它也停跳。想算真实世界时间,找 timekeeper(ktime)。

3.2 细粒度世界:timekeeper 与 ktime

timekeeper 是内核的"时间管家",它读硬件计数器①,换算成纳秒,维护几种"钟":

时钟名字特点
墙钟CLOCK_REALTIME跟着现实世界走,NTP 校时会影响它
单调钟CLOCK_MONOTONIC只进不退,校时不影响,但休眠时不走
开机钟CLOCK_BOOTTIME单调 + 休眠也计时
TAICLOCK_TAI单调,不吃闰秒

用户态的clock_gettime()/proc/uptime、printk 时间戳,全部来自这套体系——和 HZ 完全无关

3.3 NO_HZ:闲下来就不打心跳

心跳有什么代价?每次 tick 中断都要耗 CPU、耗电。但 CPU 睡觉(idle)的时候,心跳毫无用处——没有进程要调度,没有账要记。

CONFIG_NO_HZ_IDLE(即CONFIG_NO_HZ=y)的作用就是:CPU idle 时把心跳完全停掉,硬件闹钟只在下一个定时器真正到期时才响。这叫 dynticks(动态 tick)。

所以 HZ 的开销公式是忙碌核数 × HZ,不是核数 × HZ。RK3588 八核全忙、HZ=300 时是 2400 次/秒;挂机时接近 0。


第 4 章 CONFIG_HZ 详解

导读:本章回答三个问题——HZ 是什么、它到底影响什么、该怎么选。

4.1 它是什么

CONFIG_HZ 是"心跳频率"的配置,定义在kernel/Kconfig.hz,只有四档:

选项一拍多长典型用户
CONFIG_HZ_10010 ms服务器、NVR(如本仓库rk3568_nvr.config
CONFIG_HZ_2504 ms折中(本仓库机器人配置)
CONFIG_HZ_3003.33 ms多媒体甜点值(本仓库默认rockchip_linux_defconfig
CONFIG_HZ_10001 ms低延迟(rk3308/px30 defconfig)

300 这个"怪数字"的来历:它能被 PAL(50fps)和 NTSC(60fps)帧率整除,是视频/多媒体时代的折中方案。

4.2 它的五大职责

前提纠正:很多人以为 HZ 管"系统时间"——其实墙钟、uptime、printk 时间戳全走 timekeeper,HZ 管的是心跳节奏以及一切按拍计费的东西

职责 1:定时器轮的刻度密度

内核里大量定时器(timer_list,见第 6 章)以拍为刻度。刻度越密,mod_timer()定的闹钟能越准。

职责 2:睡眠/超时的取整

内核的msleep()/schedule_timeout()按"拍"向上取整:

调用HZ=100HZ=300HZ=1000
msleep(1)实际睡10~20 ms3.33~6.67 ms1~2 ms

(设 n 拍超时,实际睡 n-1~n 拍——挂表时当前拍已经走掉的那一截不计入。)一个驱动循环里写msleep(1)轮询,HZ=300 下比 1000 慢 3 倍。这是 HZ 最常见的"隐性变慢"来源。顺带一提msleep的实现(kernel/time/timer.c):

voidmsleep(unsignedintmsecs){unsignedlongtimeout=msecs_to_jiffies(msecs)+1;...}

那个+ 1不是防 0 拍——msecs_to_jiffies()本身向上取整,最小就是 1 拍。它防的是"短睡":如上所述,设 n 拍超时实际可能只睡到 n-1 拍出头,比请求的毫秒数短;+1 之后实际睡眠必然覆盖请求时长。

职责 3:进程 CPU 统计的量化粒度

内核采用 tick 记账(kernel/sched/cputime.c):

voidaccount_process_tick(structtask_struct*p,intuser_tick)

每次 tick 看一眼 CPU 正跑在用户态还是内核态,给对应账本 +1 拍。所以top/ps里 CPU 时间的记账粒度是 1 拍:HZ=100 时一拍 10ms——一个跑了 5ms 的短进程可能记 0 拍或 1 拍,显示 0% 或 100%,全看记账时机(注意:展示给用户态时还要换算成固定 100Hz 的单位,见实验 3)。

职责 4:调度节拍(响应上限)

tick 里做抢占检查和负载均衡触发。一个不睡眠、霸占 CPU 的任务(“霸王龙任务”),最坏要等 1 拍才被调度器"巡视"到:HZ=100 → 最坏 10ms;HZ=1000 → 1ms。

注意:这只影响"正在跑的任务被周期性检查"这一条路;任务唤醒抢占(wakeup preemption)是事件驱动的,不走 tick。

职责 5:写死在协议/子系统里的 HZ 常数

/* include/net/tcp.h */#defineTCP_TIMEWAIT_LEN(60*HZ)/* TIME_WAIT 存活 60 秒 */#defineTCP_RTO_MAX((unsigned)(120*HZ))/* 重传超时上限 120 秒 */#defineTCP_RTO_MIN((unsigned)(HZ/5))/* 重传下限 200ms */

这些常数的意图是秒级时长,实现是拍数,改 HZ 会自动换算,真实时长不变,但量化粒度变了。

4.3 不受 HZ 影响的东西(重要!)

东西原因
墙钟、dateCLOCK_REALTIMEtimekeeper 纳秒级
/proc/uptime、printk 时间戳timekeeper
用户态select/poll/epoll/nanosleep超时走 hrtimer
usleep_range()hrtimer_start()走 hrtimer
udelay()忙等读硬件计数器,不经定时器
用户态sysconf(_SC_CLK_TCK)ABI 固定返回 100,别被骗

4.4 选型:性能、功耗还是响应?

先破除一个误解:

高 HZ 不会卡顿——卡顿的根源恰恰是低 HZ 的粗粒度。但高 HZ 也不提高峰值性能,吞吐反而微降。

"性能"要拆成两个维度看:

维度HZ=100HZ=1000
吞吐(单位时间干完的活)略高微降 ~0.5-1%
延迟/响应(最坏等多久)差(10ms 粒度)好 10 倍

代价的具体账目(8 核全忙时):

HZ=100HZ=300HZ=1000
tick 中断/秒80024008000
忙时 CPU 开销~0.1%~0.3%~1%
msleep(1)实际睡10~20 ms3.33~6.67 ms1~2 ms

注意是忙时开销——开了 NO_HZ_IDLE 后,挂机功耗各档没有区别。

高 HZ 的代价是"匀速多缴 1% 税",平稳且可预测,不会产生任何突发卡顿。卡顿的来源排查顺序是:调度延迟、内存抖动、IO 阻塞——其中"调度巡视太慢"只有低 HZ 会贡献。

决策经验:问自己"有没有msleep(1)级别的内核轮询在热路径上、有没有 <4ms 的硬性控制环"。有 → 1000;没有 → 300 白赚省 0.7% CPU;纯录像/吞吐设备 → 100。


第 5 章 utime 与 stime:进程的两本账

导读:上一章提到"记账",本章把这本账讲清楚。

每个进程的task_struct(内核的"进程档案")里有两个记账字段(include/linux/sched.h):

u64 utime;/* 用户态时间:跑"你自己的代码"花的 CPU */u64 stime;/* 内核态时间:内核"替你干活"花的 CPU */

区分标准不是"哪个函数",而是"CPU 当时跑在哪一态"

你在做记到哪
算哈希、memcpy、解码utime
write()写 eMMC,CPU 在 ext4/blk/mmc 路径里跑stime
大量printf到串口,CPU 在 tty 驱动里折腾stime(串口慢速输出是 stime 大户)
mmap后缺页,内核分配物理页stime

在板上就能看:

time./myapp# user 即 utime,sys 即 stimecat/proc/<pid>/stat# 第 14、15 字段

如果某程序 sys 远大于 user,说明它把时间都花在"求内核办事"上了(典型:高频小 IO、疯狂 syscall)。


第 6 章 内核定时器家族:公交与专车

导读:jiffies 世界和 hrtimer 世界的运行机制对比,是理解内核时间子系统的最后一层窗户纸。

6.1 timer wheel:公交车模式

内核里数量最大的定时器用timer_list(俗称 timer wheel,定时器轮)。工作方式像公交车:

  • 定闹钟只是"登记到站牌"(挂进哈希表),不编程任何硬件
  • 每次心跳(tick)到站,停车检查:这一站该到期的,全部处理;
  • 处理在软中断上下文执行(kernel/time/timer.c 的run_timer_softirq)。
tick 硬中断 → raise_softirq(TIMER_SOFTIRQ) → 软中断阶段扫轮盘 → 回调

特点:便宜(几乎零成本登记)、量大(O(1))、但精度只有 1 拍(3.33ms),且有软中断调度延迟。

6.2 hrtimer:专车模式

hrtimer(高精度定时器)像专车:

  • 登记时挂进红黑树(按到期时间排序);
  • 每次都把硬件闹钟(比较器)重编程到最早的到期时刻(纳秒精度,kernel/time/hrtimer.c 的hrtimer_reprogramtick_program_event);
  • 闹钟一响,回调直接在硬中断里执行hrtimer_interrupt__run_hrtimer);
  • 只有回调处理不过来(过载)时,才把剩余的丢给HRTIMER_SOFTIRQ软中断兜底。

所以有一个常见误区要纠正:

❌ “hrtimer 和普通定时器一样,依赖软中断实现”
✅ timer wheel 的回调 100% 在软中断;hrtimer 的回调正常路径在硬中断,软中断只是过载兜底(以及显式选择HRTIMER_MODE_SOFT的定时器——第 7 章那 4 个 soft 柜台就是给它们留的,它们本来就跑软中断)。

hrtimer 单闹钟轮转:CVAL 是绝对刻度,等待量随计数器缩小
循环 ③→①:回调跑完 → 新最左 B 的绝对刻度 6.0ms 写入 CVAL → 实际等待 TVAL = 6.0 − now 5.0 = 1.0ms,且随计数器前进自动缩小,无需任何人改写 → 1ms 后铃响重复 ②。整棵树无论挂多少颗定时器,始终只占这一颗硬件闹钟;tick_sched_timer 靠返回 RESTART 每次重挂自己,心跳才能每 3.33ms 响一次。

6.3 依赖关系是反的!

更颠覆的一点:高精度模式下,tick 本身就是一个 hrtimer

/* kernel/time/tick-sched.c */hrtimer_init(&ts->sched_timer,CLOCK_MONOTONIC,HRTIMER_MODE_ABS_HARD);ts->sched_timer.function=tick_sched_timer;

那个每 3.33ms 响一次、给 jiffies++ 的心跳,本体是架在 hrtimer 之上的。所以层次关系是:

硬件 arch_timer 比较器(真正的"硬"定时器,全系统唯一) ▲ │ 直接重编程(纳秒精度) ┌────┴─────┐ │ hrtimer │ ← 地基 └────┬─────┘ │ tick_sched_timer 是其中一个 hrtimer ▼ tick 中断 ──→ jiffies++ ──→ TIMER_SOFTIRQ ──→ timer wheel

6.4 两兄弟对比总表

timer wheel(公交)hrtimer(专车)
组织结构分级哈希轮盘,O(1)红黑树,O(log n)
谁编程硬件不编程,搭 tick 便车每次入队重编程比较器
精度1 拍(3.33ms)+ 软中断延迟硬件计数器精度(纳秒)
回调上下文softirqhardirq(兜底才 softirq)
典型 APImod_timer()msleep()schedule_timeout()hrtimer_start()usleep_range()

6.5 API 选择口诀(背这四条)

  1. 存时间戳永远用 ktime 族——jiffies 存时间戳有三宗罪:回绕(32 位平台)、休眠停摆、粒度 3.33ms。
  2. 睡眠:不在乎 ±1 拍 →msleep();要准 →usleep_range(最小, 最大)(走 hrtimer)。
  3. 驱动定时器:ms 级、大量、频繁重挂 →timer_list;要精确 →hrtimer
  4. 忙等 <1msudelay()(arch/arm64/lib/delay.c,读硬件计数器空转,不经过任何定时器子系统);能睡则睡,忙等是最后手段。

第 7 章 hrtimer 都有谁在用

导读:把 hrtimer 理解成基础设施,这一章看看它的"客户名单"。

柜台:每个 CPU 一套 8 个 clock base(kernel/time/hrtimer.c),4 个硬中断版 + 4 个软中断版,分别对应 MONOTONIC / REALTIME / BOOTTIME / TAI 四种时钟。入队时按你指定的 clockid 路由到对应柜台。

客户(本仓库实际扫描结果,grep -r "hrtimer_init("):

类别用户干什么
内核地基tick_sched_timer心跳本体(第 6 章讲过)
内核地基CFS/RT/DL 调度器精确抢占、带宽控制周期
内核地基软锁狗(watchdog.c)检测 CPU 卡死
内核地基perf 采样PMU 轮转与采样事件的节拍
系统调用clock_nanosleeptimer_create、poll/epoll 超时、futex用户态的直接消费
协议栈TCP pacing、fq/netem 流量整形µs 级发包节拍(qdisc watchdog 同样架在 hrtimer 上)
协议栈CAN(bcm/isotp/j1939)车载总线时序
协议栈TSN(taprio/act_gate)工业以太网门控,µs 级准时
驱动8250 串口、softdog、Mali GPU(十几处)、Rockchip RGA3/RVE、USB本板子真身在用

规律:凡是"晚 3.33ms 会出事"的场景都在 hrtimer 上——调度公平性、发包节拍(TCP pacing、TSN 门控)、GPU 负载统计。而"晚 3.33ms 无所谓"的(超时保护、轮询延迟)都留在 timer wheel 上。TCP 自己就是最好的分界样本:pacing 这种 µs 级敏感的活用 hrtimer(tcp_sock里的pacing_timer);而重传/delayed-ACK/TIME_WAIT 这些百毫秒量级的定时器在 5.10 里仍是 jiffies 级的timer_listinet_connection_sock里的icsk_retransmit_timer/icsk_delack_timer)——第 4 章职责 5 那几个*HZ常数正是给它们用的。


第 8 章 动手实验(RK3588 板上可做)

导读:busybox 环境即可完成,把理论对号入座。

实验 1:亲眼看到心跳

cat/proc/interrupts|greparch_timer# 等 1 秒,再来一次# 忙碌的核每秒大约 +300(HZ=300);idle 的核几乎不动(NO_HZ 生效)# 跑个死循环让 CPU 忙起来对比:whiletrue;do:;done&

实验 2:看定时器世界的名册

cat/proc/timer_list|head-60# 能看到每个 CPU 的 hrtimer base、最早到期时间,# 以及 function: tick_sched_timer —— 印证"tick 是个 hrtimer"

实验 3:感受 CPU 记账的量化

cat/proc/stat# cpu 开头的行:user/system/idle 等,单位是 1/100 秒(USER_HZ),不是内核的拍!# 让一个核忙满 1 秒再 cat 一次:user 列增量 ≈ 100,与 HZ 无关# /proc/stat 与 times() 同属用户态 ABI,一律按 100Hz 换算——想看 HZ 本尊,回到实验 1 数 arch_timer 中断

第 9 章 常见误区 FAQ

Q1:jiffies 是当前时间吗?
不是。它是"开机以来的心跳拍数",相对量、粗粒度、休眠会停。存时间戳用 ktime 族。

Q2:改了 CONFIG_HZ,用户态的times()单位会变吗?
不会。用户态 ABI 固定 CLK_TCK=100,和内核 HZ 无关。

Q3:HZ 越大系统越流畅吗?
响应粒度越细(最坏 10ms → 1ms),但峰值吞吐微降 ~1%。流畅卡顿问题通常另有元凶(内存、IO、调度),不是 HZ。

Q4:HZ=1000 会更耗电吗?
挂机不会(NO_HZ_IDLE 停掉了心跳);只有忙时多 ~1% CPU 开销带来的功耗。

Q5:hrtimer 和 timer wheel 谁更"高级"?要全换 hrtimer 吗?
各司其职。timer wheel 便宜、O(1),适合海量 ms 级超时;hrtimer 精确但每颗都要编程硬件。全换 hrtimer 反而更慢。

Q6:hrtimer 的回调能睡吗?
不能,它在硬中断上下文(兜底的软中断也不能睡)。要睡请用工作队列(workqueue)。

Q7:msleep(1)到底睡多久?会不会"等于没睡"?
不会。msecs_to_jiffies()向上取整,最小 1 拍,绝不会取整成 0;msleep()+1再保证不短睡(第 4 章职责 2)。HZ=300 下msleep(1)实际睡 3.33~6.67ms——要 1ms 级精度请改用走 hrtimer 的usleep_range()。真正不保证睡眠时长的是自己写schedule_timeout(0)

Q8:printk 的时间戳能用来测 jiffies 吗?
不能,它来自local_clock()(纳秒级单调钟),比 jiffies 精确得多,两者是不同体系。

Q9:TIME_WAIT 是 60 秒,改 HZ 会变吗?
不会,TCP_TIMEWAIT_LEN = 60*HZ自动换算。变的只是量化精度。

Q10:怎么确认我的内核 HZ 是多少?
板上:用实验 1——让一个核忙起来,看/proc/interruptsarch_timer每秒增量(≈HZ);或zcat /proc/config.gz | grep "CONFIG_HZ="(需编译时开了 IKCONFIG_PROC);或直接查.config里的CONFIG_HZ_XXX。注意/proc/stat看不出 HZ——它的单位是固定的 USER_HZ=100。


第 10 章 术语表

术语英文一句话解释
tick / 拍tick定时器硬件的一次心跳中断
HZHZ每秒心跳次数(编译期常量)
jiffiesjiffies全局心跳计数器,一拍 +1
心跳timer tickCPU 忙时的周期性定时中断
时钟源clocksource一直走的硬件计数器,回答"现在几点"
时钟事件设备clockevent可编程硬件闹钟,回答"到时候叫我"
时间管家timekeeper读 clocksource,维护墙钟/单调钟等
高精度定时器hrtimer纳秒级定时器,回调默认跑硬中断
定时器轮timer wheeljiffies 级定时器,回调跑软中断
动态 tickdynticks / NO_HZCPU 空闲时停掉心跳
回绕wraparound32 位计数器数到顶归零
用户态/内核态user/kernel modeCPU 的两种特权级
utime / stimeuser/system time进程的用户态/内核态 CPU 账本
硬中断/软中断hardirq/softirq中断的快处理/延后处理两级
上下文context当前代码的"身份"(进程/硬中断/软中断)
睡眠sleep主动让出 CPU 等条件;中断上下文禁止

附录:源码地图

本文档引用的关键文件(基于rk3588开源仓库 5.10 源码),建议按序阅读:

文件内容
kernel/Kconfig.hzHZ 四档选项定义
include/linux/jiffies.hjiffies 声明、INITIAL_JIFFIES彩蛋、time_after宏、换算函数
kernel/time/timer.cjiffies_64 定义、timer wheel 全部实现、msleepschedule_timeout
kernel/time/timekeeping.ctimekeeper、do_timer()(jiffies 自增处)
kernel/time/hrtimer.chrtimer 全部实现、8 个 clock base、hrtimer_reprogram
kernel/time/tick-sched.ctick_sched_timer——"tick 是个 hrtimer"的证据
kernel/sched/cputime.caccount_process_tick——tick 记账
arch/arm64/lib/delay.cudelay忙等的真身
arch/arm64/configs/rockchip_linux_defconfig本板当前配置(HZ_300、NO_HZ、HIGH_RES_TIMERS)

本文档由 CONFIG_HZ / jiffies / 内核时间体系的技术讨论整理而成,示例与代码行号对应 Linux 5.10.198 源码。

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

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

立即咨询