linux下绑定任务到特定的CPU

一、linux c查看cpu核

1、命令行查看cpu有几个核
cat /proc/cpuinfo |grep processor | wc -l

nproc
2、linux c代码查看
#include <unistd.h>
int sysconf(_SC_NPROCESSORS_CONF);/* 返回系统可以使用的核数,但是其值会包括系统中禁用的核的数目,因此该值并不代表当前系统中可用的核数 */
int sysconf(_SC_NPROCESSORS_ONLN);/* 返回值真正的代表了系统当前可用的核数 */
/* 以下两个函数与上述类似 */
#include <sys/sysinfo.h>
int get_nprocs_conf (void);/* 可用核数,其值会包括系统中禁用的核的数目 */
int get_nprocs (void);/* 真正的反映了当前可用核数 */

二、设置任务绑定特定的CPU核

1、cpu_set_t ---- CPU集合

cpu_set_t用来描述CPU的集合,被sched_setaffinity等类似的函数使用。

1.1用法及例子
cpu_set_t set1, set2;
CPU_ZERO(&set1); //清空集合,即set1里不包含任何CPU,本质为所有bit清零
CPU_ZERO(&set2); //清空集合,即set2里不包含任何CPU,本质为所有bit清零
CPU_SET(0, &set1); //将cpu0添加到集合set1中,本质为对应bit置1
CPU_SET(1, &set2); //将cpu1添加到集合set2中,本质为对应bit置1
CPU_CLR(0, &set1); //将cpu0从集合set1中移除,本质为对应bit清零
int ret = CPU_ISSET(1, &set2); //判断cpu1是否在集合set2中,在返回非零,不在返回0
int cnt = CPU_COUNT(&set2); //返回集合set2中的CPU的个数
cpu_set_t result;
CPU_AND(&result, &set1, &set2); //set1和set2的所有bit按位与,结果存入result
CPU_OR(); //按位或
CPU_XOR(); //按位异或
ret = CPU_EQUAL(&set1, &set2); //集合set1和集合set2相等的话,ret为非零,不相等,ret为0

用法举例:

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

int main(int argc, char *argv[])
{
    cpu_set_t *cpusetp;
    size_t size;
    int num_cpus, cpu;

    if (argc < 2) {
        fprintf(stderr, "Usage: %s <num-cpus>\n", argv[0]);
        exit(EXIT_FAILURE);
    }   

    num_cpus = atoi(argv[1]);
    
    /* 申请一个能够存放num_cpus个CPU的集合 */
    cpusetp = CPU_ALLOC(num_cpus);
    if (cpusetp == NULL) {
        perror("CPU_ALLOC");
        exit(EXIT_FAILURE);
    }   

    /* 获取能存放num_cpus个CPU的集合的大小(单位字节) */
    size = CPU_ALLOC_SIZE(num_cpus);

    /* 清零cpusetp */
    CPU_ZERO_S(size, cpusetp);
    for (cpu = 0; cpu < num_cpus; cpu += 2)
        CPU_SET_S(cpu, size, cpusetp); //将cpu0,cpu2,... 添加到cpusetp集合

    printf("CPU_COUNT() of set:    %d\n", CPU_COUNT_S(size, cpusetp));

    /* 释放cpusetp空间 */
    CPU_FREE(cpusetp);
    exit(EXIT_SUCCESS);
}
2、绑定任务到指定CPU

CPU亲和性只是一种倾向性,当绑定的CPU不存在或者存在但是被禁用了,任务会在其他的CPU上执行
设置任务亲和性的接口有:

sched_setaffinity:修改指定pid_t的任务的亲和性
pthread_setaffinity_np:gnu接口,修改指定pthrad_t的任务的亲和性。
pthread_attr_setaffinity_np:gnu接口。创建线程前,通过线程属性结构体控制新线程的亲和性。
注:np的意思为不可移植,即在非gnu的系统上,应该是没有这个接口的。

2.1 sched_setaffinity

如果考虑可移植性的话,推荐使用sched_setaffinity()函数将任务绑定到特定CPU执行。
但是sched_setaffinity函数的不方便之处在于,无法给线程指定亲和性,要用sched_setaffinity给线程指定亲和性,比较麻烦,需要使用到不可移植的函数gettid()。
test.c:使用sched_setaffinity修改线程的CPU亲和性。主要逻辑为,主线程设置亲和性为cpu0,启动两个新线程th1和th2,此时从htop可以看到,主线程阻塞,th1和th2交替运行在cpu0上。然后过一段时间后,线程函数自己修改自己的亲和性(th1将自己的亲和性设置为cpu1,th2将自己的亲和性设置为cpu2),此时从htop可以看到,th1到cpu1上运行,th2到cpu2上运行。
test.c:

/* 该代码只是为了验证亲和性,故多数函数均没有检查返回值 */
#define _GNU_SOURCE
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <sys/sysinfo.h>
#include <sched.h>
#include <string.h>
#include <sys/syscall.h>   /* For SYS_xxx definitions */

