4. sched_setaffinity 系统调用:函数原型、CPU_SET 宏操作与 C 语言实现

在 DPDK 多核任务绑定场景中,sched_setaffinity 是 Linux 内核提供的核心系统调用,用于将进程或线程绑定到指定的 CPU 核心集合。本章节深入解析其函数原型、CPU 集合操作宏以及完整的 C 语言实现模式。

4.1 sched_setaffinity 函数原型

系统调用 sched_setaffinity 定义在 <sched.h> 头文件中,其原型如下:

#include <sched.h>

int sched_setaffinity(pid_t pid, size_t cpusetsize, const cpu_set_t *mask);
参数 类型 说明
pid pid_t 目标进程 ID;0 表示调用线程自身
cpusetsize size_t cpu_set_t 结构体的大小(字节),通常使用 sizeof(cpu_set_t)
mask const cpu_set_t * 指向 CPU 集合的指针,指定允许运行的 CPU 核心

返回值:成功返回 0;失败返回 -1 并设置 errno。

关键错误码:
  • EINVAL:cpusetsize 小于内核使用的 CPU 集合大小
  • EPERM:调用者无权限(非 root 或未设置 CAP_SYS_NICE)
  • ESRCH:目标进程不存在

4.2 CPU_SET 宏操作族

Linux 提供一组宏来操作 cpu_set_t 位图结构,用于构建和查询 CPU 亲和性掩码:

宏 功能 使用示例
CPU_ZERO(cpusetp) 清空 CPU 集合 CPU_ZERO(&mask);
CPU_SET(cpu, cpusetp) 将指定 CPU 加入集合 CPU_SET(2, &mask);
CPU_CLR(cpu, cpusetp) 从集合中移除指定 CPU CPU_CLR(2, &mask);
CPU_ISSET(cpu, cpusetp) 测试 CPU 是否在集合中 if (CPU_ISSET(2, &mask))
CPU_COUNT(cpusetp) 返回集合中 CPU 的数量 int n = CPU_COUNT(&mask);
最佳实践:在 DPDK 中,通常先调用 CPU_ZERO 清空集合,然后使用 CPU_SET 逐个添加目标核心,最后调用 sched_setaffinity 应用绑定。

4.3 C 语言实现亲和性绑定

以下是一个完整的 C 语言示例,展示如何将当前线程绑定到 CPU 核心 0、2、4:

#include <stdio.h>
#include <stdlib.h>
#include <sched.h>
#include <unistd.h>
#include <errno.h>

int main() {
    cpu_set_t mask;
    int cpus[] = {0, 2, 4};
    int i, ret;

    /* 1. 清空 CPU 集合 */
    CPU_ZERO(&mask);

    /* 2. 添加目标 CPU 核心 */
    for (i = 0; i < 3; i++) {
        CPU_SET(cpus[i], &mask);
    }

    /* 3. 调用 sched_setaffinity 绑定当前进程 */
    ret = sched_setaffinity(0, sizeof(cpu_set_t), &mask);
    if (ret != 0) {
        perror("sched_setaffinity failed");
        exit(EXIT_FAILURE);
    }

    printf("Thread bound to CPUs: ");
    for (i = 0; i < CPU_SETSIZE; i++) {
        if (CPU_ISSET(i, &mask)) {
            printf("%d ", i);
        }
    }
    printf("\n");

    /* 4. 验证绑定结果 */
    cpu_set_t getmask;
    CPU_ZERO(&getmask);
    if (sched_getaffinity(0, sizeof(cpu_set_t), &getmask) == 0) {
        printf("Effective affinity mask: ");
        for (i = 0; i < CPU_SETSIZE; i++) {
            if (CPU_ISSET(i, &getmask)) {
                printf("%d ", i);
            }
        }
        printf("\n");
    }

    return 0;
}

4.4 亲和性绑定流程 SVG 图

下图展示了从创建 CPU 集合到应用绑定的完整流程:

sched_setaffinity 绑定流程 CPU_ZERO(&mask) CPU_SET(cpu, &mask) sched_setaffinity() sched_getaffinity() 关键说明 • CPU_ZERO 必须在使用 CPU_SET 之前调用,确保集合初始为空 • sched_setaffinity 的 pid 参数为 0 时绑定调用线程自身 • 建议绑定后调用 sched_getaffinity 验证实际生效的 CPU 集合 • DPDK 中通常每个 lcore 线程独立调用此函数实现一对一绑定

4.5 线程级绑定:pthread_setaffinity_np

对于多线程程序(DPDK 的每个 lcore 通常是一个 pthread),应使用 POSIX 线程扩展接口:

#define _GNU_SOURCE
#include <pthread.h>

int pthread_setaffinity_np(pthread_t thread, size_t cpusetsize,
                           const cpu_set_t *cpuset);
int pthread_getaffinity_np(pthread_t thread, size_t cpusetsize,
                           cpu_set_t *cpuset);
接口 适用场景 线程标识
sched_setaffinity 进程级绑定(单线程进程) PID
pthread_setaffinity_np 线程级绑定(多线程程序) pthread_t
DPDK 注意事项:DPDK 的 rte_eal_remote_launch 内部会为每个 lcore 创建独立线程。在 lcore 主函数中调用 pthread_setaffinity_np(pthread_self(), sizeof(cpu_set_t), &mask) 可确保该线程独占指定核心,避免与其他 lcore 争抢 CPU 资源。

4.6 常见错误与调试

  • CPU 编号越界:CPU_SETSIZE 通常为 1024,但实际系统 CPU 数量可能更少;使用 sysconf(_SC_NPROCESSORS_CONF) 获取可用核心数。
  • 权限不足:非 root 用户绑定到特定核心可能失败;DPDK 通常以 root 权限运行或设置 CAP_SYS_NICE。
  • 绑定未生效:检查是否在正确的线程上下文中调用;使用 taskset -p <pid> 命令行工具交叉验证。

通过掌握 sched_setaffinity 与 CPU_SET 宏的配合使用,开发者可以在 DPDK 应用中精确控制每个数据面线程的 CPU 亲和性,为后续的多核任务绑定与迁移控制奠定基础。