Skip to the content.

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() 入口进入”。

所以最准确的说法是:

Tasks RCU 判断的不是低层 context_switch() 指令本身,而是 task 到达了什么语义边 界。主动调度入口、阻塞状态、进入 userspace/idle 都能证明旧执行区间已经结束;单 纯的抢占不能。

ds-v4-flash

内核如何区分「主动 schedule」和「preempt」

先给结论:不是靠”事后观察”上下文切换本身来分类的,而是调用者显式告诉调度器的 —— 两者走的是完全不同的代码路径,这个事实被编码成一个 sched_mode 参数一路传下去,最后传 给 Tasks RCU 的是一个 preempt 布尔值。

  1. 两条不同的入口路径

__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() 开头:

  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
  1. 第二个证据:prev->__state(在 __schedule() 内部再次确认)

这就是 /proc//status 里 voluntary_ctxt_switches / nonvoluntary_ctxt_switches 的来源 —— 不是观测出来的,是路径决定的。

  1. 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 对两者的不对称待遇,关键就在这个参数上。

  1. 为什么 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.

所以 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 只在自愿切换时递增。)

  1. userspace 执行为什么也是 QS(你说的第二个证据)

三个机制,全部是”明确知道任务在 userspace/即将回 userspace”:

  1. 返回用户态路径:exit_to_user_mode_loop()(kernel/entry/common.c:44-47)里,TIF_NEED_RESCHED 时调用的是普通 schedule(),不是 preempt_schedule_irq()。即使任务是因 为时间片用完被”抢”下 CPU,它此刻也处于内核→用户态边界:内核里没有悬而未决的执行状态,不会被调度器从 trampoline 中间恢复。所以这条路径天然走 SM_NONE = 自愿。

  2. 调度时钟 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.”

  3. 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 标志:

holdout list 清空后,grace period 结束,销毁 trampoline 之类的操作(call_rcu_tasks()/synchronize_rcu_tasks() 保护的) 才可以执行。

一句话总结:Tasks RCU 的读侧临界区太宽泛(整个 task 的内核执行都算),所以它只能靠”task 主动让出 CPU 或回到用户态”这类 强证据来判定 QS,而不是像普通 RCU 那样依赖禁抢占区间的边界。

本站所有文章转发 CSDN 将按侵权追究法律责任,其它情况随意。