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

发表回复