CPU亲和性的使用与机制
CPU的亲和性, 就是进程要在指定的 CPU 上尽量长时间地运行而不被迁移到其他处理器,亲和性是从affinity翻译过来的,应该有点不准确,给人的感觉是亲和性就是有倾向的意思,而实际上是倒向的意思,称为CPU关联性更好,程序员的土话就是绑定CPU,绑核。
在多核运行的机器上,每个CPU本身自己会有缓存,缓存着进程使用的信息,而进程可能会被OS调度到其他CPU上,如此,CPU cache命中率就低了,当绑定CPU后,程序就会一直在指定的cpu跑,不会由操作系统调度到其他CPU上,性能有一定的提高。
另外一种使用绑核考虑就是将重要的业务进程隔离开,对于部分实时进程调度优先级高,可以将其绑定到一个指定核上,既可以保证实时进程的调度,也可以避免其他CPU上进程被该实时进程干扰。
1.CPU亲和性在用户态的使用
linux的CPU亲和性在用户态表现为一个cpu_set_t掩码的形式,用户可以调用两个函数设置和获取掩码:
#define _GNU_SOURCE /* See feature_test_macros(7) */ #include int sched_setaffinity(pid_t pid, size_t cpusetsize, cpu_set_t *mask); int sched_getaffinity(pid_t pid, size_t cpusetsize, cpu_set_t *mask);
sched_setaffinity是设置指定pid亲和性掩码的,mask是传入的参数;sched_getaffinity则是获取指定pid亲和性掩码的,mask是获取的参数。
cpusetsize可以通过sizeof cpu_set_t算出来。
cpu_set_t 是一个掩码数组,一共有1024位,每一位都可以对应一个cpu核心,以下宏,都是对这个掩码进行操作的。如果需要,一个进程是可以绑定多个cpu的。
void CPU_ZERO(cpu_set_t *set); void CPU_SET(int cpu, cpu_set_t *set); void CPU_CLR(int cpu, cpu_set_t *set);
而mask的表现是如此的:如果是0X23,转换成二进制则为00100011,则表明进程绑定在0核、1核和5核上。
绑核需要注意是,子进程会继承父进程的绑核关系。
代码实例:
#define _GNU_SOURCE #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sched.h> #include <pthread.h> #include <sys/syscall.h> #define gettid() syscall(__NR_gettid) void *test_thread(void *arg) { cpu_set_t mask; int loop = 0; int cpu_num = 0; cpu_num = sysconf(_SC_NPROCESSORS_CONF); pthread_detach(pthread_self()); CPU_ZERO(&mask); CPU_SET(1, &mask); if(sched_setaffinity(0, sizeof(mask), &mask) == -1) { printf("set affinity failedn"); } while(1) { CPU_ZERO(&mask); if(sched_getaffinity(0, sizeof(mask), &mask) == -1) { printf("get failedn"); } for(loop = 0; loop < cpu_num; loop++) { if(CPU_ISSET(loop, &mask)) { printf("test thread %lu run on processor %dn", gettid(), loop); } } sleep(1); } } void *child_thread(void *arg) { cpu_set_t mask; int loop = 0; int cpu_num = 0; cpu_num = sysconf(_SC_NPROCESSORS_CONF); pthread_detach(pthread_self()); while(1) { CPU_ZERO(&mask); if(sched_getaffinity(0, sizeof(mask), &mask) == -1) { printf("get failedn"); } for(loop = 0; loop < cpu_num; loop++) { if(CPU_ISSET(loop, &mask)) { printf("child thread %lu run on processor %dn", gettid(), loop); } } sleep(1); } } int main(int argc, char *argv[]) { int cpu_num = 0; pthread_t thread; int cpuid = 0; int ret = 0; int loop = 0; cpu_set_t mask_set; cpu_set_t mask_get; if(argc != 2) { printf("usage:cpu numn"); return -1; } cpuid = atoi(argv[1]); /* 获取系统CPU的个数 */ cpu_num = sysconf(_SC_NPROCESSORS_CONF); printf("system has %i processor.n", cpu_num); /* 初始化mask_set */ CPU_ZERO(&mask_set); CPU_SET(cpuid, &mask_set); if(sched_setaffinity(0, sizeof(mask_set), &mask_set) == -1) { printf("Warning:set cpu %d affinity failedn", cpuid); } ret = pthread_create(&thread, NULL, child_thread, NULL); if(ret) { printf("Error:pthread_create failedn"); return -1; } ret = pthread_create(&thread, NULL, test_thread, NULL); if(ret) { printf("Error:pthread_create failedn"); return -1; } while(1) { CPU_ZERO(&mask_get); if(sched_getaffinity(0, sizeof(mask_get), &mask_get) == -1) { printf("Warning:get cpu %d affinity failedn", cpuid); } for(loop = 0; loop < cpu_num; loop++) { if(CPU_ISSET(loop, &mask_get)) { printf("this processor %lu is running on processor: %dn", gettid(), loop); } } sleep(1); } return 0; }
执行之后根据打印和/proc stat的内容可以判断,status有
Cpus_allowed: 08
Cpus_allowed_list: 3
可以更清楚的看到进程绑核状态
但是如果进程已经在运行过程中,用户不能直接改动代码,就用taskset工具更改CPU亲和性关系。
taskset [options] -p [mask] pid
其中mask前面已说了,参看man手册更详细一点。
二、CPU亲和性在内核态机制
在内核进程结构体task_struct里面有一个参数,即为
cpumask_t cpus_allowed;
用来记住CPU的绑核关系。
内核尤其是调度的时候,可以保证让task不会被调度到其他CPU上
static inline int select_task_rq(struct task_struct *p, int sd_flags, int wake_flags) { int cpu = p->sched_class->select_task_rq(p, sd_flags, wake_flags); /* * In order not to call set_task_cpu() on a blocking task we need * to rely on ttwu() to place the task on a valid ->cpus_allowed * cpu. * * Since this is common to all placement strategies, this lives here. * * [ this allows ->select_task() to simply return task_cpu(p) and * not worry about this generic constraint ] */if (unlikely(!cpumask_test_cpu(cpu, &p->cpus_allowed) || !cpu_online(cpu))) cpu = select_fallback_rq(task_cpu(p), p); return cpu; }
进程在选择CPU队列的时候,只选择被允许的CPU队列,使用cpumask_test_cpu进行测试。
CPU亲和性的使用与机制来自于OenHan
链接为:https://oenhan.com/cpu-affinity