// 将 CPU 加入到 CPU 集中 voidCPU_SET(int cpu, cpu_set_t *set); voidCPU_SET_S(int cpu, size_t setsize, cpu_set_t *set);
// 将 CPU 从 CPU 集中移除 voidCPU_CLR(int cpu, cpu_set_t *set); voidCPU_CLR_S(int cpu, size_t setsize, cpu_set_t *set);
// 判断 CPU 是否在 CPU 集中 intCPU_ISSET(int cpu, cpu_set_t *set); intCPU_ISSET_S(int cpu, size_t setsize, cpu_set_t *set);
// 计算 CPU 集的大小 voidCPU_COUNT(cpu_set_t *set); voidCPU_COUNT_S(size_t setsize, cpu_set_t *set);
// The following macros perform logical operations on CPU sets /* Store the logical AND of the sets srcset1 and srcset2 in destset (which may be one of the source sets). */ voidCPU_AND(cpu_set_t *destset, cpu_set_t *srcset1, cpu_set_t *srcset2); voidCPU_AND_S(size_t setsize, cpu_set_t *destset, cpu_set_t *srcset1, cpu_set_t *srcset2);
/* Store the logical OR of the sets srcset1 and srcset2 in destset (which may be one of the source sets). */ voidCPU_OR(cpu_set_t *destset, cpu_set_t *srcset1, cpu_set_t *srcset2); voidCPU_OR_S(size_t setsize, cpu_set_t *destset, cpu_set_t *srcset1, cpu_set_t *srcset2);
/* Store the logical XOR of the sets srcset1 and srcset2 in destset (which may be one of the source sets). */ voidCPU_XOR(cpu_set_t *destset, cpu_set_t *srcset1, cpu_set_t *srcset2); voidCPU_XOR_S(size_t setsize, cpu_set_t *destset, cpu_set_t *srcset1, cpu_set_t *srcset2);
/* Test whether two CPU set contain exactly the same CPUs. */ intCPU_EQUAL(cpu_set_t *set1, cpu_set_t *set2); intCPU_EQUAL_S(size_t setsize, cpu_set_t *set1, cpu_set_t *set2);
/* The following macros are used to allocate and deallocate CPU sets: */ /* Allocate a CPU set large enough to hold CPUs in the range 0 to num_cpus-1 */ cpu_set_t *CPU_ALLOC(int num_cpus);
/* Return the size in bytes of the CPU set that would be needed to hold CPUs in the range 0 to num_cpus-1. This macro provides the value that can be used for the setsize argument in the CPU_*_S() macros */ size_tCPU_ALLOC_SIZE(int num_cpus);
/* Free a CPU set previously allocated by CPU_ALLOC(). */ voidCPU_FREE(cpu_set_t *set);
/* API */ /*该函数设置进程为 pid 的这个进程,让它运行在 mask 所设定的 CPU 上。如果 pid 的值为 0, *则表示指定的是当前进程,使当前进程运行在 mask 所设定的那些 CPU 上. *第二个参数 cpusetsize 是 mask 所指定的数的长度。通常设定为 sizeof(cpu_set_t). *如果当前 pid 所指定的进程此时没有运行在 mask 所指定的任意一个 CPU 上, *则该指定的进程会从其它 CPU 上迁移到 mask 的指定的一个 CPU 上运行. */ intsched_setaffinity(pid_t pid, size_t cpusetsize, cpu_set_t *mask); /*该函数获得 pid 所指示的进程的 CPU 位掩码,并将该掩码返回到 mask 所指向的结构中. *即获得指定 pid 当前可以运行在哪些 CPU 上. *同样,如果 pid 的值为 0.也表示的是当前进程 */ intsched_getaffinity(pid_t pid, size_t cpusetsize, cpu_set_t *mask);
/* set CPU affinity attribute in thread attributes object */ intpthread_attr_setaffinity_np(pthread_attr_t *attr, size_t cpusetsize, constcpu_set_t *cpuset); /* get CPU affinity attribute in thread attributes object */ intpthread_attr_getaffinity_np(constpthread_attr_t *attr, size_t cpusetsize, cpu_set_t *cpuset);
/* set CPU affinity of a thread */ intpthread_setaffinity_np(pthread_t thread, size_t cpusetsize, constcpu_set_t *cpuset); /* get CPU affinity of a thread */ intpthread_getaffinity_np(pthread_t thread, size_t cpusetsize, cpu_set_t *cpuset);
#define handle_error_en(en, msg) \ do { errno = en; perror(msg); exit(EXIT_FAILURE); } while (0)
int main(int argc, char *argv[]) { int s, j; cpu_set_t cpuset; pthread_t thread;
thread = pthread_self();
/* Set affinity mask to include CPUs 0 to 7 */ CPU_ZERO(&cpuset); for (j = 0; j < 8; j++) CPU_SET(j, &cpuset);
s = pthread_setaffinity_np(thread, sizeof(cpu_set_t), &cpuset); if (s != 0) { handle_error_en(s, "pthread_setaffinity_np"); }
/* Check the actual affinity mask assigned to the thread */ s = pthread_getaffinity_np(thread, sizeof(cpu_set_t), &cpuset); if (s != 0) { handle_error_en(s, "pthread_getaffinity_np"); }
printf("Set returned by pthread_getaffinity_np() contained:\n"); for (j = 0; j < CPU_SETSIZE; j++) //CPU_SETSIZE 是定义在<sched.h>中的宏,通常是 1024 { if (CPU_ISSET(j, &cpuset)) { printf(" CPU %d\n", j); } } exit(EXIT_SUCCESS); }
1 2 3 4 5 6 7 8 9 10
➜ Affinity git:(main) ./bin/main Set returned by pthread_getaffinity_np() contained: CPU 0 CPU 1 CPU 2 CPU 3 CPU 4 CPU 5 CPU 6 CPU 7
计算机中,中断是一种电信号,由硬件产生并直接送到中断控制器上,再由中断控制器向 CPU 发送中断信号,CPU 检测到信号后,中断当前工作转而处理中断信号。CPU 会通知操作系统已经产生中断,操作系统就会对中断进行处理。 这里有篇推文:CPU 明明 8 个核,网卡为啥拼命折腾一号核?生动的解释了中断亲和性。
默认情况下,Linux 中断响应会被平均分配到所有 CPU 核心上,势必会发生写新的数据和指令缓存,并与 CPU 核心上原有进程产生冲突,造成中断响应延迟,影响进程处理时间。为了解决这个问题,可以将中断(或进程)绑定到指定 CPU 核心上,中断(或进程)所需要指令代码和数据有更大概率位于指定 CPU 本地数据和指令缓存内,而不必进行新的写缓存,从而提高中断响应(或进程)的处理速度。
中断亲和性的使用场景
对于文件服务器、Web 服务器,把不同的网卡 IRQ 均衡绑定到不同的 CPU 上将会减轻某 CP 的负载,提高多个 CPU 整体处理中断的能力; 对于数据库服务器,把磁盘控制器绑到一个 CPU、把网卡绑定到另一个 CPU 将会提高数据库的响应时间、优化性能。 合理的根据自己的生产环境和应用的特点来平衡 IRQ 中断有助于提高系统的整体吞吐能力和性能。
中断绑定流程
关闭中断平衡守护进程 中断平衡守护进程(irqbalance daemon)会周期性地将中断平均地公平地分配给各个 CPU 核心,默认开启。为了实现中断绑定,首先需要将中断平衡守护进程关闭。
systemctl status irqbalance查看守护进程的运行状态
1 2 3 4 5 6 7 8 9 10 11
➜ ~ systemctl status irqbalance
● irqbalance.service - irqbalance daemon Loaded: loaded (/lib/systemd/system/irqbalance.service; enabled; vendor preset: enable Active: active (running) since Thu 2022-05-19 14:46:20 CST; 1 weeks 1 days ago Main PID: 1062 (irqbalance) Tasks: 2 (limit: 4915) CGroup: /system.slice/irqbalance.service └─1062 /usr/sbin/irqbalance --foreground
5月 19 14:46:20 zdd systemd[1]: Started irqbalance daemon.
systemctl stop irqbalance关闭中断平衡守护进程,中断响应默认都会由 CPU0 核心处理。或者systemctl disable irqbalance取消中断平衡守护进程开机重启。因为关闭中断平衡守护进程过于强硬,可以在不关闭中断平衡守护进程条件下,让某些 CPU 核心脱离中断平衡守护进程的管理。
评论