进程/线程绑定到特定CPU核的linux实现(有代码有实例)

前言

现在计算机上的CPU大多都是多核的,有4核甚至是8核的。但是一个计算机启动之后其进程数是远远多于CPU核数的,因为操作系统会给自动调度这些进程在CPU核上轮流运行。但是对于应用程序或者进程,其性能要求较高时,可能有必要绑定该进程到指定的CPU核来运行,避免调度带来的额外开销。我自己也是因为最近的项目上有需要进程运行在指定的CPU核上的要求,所以了解了一下这项技术,并且将过程和总结记录于此。

CPU亲和性

在学习这项新技术之前,我们先来了解一下什么是CPU亲和性?所谓亲和性,就是把进程在指定的 CPU 上尽量长时间地运行而不被迁移到其他处理器,也称为CPU关联性;再简单的点的描述就将制定的进程或线程绑定到相应的cpu上;在多核运行的机器上,每个CPU本身自己会有缓存,缓存着进程使用的信息,而进程可能会被OS调度到其他CPU上,如此,CPU cache命中率就低了,当绑定CPU后,程序就会一直在指定的cpu跑,不会由操作系统调度到其他CPU上,性能有一定的提高。

预备知识

在编写测试程序之前,我们先来了解一下CPU相关的宏和函数。
1.首先要想使用CPU系列函数及相关的宏,需要声明下面的宏,以告诉编译器启用这些函数

#define _GNU_SOURCE

2.声明一个cpu_set_t,然后用 CPU_ZERO()宏来初始化数据:

cpu_set_t mask;
CPU_ZERO(&mask);

3.再下来就是要指定绑定的CPU核心,用CPU_SET()宏来指定,例如:

CPU_SET(1, &mask);//绑定CPU核心1

4.下面这个函数是进程绑定CPU核心最关键函数,也是实际绑定CPU核的操作。其原型即参数说明如下:

int sched_setaffinity(pid_t pid, size_t cpusetsize, cpu_set_t *mask);

该函数设置进程为pid的这个进程,让它运行在mask所设定的CPU上.如果pid的值为0,则表示指定的是当前进程,使当前进程运行在mask所设定的那些CPU上.第二个参数cpusetsize是mask所指定的数的长度.通常设定为sizeof(cpu_set_t).如果当前pid所指定的进程此时没有运行在mask所指定的任意一个CPU上,则该指定的进程会从其它CPU上迁移到mask的指定的一个CPU上运行。
用法:

sched_setaffinity(0, sizeof(cpu_set_t), &mask);//设置完mask之后,通过该函数完成实际绑定功能

5.补充:通过上面的4步就可以完成进程绑定CPU核心了,即被绑定的进程会一直运行在指定的CPU核上,不会被操作系统调度走。在这里,我们再介绍一下另外两个宏:

 void CPU_CLR (int cpu, cpu_set_t *set)

这个宏将 指定的 cpu 从 CPU 集 set 中删除。

int CPU_ISSET (int cpu, const cpu_set_t *set)

检查 cpu 是否是 CPU 集 set 的一员,如果在CPU集中这个宏就返回一个非零值(true),否则就返回零(false)。
还有一个函数其功能是获取当前进程运行在哪一个CPU核:

int sched_getcpu();

测试程序

接下来,我们就用一个测试程序来看一下进程是否被绑定在一个CPU核上。

#define _GNU_SOURCE
#include <sched.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main()
{
    cpu_set_t mask;
    CPU_ZERO(&mask);
    CPU_SET(1, &mask);//把main进程绑定到CPU1
    sched_setaffinity(0, sizeof(cpu_set_t), &mask);//实际的绑定函数
    printf("cpu = %d\n",sched_getcpu());//打印当前运行的CPU核,看是否绑定成功
    int i,j,k;
    for(i = 0;i < 40;i++)
    {//循环40亿次
        printf("i = %d\n",i);
        for(j = 0;j < 10000;j++)
        {
            for(k = 0;k < 10000;k++)
            {
                
                if(sched_getcpu() != 1)//如果当前CPU核不是1,说明被操作系统给调度走了,绑定失败!并且打印当前运行的CPU核
                {
                    printf("main process: cpu = %d\n",sched_getcpu());
                }
            }
        }
    }
    printf("main end\n");
    return 0;
}

