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 集合到应用绑定的完整流程:
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 亲和性,为后续的多核任务绑定与迁移控制奠定基础。