Linux 内核的 Context Tracking上下文跟踪

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 StateEQS从而安全停止对该 CPU 的 grace-period 追踪也允许停掉周期性 tick。此外它还驱动vtimeVirtual 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_KERNEL0, IN_USER } state; */ }; DEFINE_PER_CPU(struct context_tracking, context_tracking);state在新内核5.x/6.x是一个atomic_t其低位编码RCU_WATCHING bitRCU 是否在观察本 CPU1watching, 0in EQS上下文类型KERNEL / USER / IDLE / GUEST全局静态键context_tracking_enabledStatic 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_KERNELCPU 执行内核代码 → RCU 正常监视rcu_irq_enter 类语义IN_USER / IDLE / GUESTCPU 不在内核 → 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/exit1.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_WATCHING0 → CPU 进入 EQSRCU 可报告该 CPU 已过 quiescent statercu_user_exit()→ct_kernel_enter_state()dynticks 计数器RCU_WATCHING1 → RCU 恢复监视grace period 继续等待该 CPUrcu_irq_enter()/exit()处理中断中嵌套dynticks_nmi_nesting 独立维护不影响 user eqsrcu_is_watching_curr_cpu()直接读context_tracking.state的 RCU_WATCHING 位判断。六、配置与激活条件编译CONFIG_CONTEXT_TRACKING_USERy隐含CONFIG_NO_HZ_FULLy启动参数nohz_fullcpulist— 指定哪些 CPU 激活context_tracking.activetrueCONTEXT_TRACKING_FORCE强制所有 CPU 开启调试用非nohz_fullCPU 上activefalse只维护 state 用于一致性不调rcu_user_enter/exit开销接近零。七、一句话总结Context Tracking 是内核在用户态↔内核态 / idle / guest 边界插入的探针通过维护每 CPU 的上下文状态并告知 RCU 进入/退出 Extended Quiescent State使得 NO_HZ_FULL CPU 在用户态运行时可以停掉周期 tick 且 RCU grace period 仍能正确结束同时驱动进程虚拟时间vtime精确记账。