编译测试程序:g++ cpu.cpp -o cpu
运行程序:./cpu
结果如下:

在这里插入图片描述
在这里插入图片描述
可以看到,打印的CPU核正是我们绑定的CPU,并且没有打印程序中测试绑定失败的相关log,说明循环了40亿次,main进程一直都在CPU1上运行的。

进阶测试

上面的测试,我们只是做到了被绑定的进程确实运行在了指定的CPU上,但是期间是否有其他进程被操作系统调度进来运行呢?被绑定的进程是否独享指定的CPU呢?现在我们就通过测试程序来测试看看。这里我们还是需要上面刚写的测试程序来辅助测试,这里暂且称上面的程序为测试程序1吧。下面我们要写第二个测试程序,其功能就是fork5个子进程,并且对这5个子进程不做绑定,任由操作系统默认调度,测试程序2如下:

#define _GNU_SOURCE
#include <sched.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

void run(int c, int n,int cpu) {
    int lastcpu = cpu;
    // if(c == 0)
    // {
    //     cpu_set_t mask;
    //     CPU_ZERO(&mask);
    //     CPU_SET(n, &mask);
    //     sched_setaffinity(0, sizeof(cpu_set_t), &mask);
    //     printf("c= %d,cpu = %d\n",c,sched_getcpu());
    // }
    int i,j,k;
    for (i = 0; i != 60; i++) //循环60亿次
    {
        printf("c = %d,i = %d\n",c,i);
	    for(j = 0; j != 10000; j++)
        {
            for(k = 0;k != 10000;k++)
            {
                if(sched_getcpu() != lastcpu)//如果当前的CPU核和上次的CPU核不一样,说明该进程被调度了,这样只要进程发生了调度我们就可以知道该进程是从CPU的哪个核调度到哪个核
		        {
			        printf("c = %d,i = %d,lastcpu = %d, nowcpu = %d\n",c,i,lastcpu,sched_getcpu());
			        lastcpu = sched_getcpu();  //更新lastcpu的值               
		        }
            }
		    
	    }   
        
    }
	printf("c = %d ,i = %d, j = %d,is end\n",c,i,j);
}

int main()
{
    int i,initcpu;
    for (i = 0; i != 5; i++) {//循环5次,产生了5个子进程
        int pid = fork();
        if (pid == 0) {//fork返回值为0表明是子进程,if判断里面只有子进程会跑
		initcpu = sched_getcpu();//获取各个进程被分配到哪个CPU核
	printf("i = %d,initcpu = %d\n",i,initcpu);
        run(i, i,initcpu);//每一个子进程都会跑run函数
        exit(0);
        }
    }
    return 0;
}

好了,现在我们有一个绑定了CPU1的测试程序1,以及任由操作系统调度的测试程序2.
测试方法就是先执行测试程序1,目的是让测试程序1绑定CPU1,然后再运行测试程序2,由于测试程序2中的子进程都是操作系统默认调度的,所以可以看看测试程序1还在运行期间,测试程序2中的进程是否会被调度到CPU1上。
执行结果:
在这里插入图片描述
在这里插入图片描述
由于运行结果log较多,这里不一一上传,但是我们从上面的结果中可以看到,在测试程序1执行期间,测试程序2中的这些被系统自动调度的进程并没有一个进程被调度到CPU1上,直到程序1执行完了之后测试程序2的进程才被调度到CPU1上。
在这里插入图片描述

线程绑定CPU

线程绑定CPU的函数如下:

int pthread_setaffinity_np(pthread_t thread, size_t cpusetsize,                              const cpu_set_t *cpuset);

绑定线程与进程的过程和方法都类似,这里就不详细介绍了,读者可以在了解了进程绑定之后再亲自尝试一下线程绑定。

总结

经过上面测试程序的测试我们可以得出以下结论:
1.经过sched_setaffinity函数绑定了CPU之后,进程在其运行期间是独享该核心的,其他被操作系统自动调度的进程不会在该CPU核心上运行。
2.只有 在被绑定的进程运行结束之后,该核心就会被释放,供操作系统自动调度。

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值