void *func(void *arg)
{
  /* 用来计时 */
  int count = 0;
  pthread_setname_np(pthread_self(), (char *)arg);
  while(1)
  {
    char name[128] = {};
    printf("%s\n", (char *)arg);
    /* 死循环耗时,避免打印过快,不同的机器可能需要调整循环次数 */
    for(int i=0; i<1000000000; i++);
    count++;
    /* count小于15时,线程都运行在cpu0上;等于15时,线程会设置自身的亲和性,导致后续时间,亲和性就不再是cpu0了 */
    if(count == 15)
    {
      pid_t tid;
      cpu_set_t set;

      CPU_ZERO(&set);
      /* 获取本线程的pid_t类型的ID号,因为sched_setaffinity的参数类型为pid_t */
      tid = syscall(SYS_gettid);
      if(!strncmp((char *)arg, "th1", strlen("th1")+1))
      {
        CPU_SET(1, &set);
        sched_setaffinity(tid, sizeof(cpu_set_t), &set);
      }
      else
      {
        CPU_SET(2, &set);
        sched_setaffinity(tid, sizeof(cpu_set_t), &set);
      }
    }
  }
  return NULL;
}

int main(void)
{
  pthread_t th1, th2;
  cpu_set_t set;

  CPU_ZERO(&set);
  CPU_SET(0, &set);

  /* 设置主线程亲和性为cpu0,这样的话,默认新线程亲和性也是cpu0 */
  int ret = sched_setaffinity(getpid(), sizeof(cpu_set_t), &set);
  if (ret)
  {
    perror("sched_setaffinity");
    return -1;
  }

  pthread_create(&th1, NULL, func, "th1");
  pthread_create(&th2, NULL, func, "th2");

  pthread_join(th1, NULL);
  pthread_join(th2, NULL);

  return 0;
}
2.2 pthread_setaffinity_np

如果要设置已存在的线程的亲和性,就可以考虑使用pthread_setaffinity_np,尽管pthread_setaffinity_np是不可移植的,是gnu独有的。因为就算是考虑到移植性,想要使用sched_setaffinity,也避免不了要使用gettid函数(该函数也是gnu独有)。与使用sched_setaffinity设置线程亲和性相比,使用pthread_setaffinity_np的另一个好处就是,可以在线程外设置任一线程的亲和性(只要知道pthread_t即可),而sched_setaffinity的gettid,需要在线程函数内调用。
test.c:使用pthread_setaffinity_np,在main函数中,对两个子线程设置其亲和性。前10s亲和性都追随主函数(cpu0),10s后使用pthread_setaffinity_np设置th1亲和性为cpu1,th2亲和性为cpu2,结果见htop截图。

/* 该代码只是为了验证亲和性,故多数函数均没有检查返回值 */
#define _GNU_SOURCE
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <sys/sysinfo.h>
#include <sched.h>
#include <string.h>
#include <sys/syscall.h>   /* For SYS_xxx definitions */

void *func(void *arg)
{
  pthread_setname_np(pthread_self(), (char *)arg);
  while(1)
  {
    char name[128] = {};
    printf("%s\n", (char *)arg);
    /* 死循环耗时,避免打印过快,不同的机器可能需要调整循环次数 */
    for(int i=0; i<1000000000; i++);
  }
  return NULL;
}

int main(void)
{
  pthread_t th1, th2;
  cpu_set_t set;

  CPU_ZERO(&set);
  CPU_SET(0, &set);

  /* 设置主线程亲和性为cpu0,这样的话,默认新线程亲和性也是cpu0 */
  int ret = sched_setaffinity(getpid(), sizeof(cpu_set_t), &set);
  if (ret)
  {
    perror("sched_setaffinity");
    return -1;
  }

  pthread_create(&th1, NULL, func, "th1");
  pthread_create(&th2, NULL, func, "th2");

  /* 前10s,亲和性都是cpu0 */
  sleep(10);
  /* 10s过后,亲和性被修改 */

  /* 移除CPU集合中的cpu0,此时集合中没有任何CPU */
  CPU_CLR(0, &set);
  /* 增加cpu1,此时集合中只有cpu1 */
  CPU_SET(1, &set);
  /* 设置th1的亲和性为cpu1 */
  pthread_setaffinity_np(th1, sizeof(cpu_set_t), &set);

  /* 移除集合中的cpu1,此时集合中没有任何CPU */
  CPU_CLR(1, &set);
  /* 增加cpu2,此时集合中只有cpu2 */
  CPU_SET(2, &set);
  /* 设置th2的亲和性为cpu2 */
  pthread_setaffinity_np(th2, sizeof(cpu_set_t), &set);

  pthread_join(th1, NULL);
  pthread_join(th2, NULL);

  return 0;
}
2.3 pthread_attr_setaffinity_np

创建新线程时,通过属性结构体,控制新线程的亲和性。
test.c:

