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 层次关系

当同时存在软亲和性和硬亲和性时,调度器的决策流程如下:

  1. 硬亲和性过滤:调度器首先检查进程的 CPU 掩码,排除所有不在掩码中的 CPU。
  2. 软亲和性优化:在剩余的合法 CPU 集合中,调度器应用软亲和性策略(如缓存热度、调度域优先级)选择最优的 CPU。
  3. 负载均衡:如果软亲和性推荐的 CPU 负载过高,调度器可以在硬亲和性允许的范围内选择其他 CPU。

4.3.2 区别与联系对比

对比维度 软亲和性 硬亲和性
本质 调度策略偏好 资源访问约束
用户接口 调度策略、nice、cgroups cpu taskset、sched_setaffinity、cpuset
调度器自由度 高(可灵活迁移) 低(受掩码限制)
缓存局部性 尽力保持 强制保持(在掩码内)
负载均衡影响 参与全局负载均衡 仅在掩码内进行负载均衡
典型应用 通用进程、Web 服务器 实时进程、数据面处理

4.4 软硬亲和性协同工作流程图

下图展示了调度器在处理一个进程时,软亲和性与硬亲和性的协同工作流程:

软亲和性与硬亲和性协同工作流程图 进程调度请求 步骤 1:硬亲和性过滤 检查 CPU 掩码,排除非法 CPU 合法 CPU 集合为空? 是 调度失败 返回错误(EINVAL) 否 步骤 2:软亲和性优化 在合法 CPU 中,选择缓存热度最高的 CPU 步骤 3:负载均衡检查 检查目标 CPU 负载是否可接受 进程调度到目标 CPU 说明 硬亲和性先过滤,软亲和性后优化

4.5 实际调优中的选择策略

在实际性能调优工程中,选择软亲和性还是硬亲和性,取决于应用场景和性能目标:

  • 优先使用软亲和性:当系统运行多种类型的任务,且负载动态变化时,软亲和性允许调度器灵活调整,通常能获得更好的整体吞吐量。
  • 使用硬亲和性的典型场景:
    • 实时系统:需要确定性延迟,避免进程被迁移到其他 CPU。
    • DPDK/NFV:数据面处理进程需要独占 CPU,避免上下文切换和缓存污染。
    • NUMA 感知优化:将内存密集型的进程绑定到与内存控制器同一 NUMA 节点的 CPU 上。
    • 中断隔离:将中断处理绑定到特定 CPU,避免中断干扰关键业务进程。
  • 混合使用:先通过硬亲和性划定 CPU 子集(如绑定到同一 NUMA 节点的 4 个核心),再让调度器在子集内通过软亲和性进行动态负载均衡。
调优经验:在大多数 Linux 服务器上,除非有明确的实时性或缓存隔离需求,否则保持默认的软亲和性策略即可。滥用硬亲和性可能导致调度器负载均衡能力下降,反而增加调度延迟。