你盯着 top 里的 cs 列,数字跳到了每秒四万多次。系统也没跑什么重活,为什么?
先说清楚两个最基本的概念:用户抢占和内核抢占。
preempt_count 的 32 位
Linux 用一个 32 位整数记录 CPU 在什么上下文里。源码在 include/linux/preempt.h(Linux v6.6):
#define PREEMPT_BITS 8
#define SOFTIRQ_BITS 8
#define HARDIRQ_BITS 4
#define NMI_BITS 4
最低 8 位是抢占计数,紧接着 8 位是软中断计数,再 4 位是硬中断嵌套,最高 4 位是 NMI(从 bit 20 开始)。第 28 位是 PREEMPT_ACTIVE 标记,标识当前进程因抢占而非主动阻塞进入调度。只要有一个字段不为 0,CPU 就不可以被抢占。
用户抢占
一个进程在用户态跑着,硬件时钟中断来了,CPU 切到内核。操作系统发现 B 比 A 急,把 A 的 TIF_NEED_RESCHED 置上,然后继续跑 A。arch/x86/include/asm/thread_info.h(v6.6):
#define TIF_NEED_RESCHED 3 /* rescheduling necessary */
置上之后 A 还在跑,真正交接的时机是固定的:系统调用返回用户态之前、中断返回用户态之前、信号返回之前。这三个点内核都会查 TIF_NEED_RESCHED,发现是 1,就调 schedule() 把 A 换下去。
用户抢占的触发时机是固定的,不是任意时刻。
内核抢占
进程 A 进了内核执行系统调用,按理说应该一口气跑完。但 Linux 内核可以中途把 A 换走。include/linux/preempt.h(v6.6):
#define preemptible() (preempt_count() == 0 && !irqs_disabled())
内核要抢占当前进程,必须同时满足两个条件:preempt_count 等于 0,且中断没关。kernel/sched/core.c(v6.6):
asmlinkage __visible void __sched schedule(void)
{
do {
preempt_disable();
__schedule(SM_NONE);
sched_preempt_enable_no_resched();
} while (need_resched());
}
EXPORT_SYMBOL(schedule);
内核抢占没有固定触发时机。进程 A 在内核态的任何一点都可能被打断——只要此时的 preempt_count 为 0 且中断是开的。
什么时候 preempt_count 不为 0
spin_lock() 把抢占计数加 1,锁持有期间禁止抢占。硬中断进来加 HARDIRQ_OFFSET,软中断处理加 SOFTIRQ_OFFSET。加了大锁,系统反而变慢了——锁持有期间 preempt_count 不为 0,内核抢占被彻底关掉,其他进程全在等锁,调度延迟全压在持锁的那几百毫秒里。
反过来,只要 preempt_count 是 0,锁也没拿,内核态的任何一点都可能被抢走。
回到 cs
cs 高不等于 CPU 负载高。cs 跳到四万次/秒,说明内核在频繁执行 schedule(),原因无非三点:
1. 进程过多或时间片太短——用户抢占。 时钟中断频繁检查并置位 TIF_NEED_RESCHED,一旦系统调用或中断返回用户态,立刻触发抢占。线程数越多,来回切得越凶。
2. 满足内核抢占条件——内核抢占。 只要当前没有持锁、没关中断(preempt_count == 0 且中断开启),内核态函数执行到任意一行代码都可能被 preempt_schedule() 打断。
3. 互斥锁等待或 I/O 阻塞——自愿切换。 大量线程因等待互斥锁、信号量或 I/O 而陷入阻塞,被迫主动放弃 CPU。每次等锁都会触发一次 schedule() 并计为 cs。注意:spin_lock 是自旋锁,获取不到锁时 CPU 处于忙等状态,它不触发上下文切换。真正的 cswch/s 飙高来自 mutex/semaphore/I/O 阻塞。这是"无重活但 cs 高"的常见干扰项。
想分清楚是哪种,用 pidstat -w 1:cswch/s 飙高 → 互斥锁或 I/O 阻塞;nvcswch/s 飙高 → 非自愿切换,先看 TIF_NEED_RESCHED(用户抢占),再看 preempt_count(内核抢占)。
想进一步定位调度瓶颈在哪,用 perf sched record -a -g -- sleep 10 && perf sched latency,它能告诉你哪个进程调度最频繁、延迟主要卡在哪个函数。