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=true -
CONTEXT_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)精确记账。
更多推荐




所有评论(0)