在Linux系统中,进程调度是操作系统最核心的功能之一。想象一下CPU就像是一个忙碌的厨师,而进程就是等待烹饪的订单。调度器就是那位决定先做哪道菜的餐厅经理,需要平衡效率、公平性和响应速度。
现代Linux内核主要使用完全公平调度器(CFS)作为默认调度算法,它取代了早期的O(1)调度器。CFS的核心思想是为每个进程分配公平的CPU时间,通过虚拟运行时间(vruntime)的计算来实现这一目标。
注意:从Linux 2.6.23内核开始,CFS成为默认调度器,它特别适合交互式系统和服务器环境。
Linux系统支持多种调度策略,每种策略适用于不同类型的进程:
SCHED_NORMAL (也称为SCHED_OTHER)
SCHED_FIFO (先进先出)
SCHED_RR (轮转)
SCHED_BATCH
SCHED_IDLE
Linux内核通过调度类(sched_class)的抽象来实现多种调度策略的共存。主要的调度类包括:
每个CPU核心都有自己的运行队列(runqueue),包含多个调度类的就绪队列。调度器会按照优先级从高到低检查这些队列,选择最合适的进程来运行。
普通进程的静态优先级通过nice值来设置,范围从-20(最高优先级)到19(最低优先级)。可以使用以下命令查看和修改:
bash复制# 查看进程nice值
ps -eo pid,ni,comm
# 启动时设置nice值
nice -n 10 command
# 修改运行中进程的nice值
renice 5 -p 1234
CFS调度器有几个重要的可调参数:
sched_min_granularity_ns (默认4ms)
sched_latency_ns (默认24ms)
sched_wakeup_granularity_ns (默认4ms)
这些参数可以通过/proc文件系统调整:
bash复制# 查看当前值
cat /proc/sys/kernel/sched_min_granularity_ns
# 临时修改
echo 6000000 > /proc/sys/kernel/sched_min_granularity_ns
CPU亲和力(cpu affinity)允许将进程绑定到特定的CPU核心上运行,可以减少缓存失效和提高性能:
bash复制# 查看进程的CPU亲和力
taskset -p 1234
# 启动时设置CPU亲和力
taskset -c 0,1 command
# 修改运行中进程的CPU亲和力
taskset -cp 0,1 1234
top/htop
ps
bash复制ps -eo pid,comm,cls,pri,ni,pcpu,pmem --sort=-pcpu
vmstat
bash复制vmstat 1
pidstat
bash复制pidstat -t -p 1234 1
perf sched
bash复制perf sched record
perf sched latency
trace-cmd
bash复制trace-cmd record -e sched_switch
trace-cmd report
bpftrace
bash复制bpftrace -e 'tracepoint:sched:sched_switch { @[kstack] = count(); }'
对于需要快速响应的交互式应用(如GUI程序),可以考虑:
bash复制# 示例:启动高优先级GUI程序
nice -n -15 ionice -c 2 -n 0 gui_app
对于服务器应用(如Nginx、MySQL):
bash复制# 创建cgroup限制CPU使用
cgcreate -g cpu:/web_server
cgset -r cpu.shares=512 web_server
cgexec -g cpu:web_server nginx
对于需要确定性的实时应用:
bash复制# 设置实时优先级
chrt -f 99 realtime_app
检查运行队列长度
bash复制uptime # 查看1/5/15分钟平均负载
分析上下文切换频率
bash复制vmstat 1 # 查看cs列
检查是否有CPU热点
bash复制perf top
当高优先级进程因为等待低优先级进程持有的资源而被阻塞时,会发生优先级反转。解决方案包括:
Linux调度器会自动进行负载均衡,但有时需要手动干预:
检查CPU使用是否均衡
bash复制mpstat -P ALL 1
考虑手动设置CPU亲和力
调整内核参数如sched_migration_cost
bash复制# 减少调度粒度,提高吞吐量
echo 10000000 > /proc/sys/kernel/sched_min_granularity_ns
# 禁用NUMA平衡,减少跨节点内存访问
echo 0 > /proc/sys/kernel/numa_balancing
# 增加进程fork子进程的速度
echo 65536 > /proc/sys/kernel/threads-max
bash复制# 启用完全抢占式内核
echo 1 > /proc/sys/kernel/preempt
# 减少时钟中断频率(需要内核支持)
echo 1000 > /proc/sys/kernel/sched_rt_period_us
echo 950 > /proc/sys/kernel/sched_rt_runtime_us
# 禁用CPU频率调节
for i in /sys/devices/system/cpu/cpu*/cpufreq/scaling_governor; do echo performance > $i; done
bash复制# 启用KVM steal time accounting
echo 1 > /proc/sys/kernel/kvm_steal_time
# 调整调度器对虚拟CPU的感知
echo 10 > /proc/sys/kernel/sched_migration_cost_ns
# 禁用透明大页(THP)减少抖动
echo never > /sys/kernel/mm/transparent_hugepage/enabled
CFS使用红黑树来维护可运行进程队列,键值是进程的虚拟运行时间(vruntime)。每次调度时,调度器会选择vruntime最小的进程(最左边的节点)来运行。
c复制// 内核中的关键数据结构
struct sched_entity {
struct load_weight load;
struct rb_node run_node;
u64 vruntime;
// ...
};
struct cfs_rq {
struct rb_root tasks_timeline;
struct rb_node *rb_leftmost;
// ...
};
Linux使用高精度时钟源来计算进程运行时间。每次时钟中断时,会更新当前运行进程的vruntime:
code复制vruntime += (实际运行时间) × (NICE_0_LOAD / 进程权重)
其中进程权重由nice值决定,nice值每增加1,权重降低约10%。
当进程被唤醒时,调度器会检查它是否应该抢占当前运行进程。主要考虑因素包括:
在对称多处理(SMP)系统中,Linux采用每CPU运行队列的设计:
Linux调度器通过以下方式保持多核负载均衡:
对于NUMA架构系统,调度器会考虑:
可以通过numactl工具进行手动控制:
bash复制numactl --cpunodebind=0 --membind=0 application
标准Linux内核并非硬实时系统,但可以通过以下补丁增强实时性:
cyclictest:测量调度延迟
bash复制cyclictest -t1 -p80 -n -i 10000 -l 10000
hwlatdetect:检测硬件引起的延迟
rt-tests:实时性测试套件