Kimi k3 并不好
记录于 2026-08-06
由于我的 codex 用完了,我一直在订阅 kimi ,感觉 k3 明显没有大家说的那么好。 分析问题,容易走到死胡同,代码分析也是非常一般,今天一怒之下,分别对比 k3 / ds-v4-flash / codex-5.6-sol
如下是提示词,一个标点都没修改,后面是三者的回答,直接过来的。
我认为 codex 回答最好,简单清晰,大改几秒钟就可以看懂了, ds-v4-flash 稍微差点,k3 这个回答是其他都看懂了,都需要反应半天。
5. Tasks RCU 的 quiescent state 计算
最重要的是 voluntary context switch
也就是 task 主动走到 scheduler:
Task T kernel code | | v schedule() | +-------- Tasks-RCU QS另外,进入 userspace 也是一个很强的证据:
kernel | | return to userspace v userspace因为如果 task 已经跑回 userspace:
它显然已经不可能还停留在刚才那个 kernel trampoline 里面。
Linux 因此将 voluntary scheduling、userspace execution 等作为 Tasks RCU 判断 task 已离开旧执行区间的重要依据。 为什么同样是 context switch ,主动 schedule 和 preempt ,内核可以区分出来?
codex
虽然最后都执行到 context_switch(),但进入调度器的入口不同,内核会把“为什么进入调 度器”作为参数一直传到 __schedule(),因此可以区分。
核心代码是:
static void __schedule(int sched_mode)
其中 sched_mode 表示调度原因:
#define SM_NONE 0 /* 主动调用 schedule() */
#define SM_PREEMPT 1 /* 被抢占 */
调用链分别是:
主动阻塞/主动调度
schedule()
-> __schedule_loop(SM_NONE)
-> __schedule(SM_NONE)
内核抢占
preempt_schedule()
-> preempt_schedule_common()
-> __schedule(SM_PREEMPT)
对应源码见 kernel/sched/core.c:7264 和 kernel/sched/core.c:7368。
进入 __schedule() 后,RCU 在真正切换之前就收到这个信息:
bool preempt = sched_mode > SM_NONE;
local_irq_disable();
rcu_note_context_switch(preempt);
见 kernel/sched/core.c:7017。
Tasks RCU 根据 preempt 决定这是不是 QS:
#define rcu_tasks_classic_qs(t, preempt) \
do { \
if (!(preempt) && \
READ_ONCE((t)->rcu_tasks_holdout)) \
WRITE_ONCE((t)->rcu_tasks_holdout, false); \
} while (0)
见 include/linux/rcupdate.h:174。
因此:
schedule() -> SM_NONE -> preempt=false -> Tasks RCU QS
preempt_schedule() -> SM_PREEMPT -> preempt=true -> 不是 Tasks RCU QS
为什么抢占不能算 QS?
因为被抢占的 task 恢复后,会从原来的指令位置继续:
trampoline 中间 | | 被抢占 v schedule other task | | 恢复 v 仍从 trampoline 中间继续
所以一次抢占不能证明它已经离开旧 trampoline。主动走到允许调度的边界,则按 Tasks RCU 的契约结束了之前的隐式 read-side critical section。
另外,调度器还会结合 prev->__state 维护两类统计:
switch_count = &prev->nivcsw; /* 默认:非主动/抢占式 */
if (!preempt && prev_state) {
...
switch_count = &prev->nvcsw; /* task 主动睡眠 */
}
见 kernel/sched/core.c:7074。
需要注意一个细节:nvcsw 并不完全等同于“从 schedule() 入口进入”。
- schedule() + 非 TASK_RUNNING:真正主动阻塞,增加 nvcsw
- 抢占:增加 nivcsw
-
schedule()/cond_resched() 时 task 仍是 TASK_RUNNING:统计上可能增加 nivcsw,但 Tasks RCU 仍可根据 sched_mode 把这个显式调度点看作 quasi-voluntary QS
- cond_resched_tasks_rcu_qs() 甚至会显式报告 Tasks RCU QS,即使最后没有发生真正的 task switch
所以最准确的说法是:
Tasks RCU 判断的不是低层 context_switch() 指令本身,而是 task 到达了什么语义边 界。主动调度入口、阻塞状态、进入 userspace/idle 都能证明旧执行区间已经结束;单 纯的抢占不能。
ds-v4-flash
内核如何区分「主动 schedule」和「preempt」
先给结论:不是靠”事后观察”上下文切换本身来分类的,而是调用者显式告诉调度器的 —— 两者走的是完全不同的代码路径,这个事实被编码成一个 sched_mode 参数一路传下去,最后传 给 Tasks RCU 的是一个 preempt 布尔值。
- 两条不同的入口路径
__schedule() 的唯一入口参数就是模式(kernel/sched/core.c:6564-6572):
#define SM_IDLE (-1)
#define SM_NONE 0
#define SM_PREEMPT 1
#define SM_RTLOCK_WAIT 2
- 自愿切换:任务在自己的上下文里(进程上下文)调用 schedule() → __schedule_loop(SM_NONE) → __schedule(SM_NONE)(core.c:7273-7287)。调用点就是 mutex/semaphore/waitqueue 等阻塞代码,是任务自己决定让出 CPU。
- 抢占:调用发生在中断/异常返回路径上,任务并不是自己停下来的,是调度器(时钟中断 sched_tick、wakeup IPI)强制它让出:
- 中断返回内核时:raw_irqentry_exit_cond_resched() → preempt_schedule_irq()(kernel/entry/common.c:142)→ __schedule(SM_PREEMPT)(core.c:7501-7513)
- 内核抢占点(preempt_count 归零):preempt_schedule() → preempt_schedule_common() → __schedule(SM_PREEMPT)(core.c:7352-7370)
在 __schedule() 开头:
static void __sched notrace __schedule(int sched_mode)
{
bool preempt = sched_mode > SM_NONE; // core.c:7024
...
rcu_note_context_switch(preempt); // core.c:7044
- 第二个证据:prev->__state(在 __schedule() 内部再次确认)
- 主动阻塞的任务在调 schedule() 之前会先把自己置为非 RUNNING 状态(TASK_INTERRUPTIBLE/TASK_UNINTERRUPTIBLE)。所以 !preempt && prev_state 为真时,switch_count = &prev->nvcsw(自愿计数 +1),并且任务被 deactivate_task() 从 runqueue 摘掉(on_rq = 0)(core.c:7074-7100)。
- 被抢占的任务仍是 TASK_RUNNING,留在 runqueue 上,switch_count = &prev->nivcsw(非自愿计数 +1),++*switch_count 在 core.c:7163。
这就是 /proc/
- preempt 标志一路传到 Tasks RCU
__schedule(sched_mode)
└─ preempt = sched_mode > SM_NONE
└─ rcu_note_context_switch(preempt) core.c:7044
└─ rcu_tasks_qs(current, preempt) tree_plugin.h:371 / :1006
└─ rcu_tasks_classic_qs(t, preempt) rcupdate.h:174-178
if (!(preempt) && READ_ONCE(t->rcu_tasks_holdout))
WRITE_ONCE(t->rcu_tasks_holdout, false);
注意宏里的 !(preempt) 条件:只有自愿切换才清除 holdout,抢占什么都不做。这就是 Tasks RCU 对两者的不对称待遇,关键就在这个参数上。
- 为什么 Tasks RCU 只认自愿切换?
kernel/sched/core.c:7289 的注释点明了目标:
│ synchronize_rcu_tasks() makes sure that no task is stuck in preempted state … by making sure that all tasks have either left the run queue or have gone into │ user space.
- 自愿切换是强证据:任务自己选择了停止执行的点,从 runqueue 上被摘掉(on_rq == 0,见 rcu_tasks_is_holdout(),tasks.h:973-1004),此刻它肯定不在执行任何内核 trampoline;等它被唤醒恢复时,是从它自己代码里选定的位置(schedule() 之后的代码)继续,而不是从任意指令恢复。
- 抢占不是证据:被抢占的任务仍是 TASK_RUNNING、仍在 runqueue 上,调度器随时可能在任何一条指令处把它调度回来 —— 完全可能恰好是旧 trampoline 中间。抢占没有提供”任务已 经离开旧代码”的任何信息。
所以 grace period 扫描 check_holdout_task() 的判定(tasks.h:1092-1098)三个条件全部只跟”自愿”有关:
if (!READ_ONCE(t->rcu_tasks_holdout) || // 被自愿切换直接清除
t->rcu_tasks_nvcsw != READ_ONCE(t->nvcsw) || // nvcsw 变了 = 发生过自愿切换
!rcu_tasks_is_holdout(t) || ...) // !on_rq = 主动睡下去了
(nvcsw 快照对比:GP 开始时 rcu_tasks_pertask() 记下 t->rcu_tasks_nvcsw = READ_ONCE(t->nvcsw)(tasks.h:1013),而 nvcsw 只在自愿切换时递增。)
- userspace 执行为什么也是 QS(你说的第二个证据)
三个机制,全部是”明确知道任务在 userspace/即将回 userspace”:
-
返回用户态路径:exit_to_user_mode_loop()(kernel/entry/common.c:44-47)里,TIF_NEED_RESCHED 时调用的是普通 schedule(),不是 preempt_schedule_irq()。即使任务是因 为时间片用完被”抢”下 CPU,它此刻也处于内核→用户态边界:内核里没有悬而未决的执行状态,不会被调度器从 trampoline 中间恢复。所以这条路径天然走 SM_NONE = 自愿。
-
调度时钟 tick 直接报告:rcu_sched_clock_irq() 里,若 tick 打断的是 userspace 任务(user == true),直接 rcu_note_voluntary_context_switch(current) = rcu_tasks_qs(current, false)(kernel/rcu/tree.c:2719)。注释写得很直白:”Idle and userspace execution already are quiescent states.”
-
softirq 路径:rcu_softirq_qs() 也调 rcu_tasks_qs(current, false)(tree.c:270)。
直觉上:任务跑在 userspace,说明它肯定不在内核 trampoline 里;即使它马上再进内核,也是通过已经更新过的新 trampoline 指针进入的 —— 这正是 trampoline 替换 (static_call / BPF trampoline / ftrace)使用 synchronize_rcu_tasks() 的全部逻辑:先原子切换指针,再等所有人离开旧代码。
一个补充细节:SM_RTLOCK_WAIT
__schedule() 里 preempt 被算过两次:
bool preempt = sched_mode > SM_NONE; // 给 RCU / schedule_debug 用
...
preempt = sched_mode == SM_PREEMPT; // 给任务状态逻辑用(core.c:7077)
在 PREEMPT_RT 上,任务阻塞在”会睡眠的 spin/rwlock”上走 schedule_rtlock() → SM_RTLOCK_WAIT:对 RCU 来说算抢占(> SM_NONE),对”任务是否主动睡眠”的逻辑不算。这再次说 明:”这是哪种切换”纯粹是调用点自报家门的结果,而不是调度器从切换本身推导出来的。
k3
源码已确认,机制和你笔记里写的一致。帮你把截断的句子补完并收尾:
Linux 因此将 voluntary scheduling、userspace execution 等作为 Tasks RCU 判断 task 已经过 quiescent state 的证据,具体 实现是每个 task 上的 rcu_tasks_holdout 标志:
- grace period 开始时,Tasks RCU 扫描 tasklist,对每个可能还在读侧的 task 置 rcu_tasks_holdout = true 并挂入 holdout list(kernel/rcu/tasks.h:1009 rcu_tasks_pertask())。
- task 每次发生 context switch,rcu_note_context_switch() 会调 rcu_tasks_qs(t, preempt)(kernel/rcu/tree_plugin.h:371):只有 preempt == false(即主动调度,对应 rcu_note_voluntary_context_switch(),include/linux/rcupdate.h:195)才清掉 holdout 标志 (include/linux/rcupdate.h:174-178)。被动抢占不算 QS —— 因为被抢占的 task 仍可能停在 trampoline 中间。
- GP 检查端 check_holdout_task()(kernel/rcu/tasks.h:1087)轮询 holdout list,满足任一条件即认为该 task 已过 QS:
- rcu_tasks_holdout 已被 task 自己清掉(做过 voluntary switch);
- t->nvcsw 相比 GP 开始时记录的 rcu_tasks_nvcsw 变了(发生了主动切换);
- rcu_tasks_is_holdout() 为 false:task 已不在 runqueue 上(!t->on_rq,即已睡进内核某个安全的阻塞点)、是 idle task、或 CPU offline 的 idle;
- t->rcu_tasks_idle_cpu >= 0(NO_HZ_FULL 下由 context tracking 设置)—— 这就是”已经回到 userspace”的证据。
holdout list 清空后,grace period 结束,销毁 trampoline 之类的操作(call_rcu_tasks()/synchronize_rcu_tasks() 保护的) 才可以执行。
一句话总结:Tasks RCU 的读侧临界区太宽泛(整个 task 的内核执行都算),所以它只能靠”task 主动让出 CPU 或回到用户态”这类 强证据来判定 QS,而不是像普通 RCU 那样依赖禁抢占区间的边界。
本站所有文章转发 CSDN 将按侵权追究法律责任,其它情况随意。