Linux 内核的 Context Tracking上下文跟踪
Linux 内核的Context Tracking(上下文跟踪,也叫 CT / EQoS — Extended Quiescent State) 是内核用来精确感知 CPU 当前运行在哪个"执行域"(用户态 / 内核态 / 空闲 / Guest VM),并通知 RCU 和 虚拟时间记账子系统做相应处理的基础设施。它是NO_HZ_FULL(全动态无滴答 tickless)能正常工作的前提。
下面从数据结构、核心原理、关键流程、与 RCU 的关系四个层面详细说明。
一、Context Tracking 解决什么问题
传统 RCU 依赖周期性的 timer tick(调度时钟中断)来判断某 CPU 是否经过 quiescent state(经历用户态或 idle)。但在NO_HZ_FULL模式下,tick 被停止了——RCU 无法靠 tick 感知 CPU 进入/离开用户空间。
Context Tracking 的作用就是:
在体系结构入口/出口(syscall/异常/中断返回用户态、idle 进入/退出)显式通知内核 "本 CPU 现在进入/退出用户态(或 idle/GUEST)",让 RCU 可以把用户态当作 Extended Quiescent State(EQS),从而安全停止对该 CPU 的 grace-period 追踪,也允许停掉周期性 tick。
此外它还驱动:
vtime(Virtual Time Accounting):区分进程在用户态 / 内核态 / guest 的耗时
TASKS_RCU / TRACE_RCU:标记任务不在内核临界区
二、核心数据结构
每 CPU 一个struct context_tracking(新内核重命名为struct ct_rcu,原理相同):
// include/linux/context_tracking.h + context_tracking_state.h struct context_tracking { bool active; // 该 CPU 是否启用 context tracking (在 NO_HZ_FULL mask 中) atomic_t state; // 当前上下文状态 + RCU watching 位 /* 旧版用 enum { IN_KERNEL=0, IN_USER } state; */ }; DEFINE_PER_CPU(struct context_tracking, context_tracking);state在新内核(5.x+/6.x)是一个atomic_t,其低位编码:
RCU_WATCHING bit:RCU 是否在观察本 CPU(1=watching, 0=in EQS)
上下文类型:KERNEL / USER / IDLE / GUEST
全局静态键context_tracking_enabled(Static Key)确保未开启CONFIG_CONTEXT_TRACKING_USER或 CPU 不在 dyntick mask 时零开销跳过。
三、关键状态转换与调用点
状态机
┌──────────────┐ ct_user_enter() ┌──────────────┐ │ IN_KERNEL │ ──────────────────▶ │ IN_USER │ │ (RCU watching)│ ◀────────────────── │(RCU EQS/off) │ └──────────────┘ ct_user_exit() └──────────────┘ ▲ │ ▲ │ │ │ ct_idle_enter/exit() │ │ │ ▼ │ ▼ IDLE (也是一种 EQS)IN_KERNEL:CPU 执行内核代码 → RCU 正常监视(rcu_irq_enter 类语义)
IN_USER / IDLE / GUEST:CPU 不在内核 → RCU 认为这是Extended Quiescent State,可停止 grace period 对该 CPU 的等待
体系结构入口处的埋点
场景 | 汇编/早期 C 入口调用 |
|---|---|
内核→用户态返回(syscall/exception 返回) |
|
用户态→内核(syscall/异常进入) |
|
idle loop 进入 |
|
idle loop 退出 |
|
KVM guest 进入/退出 |
|
这些函数通常在noinstr标记的入口代码中调用(IRQs off),在 RCU 开始/停止 watching 的前后。
四、核心函数工作流程
以用户态进出为例(新内核__ct_user_enter/__ct_user_exit,旧版context_tracking_user_enter/exit):
1.ct_user_enter()— 内核 → 用户态
ct_user_enter(CONTEXT_USER): 若 !context_tracking_enabled() → return 若 in_interrupt() → return // 嵌套 IRQ 中不处理 若 state 已是 IN_USER → return // 幂等 ── 以下 this_cpu active == true ── vtime_user_enter(current) // 停止内核时间记账,开始用户时间 rcu_user_enter() // ★ 通知 RCU 进入 Extended Quiescent State → ct_kernel_exit_state() // 递增 dynticks 计数器,清除 RCU_WATCHING 位 this_cpu_write(state, IN_USER)关键点:
rcu_user_enter()使 RCU 对该 CPU "不再 watching",该 CPU 被视为已通过 quiescent state,后续不需要 tick。
2.ct_user_exit()— 用户态 → 内核(syscall/异常)
ct_user_exit(CONTEXT_USER): 若 !enabled → return 若 in_interrupt() → return 若 state != IN_USER → return (已在内核,幂等) ── this_cpu active == true ── rcu_user_exit() // ★ 通知 RCU 退出 EQS → ct_kernel_enter_state() // 递增 dynticks,设置 RCU_WATCHING 位 vtime_user_exit(current) // 恢复内核时间记账 this_cpu_write(state, IN_KERNEL)必须在任何 RCU read-side critical section 之前调用,否则会读到已停用的 RCU。
3. 异常嵌套处理 —exception_enter()/exception_exit()
// include/linux/context_tracking.h static inline enum ctx_state exception_enter(void) { prev = __ct_state(); // 读取当前状态 if (prev != CT_STATE_KERNEL) // 若原在 USER,先退出 USER ct_user_exit(prev); return prev; } static inline void exception_exit(enum ctx_state prev) { if (prev != CT_STATE_KERNEL) ct_user_enter(prev); // 异常返回时恢复 USER 状态 }保证异常发生在用户态时,短暂把 context 切回 KERNEL(让 RCU watching),返回时再切回去。
4. 任务切换时的处理
void context_tracking_task_switch(prev, next) { if (ct->active) { clear_tsk_thread_flag(prev, TIF_NOHZ); set_tsk_thread_flag(next, TIF_NOHZ); } }TIF_NOHZ使新任务走 syscall slow path,从而在返回用户态时触发ct_user_enter()检查。init 进程启动时设TIF_NOHZ,通过 fork 继承给所有进程。
五、与 RCU 的协作关系
Context Tracking 动作 | RCU 对应行为 |
|---|---|
| dynticks 计数器++,RCU_WATCHING=0 → CPU 进入 EQS,RCU 可报告该 CPU 已过 quiescent state |
| dynticks 计数器++,RCU_WATCHING=1 → RCU 恢复监视,grace period 继续等待该 CPU |
| 处理中断中嵌套,dynticks_nmi_nesting 独立维护,不影响 user eqs |
rcu_is_watching_curr_cpu()直接读context_tracking.state的 RCU_WATCHING 位判断。
六、配置与激活条件
编译:
CONFIG_CONTEXT_TRACKING_USER=y(隐含CONFIG_NO_HZ_FULL=y)启动参数:
nohz_full=cpulist— 指定哪些 CPU 激活context_tracking.active=trueCONTEXT_TRACKING_FORCE:强制所有 CPU 开启(调试用)
非nohz_fullCPU 上active=false,只维护 state 用于一致性,不调rcu_user_enter/exit,开销接近零。
七、一句话总结
Context Tracking 是内核在用户态↔内核态 / idle / guest 边界插入的探针,通过维护每 CPU 的上下文状态并告知 RCU 进入/退出 Extended Quiescent State,使得 NO_HZ_FULL CPU 在用户态运行时可以停掉周期 tick 且 RCU grace period 仍能正确结束,同时驱动进程虚拟时间(vtime)精确记账。
