在多核系统上,CPU 资源如何被分配,直接决定了关键业务的吞吐量和延迟。本节介绍的三个优化方向——进程绑定到特定核心、调整调度策略以及均衡硬件中断——并不需要修改内核代码,只需利用内核现有的接口就能实现显著的性能提升。
1. 进程绑定:让关键任务独占或固定在某组 CPU 核心上
默认情况下,Linux 调度器会在所有可用核心之间迁移进程,以尽量让各核心的任务队列长度接近。但这种迁移会带来缓存失效和上下文切换的开销。对于数据库、高性能计算或实时任务,显式地将进程“钉”在指定的核心上,既能避免迁移损耗,也能隔离干扰。
查看进程当前绑定的核心
taskset -cp <PID> # 查看进程的 CPU 亲和性,以核心列表和掩码形式展示
cat /proc/<PID>/status | grep Cpus_allowed_list
启动时指定绑核
taskset -c 0,2,4 ./my_server # 将进程限定在 core0、2、4 上运行
运行时调整现有进程的绑核
taskset -cp 3-7 <PID> # 将已在运行的进程重新绑定到 core3~core7
更精细的控制:cgroup cpuset
当需要为多个进程组成的服务设置独立的 CPU 资源池时,cgroup 的 cpuset 子系统更合适。它不仅可以限定可用的核心,还能指定内存节点。
# 创建一个 cgroup,分配核心 4-7
mkdir /sys/fs/cgroup/cpuset/myapp
echo "4-7" > /sys/fs/cgroup/cpuset/myapp/cpuset.cpus
echo "0" > /sys/fs/cgroup/cpuset/myapp/cpuset.mems # 内存节点 0
# 将进程 PID 写入 tasks 即可
echo <PID> > /sys/fs/cgroup/cpuset/myapp/cgroup.procs
实用建议:
- 对于延迟敏感的服务(如 Redis、Nginx worker),可以将它们绑定到固定的几个核心,并在内核启动参数中使用
isolcpus将这些核心从调度器的常规负载中隔离出来,进一步减少中断和其他进程的干扰。 - 避免过度绑定:如果核心数量略大于线程数,可以留出空闲核心处理中断和内核线程。
2. 调度策略调整:为不同负载选择合适的调度类
Linux 支持多种调度策略,主要分为普通调度类(SCHED_OTHER)和实时调度类(SCHED_FIFO、SCHED_RR),以及较新的 SCHED_BATCH 和 SCHED_IDLE。
- SCHED_OTHER(CFS 完全公平调度器):默认策略,以时间片轮转按优先级分配 CPU 时间,通过
nice值(-20 到 +19)调整权重。适合所有常规应用程序。 - SCHED_FIFO:静态优先级实时调度,高优先级的 FIFO 任务会一直运行直到阻塞或主动让出 CPU。严禁在普通计算任务上使用。
- SCHED_RR:与 FIFO 类似,但相同优先级的任务会轮流执行时间片。
- SCHED_BATCH:适合非交互的批处理作业,调度粒度更粗,减少切换。
- SCHED_IDLE:极低优先级,只在系统空闲时运行。
查看进程的调度策略
chrt -p <PID> # 显示策略和实时优先级
修改调度策略和优先级
# 将 PID 设置为 SCHED_FIFO 策略,优先级为 80(范围 1~99,数值越大越优先)
chrt -f -p 80 <PID>
# 或者以 SCHED_RR 启动新进程
chrt -r 80 ./my_realtime_task
调整普通进程的 nice 值
renice -n -5 -p <PID> # 提高优先级(负值表示更高优先级)
nice -n 10 ./batch_job # 降低优先级
重要警告:
- 实时调度策略一旦滥用(例如一个无限制循环的 FIFO 任务),会完全霸占 CPU,导致整个系统卡死,连 SSH 远程登录都可能无响应。在生产环境修改前务必充分测试,并配合进程绑定隔离核心。
- 大部分场景中,精心调整
nice和 cgroup 的 CPU 权重比使用实时调度更安全、更易管理。
3. 中断均衡:将硬件中断分散到多核以减轻单一 CPU 瓶颈
高速网卡、磁盘控制器等硬件会频繁产生中断请求(IRQ)。默认情况下,所有中断可能都涌向 CPU 0,导致核心 0 负载过高(“CPU 0 瓶颈”),而其他核心空闲。通过手动绑定中断处理核心,并借助多队列网卡等硬件特性,可以实现中断负载的均衡。
查看当前中断分布
cat /proc/interrupts # 列出每个 IRQ 在各 CPU 上的处理次数
手动设置中断亲和性
每个 IRQ 号在 /proc/irq/<IRQ号>/smp_affinity(或 smp_affinity_list)中都有一个位掩码,决定允许哪些核心处理该中断。
# 设定 IRQ 125 只能由 CPU 4~7 处理(十六进制掩码 0xf0 对应 CPU 4,5,6,7)
echo 0f > /proc/irq/125/smp_affinity
# 或者用列表形式(更方便)
echo 4-7 > /proc/irq/125/smp_affinity_list
自动化工具 irqbalance
大部分通用环境推荐使用 irqbalance 守护进程,它会根据系统负载和中断频次自动调整亲和性。但在性能调优场景下,特别是使用 isolcpus 隔离核心后,通常需要关闭 irqbalance,然后手动将中断精确绑定到未隔离的核心上,以避免它对实时核心的扰动。
# 停止并禁用 irqbalance
systemctl stop irqbalance
systemctl disable irqbalance
多队列网卡的 RSS 技术
现代网卡支持接收端缩放(RSS),它会将网络流分成多条硬件队列,每条队列对应独立的中断号。这样不仅可以手动将不同中断绑定到不同核心,还能利用 ethtool 调整哈希字段(如 IP、端口),使流量均匀分布。
# 查看网卡队列数
ethtool -l eth0
# 设置网卡合并中断的参数(NAPI、合并帧数等)
ethtool -C eth0 rx-usecs 20
实用场景:
- 在 NAT 网关或负载均衡器上,将网卡的各队列中断分别绑定到 CPU 的多个核心,同时将转发应用(如 Nginx)绑定到另一些核心,可以显著降低 tail latency。
- 结合
numactl确保 IRQ 绑定与处理该流的进程在同一 NUMA 节点上,可进一步减少跨节点内存访问。
总结
- 进程绑定:用
taskset或 cgroup cpuset 固定关键进程的位置,结合isolcpus实现物理隔离。 - 调度策略:日常调优从
nice和 cgroup 权重入手;实时任务严格控制范围并使用专用核心。 - 中断均衡:先观察
/proc/interrupts,对于高吞吐场景关闭irqbalance并手动绑定 IRQ,利用多队列硬件分散处理。
三者结合,正是在高负载下实现稳定、低延迟响应的核心方法。每一次调优前,记得用 perf、mpstat、sar 等工具建立性能基线,改动后一一验证,避免盲目优化。