4. CPU 亲和性类型:软亲和性与硬亲和性的区别与联系
CPU 亲和性(CPU Affinity)是操作系统调度器将进程/线程绑定到特定 CPU 核心上运行的一种机制。根据实现层次和强制程度的不同,亲和性分为软亲和性(Soft Affinity)和硬亲和性(Hard Affinity)两种类型。理解两者的区别与联系,是进行调度延迟分析与性能调优的基础。
核心观点:软亲和性是一种“建议”,调度器会尽量遵守但非强制;硬亲和性是一种“命令”,调度器必须严格遵守,不允许违反。
4.1 软亲和性(调度策略)
软亲和性由 Linux 内核的调度策略(Scheduling Policy)和调度类(Scheduling Class)隐式或显式地实现。它不通过 CPU 掩码强制绑定,而是通过调度器的负载均衡(Load Balance)算法和缓存热度(Cache Hotness)偏好来体现。
4.1.1 软亲和性的实现机制
- 调度域(Scheduling Domain)与调度组(Scheduling Group):内核将 CPU 按拓扑结构(如超线程、物理核、NUMA 节点)划分为多个调度域。调度器优先在同一个调度域内进行负载均衡,避免跨域迁移带来的缓存丢失和内存访问延迟。
- 唤醒迁移(Wake-up Migration)偏好:当进程被唤醒时,调度器会优先选择上次运行该进程的 CPU(即“缓存热”的 CPU),以减少缓存未命中。这是一种典型的软亲和性行为。
- CFS 调度类的负载均衡:完全公平调度器(CFS)在运行队列之间进行负载均衡时,会尽量保持进程在同一个 CPU 或同一组 CPU 上运行,除非负载严重不均。
性能提示:软亲和性在大多数通用场景下已经足够高效。它允许调度器在保持缓存局部性和平衡系统负载之间做出动态权衡,适合 CPU 密集型任务和 I/O 密集型任务混合部署的场景。
4.1.2 软亲和性的特点
| 特性 | 说明 |
|---|---|
| 强制程度 | 弱(建议性) |
| 实现方式 | 调度策略、调度域、负载均衡算法 |
| 用户控制 | 间接(通过 nice 值、调度策略、cgroups) |
| 迁移代价 | 低(调度器可随时迁移) |
| 适用场景 | 通用服务器、负载动态变化、多任务混合部署 |
4.2 硬亲和性(CPU 掩码)
硬亲和性通过设置进程的 CPU 掩码(CPU Mask / cpumask)来显式限制进程可以运行的 CPU 集合。一旦设置,调度器在调度该进程时,只能从掩码指定的 CPU 中选择运行核心,即使其他 CPU 空闲,也不能违反此约束。
4.2.1 硬亲和性的设置方法
在 Linux 系统中,硬亲和性主要通过以下方式设置:
- 命令行工具:使用
taskset命令设置或查询进程的 CPU 亲和性掩码。 - 系统调用:使用
sched_setaffinity()和sched_getaffinity()系统调用。 - cgroups cpuset 子系统:通过 cgroup 的 cpuset 控制器,将一组进程绑定到指定的 CPU 集合。
# 使用 taskset 将进程 PID 1234 绑定到 CPU 0 和 CPU 2
taskset -p -c 0,2 1234
# 使用 sched_setaffinity 系统调用(C 代码示例)
#define _GNU_SOURCE
#include <sched.h>
#include <stdio.h>
int main() {
cpu_set_t mask;
CPU_ZERO(&mask);
CPU_SET(0, &mask); // 绑定到 CPU 0
CPU_SET(2, &mask); // 绑定到 CPU 2
if (sched_setaffinity(0, sizeof(mask), &mask) == -1) {
perror("sched_setaffinity");
return 1;
}
printf("Process bound to CPU 0 and CPU 2\n");
return 0;
}
4.2.2 硬亲和性的特点
| 特性 | 说明 |
|---|---|
| 强制程度 | 强(强制性) |
| 实现方式 | CPU 掩码(cpumask) |
| 用户控制 | 直接(taskset、sched_setaffinity、cpuset) |
| 迁移代价 | 高(只能在掩码内迁移) |
| 适用场景 | 实时系统、DPDK、高性能计算、NUMA 感知优化 |
注意事项:硬亲和性设置不当可能导致 CPU 负载不均,部分核心过载而其他核心空闲。在设置硬亲和性时,必须仔细规划 CPU 分配,避免造成资源浪费或性能瓶颈。
4.3 软亲和性与硬亲和性的关系
软亲和性和硬亲和性并非互斥,而是共存且分层的关系。硬亲和性定义了进程可运行的最大 CPU 集合,而软亲和性在这个集合内部进一步优化调度决策。
4.3.1 层次关系
当同时存在软亲和性和硬亲和性时,调度器的决策流程如下:
- 硬亲和性过滤:调度器首先检查进程的 CPU 掩码,排除所有不在掩码中的 CPU。
- 软亲和性优化:在剩余的合法 CPU 集合中,调度器应用软亲和性策略(如缓存热度、调度域优先级)选择最优的 CPU。
- 负载均衡:如果软亲和性推荐的 CPU 负载过高,调度器可以在硬亲和性允许的范围内选择其他 CPU。
4.3.2 区别与联系对比
| 对比维度 | 软亲和性 | 硬亲和性 |
|---|---|---|
| 本质 | 调度策略偏好 | 资源访问约束 |
| 用户接口 | 调度策略、nice、cgroups cpu | taskset、sched_setaffinity、cpuset |
| 调度器自由度 | 高(可灵活迁移) | 低(受掩码限制) |
| 缓存局部性 | 尽力保持 | 强制保持(在掩码内) |
| 负载均衡影响 | 参与全局负载均衡 | 仅在掩码内进行负载均衡 |
| 典型应用 | 通用进程、Web 服务器 | 实时进程、数据面处理 |
4.4 软硬亲和性协同工作流程图
下图展示了调度器在处理一个进程时,软亲和性与硬亲和性的协同工作流程:
4.5 实际调优中的选择策略
在实际性能调优工程中,选择软亲和性还是硬亲和性,取决于应用场景和性能目标:
- 优先使用软亲和性:当系统运行多种类型的任务,且负载动态变化时,软亲和性允许调度器灵活调整,通常能获得更好的整体吞吐量。
- 使用硬亲和性的典型场景:
- 实时系统:需要确定性延迟,避免进程被迁移到其他 CPU。
- DPDK/NFV:数据面处理进程需要独占 CPU,避免上下文切换和缓存污染。
- NUMA 感知优化:将内存密集型的进程绑定到与内存控制器同一 NUMA 节点的 CPU 上。
- 中断隔离:将中断处理绑定到特定 CPU,避免中断干扰关键业务进程。
- 混合使用:先通过硬亲和性划定 CPU 子集(如绑定到同一 NUMA 节点的 4 个核心),再让调度器在子集内通过软亲和性进行动态负载均衡。
调优经验:在大多数 Linux 服务器上,除非有明确的实时性或缓存隔离需求,否则保持默认的软亲和性策略即可。滥用硬亲和性可能导致调度器负载均衡能力下降,反而增加调度延迟。