Linux 内核的 Context Tracking上下文跟踪

📅 2026/7/9 0:41:11 👁️ 阅读次数 📝 编程学习
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 返回)

ct_user_enter()/user_enter_irqoff()

用户态→内核(syscall/异常进入)

ct_user_exit()/user_exit_irqoff()

idle loop 进入

ct_idle_enter()rcu_eqs_enter()

idle loop 退出

ct_idle_exit()rcu_eqs_exit()

KVM guest 进入/退出

ct_guest_enter()/ct_guest_exit()

这些函数通常在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 对应行为

rcu_user_enter()ct_kernel_exit_state()

dynticks 计数器++,RCU_WATCHING=0 → CPU 进入 EQS,RCU 可报告该 CPU 已过 quiescent state

rcu_user_exit()ct_kernel_enter_state()

dynticks 计数器++,RCU_WATCHING=1 → RCU 恢复监视,grace period 继续等待该 CPU

rcu_irq_enter()/exit()

处理中断中嵌套,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)精确记账。