4. CPU 亲和性:把任务钉在指定的核上
好,咱们进入正经优化环节之前,先把一个基础概念说透——CPU 亲和性。
我刚接触多核优化时做过一件傻事:写了个网络收包程序,发现 8 个核里只有 1 个核在疯狂飙到 100%,其他核在围观。后来才意识到,我给那个进程绑定了 CPU 0,还不自知。嗯,这就是亲和性在捣鬼。
说白了,CPU 亲和性就是让进程或中断强制跑在指定的 CPU 上,而不是任由调度器随机分配。这么做的好处很明显:缓存命中率高了,上下文切换少了,性能自然就稳了。
4.1 硬亲和性 vs. 软亲和性
亲和性分两种:硬亲和性和软亲和性。我在很多公开课和线上答疑里发现,好多朋友把这两者搞混,所以咱们先把这个理清楚。
| 属性 | 硬亲和性 | 软亲和性 |
|---|---|---|
| 控制方式 | 硬件/固件级别绑定 | 软件/OS 调度器策略 |
| 灵活性 | 固定,难以在线修改 | 可动态调整 |
| 粒度 | 中断控制器/设备级 | 进程/线程级 |
| 实现位置 | GIC / IOMMU / 中断路由表 | Linux 内核调度器 + cgroup / sched_setaffinity |
| 典型场景 | DMA 中断路由、PCIe MSI-X 绑定 | 进程/线程亲和性设置、绑核测试 |
硬亲和性你得把它想象成硬件级的强制绑定。比如中断控制器(GIC)会把某个设备的中断线直接映射到指定 CPU 上——这是硬件层面的路由决策,操作系统都没法干预。我在调试一块 FPGA 加速卡时,它的 DMA 完成中断硬绑到了 CPU0,结果 CPU0 负载一直高居不下,其他的核闲着。那个场景让我第一次深刻理解了硬亲和性的边界。
软亲和性就好比给调度器一个建议或指令。调度器会尽量满足你的要求,但在系统负载极度不均衡时,它仍然可能迁移任务。你通过 sched_setaffinity 设置的亲和性就属于这一类。
4.2 sched_setaffinity:软亲和性的核心 API
实战中最常用的是 sched_setaffinity 系统调用。这个函数设置进程/线程的 CPU 亲和性掩码。我来演示一个实际例子。
#define _GNU_SOURCE
#include <sched.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
void bind_thread_to_cpu(int cpu_id) {
cpu_set_t cpuset;
CPU_ZERO(&cpuset);
CPU_SET(cpu_id, &cpuset);
pthread_t current_thread = pthread_self();
if (pthread_setaffinity_np(current_thread, sizeof(cpu_set_t), &cpuset) != 0) {
perror("pthread_setaffinity_np failed");
exit(EXIT_FAILURE);
}
printf("线程已绑定到 CPU %d\n", cpu_id);
}
- CPU_ZERO:清零掩码
- CPU_SET:置位指定 CPU
- pthread_setaffinity_np:线程级设置,非标准扩展
- CPU_ISSET:检查是否绑定到某 CPU
这段代码看起来简单,但我在真实项目里遇到过一个大坑:CPU 编号搞错。有些系统上用 lscpu 看到的是逻辑编号,而 sched_setaffinity 使用的是系统级编号。你想想看,一个收包线程本想绑到 CPU 0,结果绑成了 CPU 7,性能不升反降。
cat /proc/cpuinfo | grep "processor" | wc -l 确认核数,再用 cat /proc/$$/status | grep Cpus_allowed_list 验证当前进程的亲和性。
4.3 知识体系总览
下面这张图,把亲和性的两个分支、对应 API 和典型粒度梳理清楚。你记着它,以后做优化可以先定位自己绑的是硬路由还是软调度。
4.4 实战考量:什么时候用什么亲和性?
现在你清楚了两种亲和性的区别,但什么时候用硬、什么时候用软呢?我总结一个快速判断法:
- 中断 / DMA:必须用硬亲和性。通过
/proc/irq/或irqbalance把中断路由到指定 CPU,保证始终在那里处理。 - 用户态进程:用软亲和性。比如 ksoftirqd 线程,通过
taskset -c 2 command启动,或者cpuset控制组。 - 混合场景:先配置硬亲和性把中断钉死,再把那个中断对应的收包线程用软亲和性绑到同一个 CPU。
好了,这一章我们讲清楚了什么是亲和性、硬亲和性和软亲和性的区别,以及 sched_setaffinity 的具体用法。你理解了这些,后面做 DMA 中断亲和性优化时,就能知道自己在哪一层下刀了。