/* 该代码只是为了验证亲和性,故多数函数均没有检查返回值 */
#define _GNU_SOURCE
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <sys/sysinfo.h>
#include <sched.h>
#include <string.h>
#include <sys/syscall.h>   /* For SYS_xxx definitions */

void *func(void *arg)
{
  pthread_setname_np(pthread_self(), (char *)arg);
  while(1)
  {
    char name[128] = {};
    printf("%s\n", (char *)arg);
    /* 死循环耗时,避免打印过快,不同的机器可能需要调整循环次数 */
    for(int i=0; i<1000000000; i++);
  }
  return NULL;
}

int main(void)
{
  pthread_t th1;
  cpu_set_t set;
  pthread_attr_t attr;

  CPU_ZERO(&set);
  CPU_SET(0, &set);

  /* 设置主线程亲和性为cpu0,这样的话,默认新线程亲和性也应该是cpu0 */
  /* 但是由于新线程创建的时候,使用属性指定了亲和性,就会发现新线程启动之后直接
   * 就在指定的cpu运行了 */
  int ret = sched_setaffinity(getpid(), sizeof(cpu_set_t), &set);
  if (ret)
  {
    perror("sched_setaffinity");
    return -1;
  }

  pthread_attr_init(&attr);
  CPU_ZERO(&set);
  CPU_SET(1, &set);
  pthread_attr_setaffinity_np(&attr, sizeof(cpu_set_t), &set);

  pthread_create(&th1, &attr, func, "th1");

  pthread_join(th1, NULL);

  return 0;
}
2.4主线程和线程分开设置亲和性
#define _GNU_SOURCE   //代表使用GNU,pthread_setaffinity_np是不可移植的,是gnu独有的。
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <string.h>
#include <pthread.h>
#include <sched.h>

static int g_processCpuNum = 0;         // 进程占用的CPU核

/** 线程读取数据 */
void *read_data(void *args)
{
	// 配置线程绑定的CPU核
    cpu_set_t mask;
    CPU_ZERO(&mask);
    CPU_SET(g_processCpuNum + 1, &mask);    // 线程和进程用不同的CPU核
    if (pthread_setaffinity_np(pthread_self(), sizeof(mask), &mask) < 0)
    {
        return;
    }
	
	 // 线程循环
    while(1)
    {
    	..............
    }
}

// 获取调度策略
int get_thread_policy(pthread_attr_t *attr)
{
    int policy;
    int rs = pthread_attr_getschedpolicy(attr, &policy);
    switch (policy)
    {
        case SCHED_FIFO:
            printf("policy= SCHED_FIFO\n");
            break;
        case SCHED_RR:
            printf("policy= SCHED_RR");
            break;
        case SCHED_OTHER:
            printf("policy=SCHED_OTHER\n");
            break;
        default:
            printf("policy=UNKNOWN\n");
            break;
    }
    return policy;
}

/** 主函数 */
int main( int argc, char **argv )
{
	 int interruptCpuNum = 2;	//中断绑定CPU核心号
    int interrruptNum = 6;   //中断号

	// 绑定中断CPU核,暂时采用固定中断号的形式,也可以通过系统获取中断号
    if(interruptCpuNum && interrruptNum)
    {
        sprintf(sysCmd, "echo %d > /proc/irq/%d/smp_affinity", 1 << interruptCpuNum, interrruptNum);//通过命令形式绑定
        system(sysCmd);
    }

	// 绑定进程的CPU核
    if(!g_processCpuNum)
    {
        CPU_ZERO(&set);   //清空CPU集合
        CPU_SET(g_processCpuNum, &set);  //向集合中添加CPU核,这个cpu核指参数输入的整数,比如输入了2,代表了cpu2
        if (sched_setaffinity(getpid(), sizeof(set), &set) == -1)   /* 设置主线程的亲和性,子线程会继承父线程的亲和性设置(见man pthread_create) */
        {
            printf("parent sched_setaffinity error\n");
            return -1;
        }
    }
	// 创建线程读取数据,并分配线程优先级
    pthread_t pid;				//线程标识		
    pthread_attr_t attr;   //线程属性
    struct sched_param param;
    pthread_attr_init(&attr);
#if 1
    pthread_attr_setinheritsched(&attr, PTHREAD_EXPLICIT_SCHED);                // 线程不继承父线程优先级,即线程也不继承进程的亲和性
    pthread_attr_setschedpolicy(&attr, SCHED_FIFO);                             // 设置调度策略为SCHED_FIFO
    param.sched_priority = sched_get_priority_max(get_thread_policy(&attr));    // 配置优先级最高
    pthread_attr_setschedparam(&attr, &param);
#endif
    ret = pthread_create(&pid, &attr, read_data, (void*)&handle);
    if(ret < 0)
    {
        printf("pthread create failed!\n");
        return -1;
    }

	// 主循环
    while(run)
    {
    	.........
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值