在Linux系统中,进程调度器就像交通指挥中心,负责决定哪个进程能获得CPU资源、能获得多少时间。我曾在生产环境遇到过一个典型案例:某个关键服务突然响应变慢,但CPU使用率显示还有大量空闲。经过排查发现,正是由于进程优先级设置不当,导致低优先级的后台任务抢占了关键服务的CPU时间片。
理解进程调度机制,能帮助我们:
nice值(-20到19)是用户空间可见的优先级指标,数值越小优先级越高。通过以下命令可以查看和修改:
bash复制# 查看进程nice值
ps -eo pid,ni,comm
# 启动时设置nice值
nice -n 10 ./script.sh
# 修改运行中进程的nice值
renice 5 -p 1234
内核中,nice值会被转换为静态优先级(static_prio),换算公式为:
code复制static_prio = MAX_RT_PRIO + nice + 20
其中MAX_RT_PRIO默认为100,所以普通进程的静态优先级范围是100-139。
注意:非root用户只能调低优先级(增大nice值),这是Linux的多用户安全机制。
内核实际使用的是动态优先级(dynamic_prio),计算公式:
code复制dynamic_prio = max(100, min(static_prio - bonus + 5, 139))
bonus值范围0-10,反映进程的交互性。I/O密集型进程会获得bonus奖励,CPU密集型则会被惩罚。
我曾通过以下方法验证动态调整:
c复制// 监控进程优先级变化
while(1) {
FILE *fp = fopen("/proc/self/stat", "r");
fscanf(fp, "%*d %*s %*c %*d %*d %*d %*d %*d %*u %*u %*u %*u %*u %*d %*d %*d %*d %*d %*d %*d %*d %*d %d", &pri);
printf("Current priority: %d\n", pri);
fclose(fp);
sleep(1);
}
实时进程(SCHED_FIFO/SCHED_RR)的优先级范围是1-99,数字越大优先级越高。关键区别:
设置实时优先级的正确姿势:
c复制struct sched_param param = { .sched_priority = 50 };
pthread_setschedparam(pthread_self(), SCHED_FIFO, ¶m);
警告:错误使用实时优先级可能导致系统卡死!务必通过ulimit -r限制普通用户的实时优先级上限。
完全公平调度器(CFS)通过vruntime(虚拟运行时间)实现公平性:
code复制vruntime += 实际运行时间 * NICE_0_LOAD / 权重
权重由优先级决定,高优先级进程的vruntime增长更慢。
内核使用红黑树组织可运行进程,键值就是vruntime。调度时总是选择最左侧(vruntime最小)的进程。这保证了:
我在内核日志中观察到的典型调度事件:
code复制[ 1234.567890] watchdog: BUG: soft lockup - CPU#1 stuck for 22s! [a.out:54321]
[ 1234.567891] Call Trace:
[ 1234.567892] <IRQ>
[ 1234.567893] scheduler_tick+0x5d/0x120
[ 1234.567894] update_process_times+0x3a/0x50
一次完整的上下文切换(context_switch)包含:
实测数据(x86_64平台):
| 操作 | 耗时(ns) |
|---|---|
| 纯寄存器切换 | 200-300 |
| 包含TLB刷新 | 1000-1500 |
| 跨NUMA节点切换 | 3000+ |
优化建议:
典型症状:进程明明就绪却长时间未运行。排查步骤:
bash复制cat /proc/<pid>/sched
关注:
bash复制echo 1 > /sys/kernel/debug/tracing/events/sched/enable
cat /sys/kernel/debug/tracing/trace_pipe
bash复制cat /proc/sched_debug | grep -A10 'cpu#'
典型案例:高优先级进程等待低优先级进程持有的锁,而低优先级进程被中优先级进程抢占。
解决方案:
c复制pthread_mutexattr_t attr;
pthread_mutexattr_setprotocol(&attr, PTHREAD_PRIO_INHERIT);
pthread_mutex_init(&mutex, &attr);
c复制rt_mutex_init(&rt_lock);
rt_mutex_lock(&rt_lock);
对于数据库等关键服务,推荐配置:
bash复制# 设置实时优先级
chrt -f 80 /path/to/service
# 绑定CPU核心
taskset -c 0,1 /path/to/service
# 禁用内存过量使用
echo 2 > /proc/sys/vm/overcommit_memory
# 调整swappiness
echo 10 > /proc/sys/vm/swappiness
核心调用流程:
code复制schedule()
├── pick_next_task() # 选择下一个运行进程
│ ├── for_each_class() # 遍历调度类
│ └── class->pick_next_task()
├── context_switch() # 执行上下文切换
│ ├── switch_mm() # 地址空间切换
│ └── switch_to() # 寄存器状态切换
└── post_schedule() # 后处理
关键数据结构:
c复制struct task_struct {
// 调度相关字段
int prio, static_prio, normal_prio;
unsigned int rt_priority;
struct sched_entity se;
struct sched_rt_entity rt;
// ...
};
struct sched_class {
const struct sched_class *next;
void (*enqueue_task) (...);
void (*dequeue_task) (...);
struct task_struct * (*pick_next_task) (...);
// ...
};
vruntime计算关键代码(kernel/sched/fair.c):
c复制static void update_curr(struct cfs_rq *cfs_rq)
{
struct sched_entity *curr = cfs_rq->curr;
u64 now = rq_clock_task(rq_of(cfs_rq));
u64 delta_exec;
delta_exec = now - curr->exec_start;
curr->vruntime += calc_delta_fair(delta_exec, curr);
curr->exec_start = now;
// ...
}
权重计算(prio_to_weight数组):
c复制const int prio_to_weight[40] = {
/* -20 */ 88761, 71755, 56483, 46273, 36291,
/* -15 */ 29154, 23254, 18705, 14949, 11916,
/* -10 */ 9548, 7620, 6100, 4904, 3906,
/* -5 */ 3121, 2501, 1991, 1586, 1277,
/* 0 */ 1024, 820, 655, 526, 423,
/* 5 */ 335, 272, 215, 172, 137,
/* 10 */ 110, 87, 70, 56, 45,
/* 15 */ 36, 29, 23, 18, 15,
};
在Docker/K8s环境中常见问题:
解决方案:
bash复制# 设置正确的CPU份额
docker run --cpu-shares=1024 ...
# 使用cpuset约束
docker run --cpuset-cpus="0-3" ...
# 调整cgroup调度参数
echo "100000" > /sys/fs/cgroup/cpu/docker/.../cpu.cfs_period_us
echo "50000" > /sys/fs/cgroup/cpu/docker/.../cpu.cfs_quota_us
硬件中断可能打断正在运行的进程,导致:
优化方案:
bash复制echo 2 > /proc/irq/123/smp_affinity
c复制request_threaded_irq(irq, handler, thread_fn, flags, name, dev);
跨NUMA节点调度会导致:
最佳实践:
bash复制numactl --cpunodebind=0 --membind=0 ./program
bash复制echo 1 > /proc/sys/kernel/numa_balancing
bash复制perf stat -e numa_migrations,local_load,remote_load