Linux中CPU亲和性

Linux中CPU亲和性

超线程技术(Hyper-Threading):就是利用特殊的硬件指令,把两个逻辑内核(CPU core)模拟成两个物理芯片,让单个处理器都能使用线程级并行计算,进而兼容多线程操作系统和软件,减少了CPU的闲置时间,提高的CPU的运行效率。我们常听到的双核四线程/四核八线程指的就是支持超线程技术的CPU

物理CPU:机器上安装的实际CPU, 比如说你的主板上安装了一个8核CPU,那么物理CPU个数就是1个,所以物理CPU个数就是主板上安装的CPU个数

逻辑CPU:一般情况,我们认为一颗CPU可以有多核,加上intel的超线程技术(HT), 可以在逻辑上再分一倍数量的CPU core出来

绑定任务到特定CPU(CPU亲和性)

Linux下查看CPU相关信息, CPU的信息主要都在/proc/cupinfo中

shell 复制代码
# 查看物理CPU个数
cat /proc/cpuinfo|grep "physical id"|sort -u|wc -l

# 查看每个物理CPU中core的个数(即核数)
cat /proc/cpuinfo|grep "cpu cores"|uniq

# 查看逻辑CPU的个数
cat /proc/cpuinfo|grep "processor"|wc -l

# 查看CPU的名称型号
cat /proc/cpuinfo|grep "name"|cut -f2 -d:|uniq

Linux查看某个进程运行在哪个逻辑CPU上

shell 复制代码
ps -eo pid,args,psr
#参数的含义:
pid  - 进程ID
args - 该进程执行时传入的命令行参数
psr  - 分配给进程的逻辑CPU

例子:
[~]# ps -eo pid,args,psr | grep nginx
9073 nginx: master process /usr/   1
9074 nginx: worker process         0
9075 nginx: worker process         1
9076 nginx: worker process         2
9077 nginx: worker process         3
13857 grep nginx                   3

Linux查看线程的TID

TID就是Thread ID,他和POSIX中pthread_t表示的线程ID完全不是同一个东西.

Linux中的POSIX线程库实现的线程其实也是一个轻量级进程(LWP),这个TID就是这个线程的真实PID.

但是又不能通过getpid()函数获取,Linux中定义了gettid()这个接口,但是通常都是未实现的,所以需要使用下面的方式获取TID。

shell 复制代码
//program
#include <sys/syscall.h>  
pid_t tid;
tid = syscall(__NR_gettid);// or syscall(SYS_gettid)  

//command-line 3种方法(推荐第三种方法)
(1)ps -efL | grep prog_name
(2)ls /proc/pid/task            //文件夹名即TID
(3)ps -To 'pid,lwp,psr,cmd' -p PID

基本概念

CPU affinity 是一种调度属性(scheduler property), 它可以将一个进程"绑定" 到一个或一组CPU上.

在SMP(Symmetric Multi-Processing对称多处理)架构下,Linux调度器(scheduler)会根据CPU affinity的设置让指定的进程运行在"绑定"的CPU上,而不会在别的CPU上运行.

Linux调度器同样支持自然CPU亲和性(natural CPU affinity): 调度器会试图保持进程在相同的CPU上运行, 这意味着进程通常不会在处理器之间频繁迁移,进程迁移的频率小就意味着产生的负载小。

因为程序的作者比调度器更了解程序,所以我们可以手动地为其分配CPU核,而不会过多地占用CPU0,或是让我们关键进程和一堆别的进程挤在一起,所有设置CPU亲和性可以使某些程序提高性能。

表示方法

CPU affinity 使用位掩码(bitmask)表示, 每一位都表示一个CPU, 置1表示"绑定".

最低位表示第一个逻辑CPU, 最高位表示最后一个逻辑CPU.

CPU affinity典型的表示方法是使用16进制,具体如下.

0x00000001
    is processor #0

0x00000003
    is processors #0 and #1

0xFFFFFFFF
    is all processors (#0 through #31)

taskset命令

taskset命名用于获取或者设定CPU亲和性

shell 复制代码
# 命令行形式
taskset [options] mask command [arg]...
taskset [options] -p [mask] pid

PARAMETER
    mask : cpu亲和性,当没有-c选项时, 其值前无论有没有0x标记都是16进制的,
        当有-c选项时,其值是十进制的.
    command : 命令或者可执行程序
    arg : command的参数
    pid : 进程ID,可以通过ps/top/pidof等命令获取


OPTIONS
    -a, --all-tasks (旧版本中没有这个选项)
        这个选项涉及到了linux中TID的概念,他会将一个进程中所有的TID都执行一次CPU亲和性设置.
        TID就是Thread ID,他和POSIX中pthread_t表示的线程ID完全不是同一个东西.
        Linux中的POSIX线程库实现的线程其实也是一个进程(LWP),这个TID就是这个线程的真实PID.
       -p, --pid
              操作已存在的PID,而不是加载一个新的程序
       -c, --cpu-list
              声明CPU的亲和力使用数字表示而不是用位掩码表示. 例如 0,5,7,9-11.
       -h, --help
              display usage information and exit
       -V, --version
              output version information and exit
  USAGE

    1) 使用指定的CPU亲和性运行一个新程序

      taskset [-c] mask command [arg]...

        举例:使用CPU0运行ls命令显示/etc/init.d下的所有内容 

          taskset -c 0 ls -al /etc/init.d/

    2) 显示已经运行的进程的CPU亲和性

      taskset -p pid

        举例:查看init进程(PID=1)的CPU亲和性

          taskset -p 1

    3) 改变已经运行进程的CPU亲和力

        taskset -p[c] mask pid

        举例:打开2个终端,在第一个终端运行top命令,第二个终端中

          首先运行:[~]# ps -eo pid,args,psr | grep top #获取top命令的pid和其所运行的CPU号

          其次运行:[~]# taskset -cp 新的CPU号 pid       #更改top命令运行的CPU号

          最后运行:[~]# ps -eo pid,args,psr | grep top #查看是否更改成功

  PERMISSIONS
        一个用户要设定一个进程的CPU亲和性,如果目标进程是该用户的,则可以设置,如果是其他用户的,则会设置失败,提示 Operation not permitted.当然root用户没有任何限制.

        任何用户都可以获取任意一个进程的CPU亲和性.

编程API

下面是用用于设置和获取CPU亲和性相关的API.

c 复制代码
#define _GNU_SOURCE
#include <sched.h>
#include <pthread.h> //for pthread functions(last 4) 注意<pthread.h>包含<sched.h>

/* MACRO */
    /* The following macros are provided to operate on the CPU set set */
        /* Clears set, so that it contains no CPUs */
        void CPU_ZERO(cpu_set_t *set);
        void CPU_ZERO_S(size_t setsize, cpu_set_t *set);
        
        /* Add CPU cpu to set */
        void CPU_SET(int cpu, cpu_set_t *set);
        void CPU_SET_S(int cpu, size_t setsize, cpu_set_t *set);
        
        /* Remove CPU cpu from set */
        void CPU_CLR(int cpu, cpu_set_t *set);
        void CPU_CLR_S(int cpu, size_t setsize, cpu_set_t *set);
        
        /* Test to see if CPU cpu is a member of set */
        int CPU_ISSET(int cpu, cpu_set_t *set);
        int CPU_ISSET_S(int cpu, size_t setsize, cpu_set_t *set);
        
        /* Return the number of CPUs in set */
        void CPU_COUNT(cpu_set_t *set);
        void CPU_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). */
        void CPU_AND(cpu_set_t *destset, cpu_set_t *srcset1, cpu_set_t *srcset2);
        void CPU_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). */
        void CPU_OR(cpu_set_t *destset, cpu_set_t *srcset1, cpu_set_t *srcset2);
        void CPU_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). */
        void CPU_XOR(cpu_set_t *destset, cpu_set_t *srcset1, cpu_set_t *srcset2);
        void CPU_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. */
        int CPU_EQUAL(cpu_set_t *set1, cpu_set_t *set2);
        int CPU_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_t CPU_ALLOC_SIZE(int num_cpus);
        
        /* Free a CPU set previously allocated by CPU_ALLOC(). */
        void CPU_FREE(cpu_set_t *set);

/* API */
    /* Set the CPU affinity for a task */
    int sched_setaffinity(pid_t pid, size_t cpusetsize, cpu_set_t *mask);
    /* Get the CPU affinity for a task */
    int sched_getaffinity(pid_t pid, size_t cpusetsize, cpu_set_t *mask);
    
    /* set CPU affinity attribute in thread attributes object */
    int pthread_attr_setaffinity_np(pthread_attr_t *attr, size_t cpusetsize, const cpu_set_t *cpuset);
    /* get CPU affinity attribute in thread attributes object */
    int pthread_attr_getaffinity_np(const pthread_attr_t *attr, size_t cpusetsize, cpu_set_t *cpuset);
    
    /* set CPU affinity of a thread */
    int pthread_setaffinity_np(pthread_t thread, size_t cpusetsize, const cpu_set_t *cpuset);
    /* get CPU affinity of a thread */
    int pthread_getaffinity_np(pthread_t thread, size_t cpusetsize, cpu_set_t *cpuset);

相关的宏通常都分为2种,一种是带_S后缀的,一种不是不带_S后缀的, 从声明上看带_S后缀的宏都多出一个参数 setsize.

从功能上看他们的区别是带_S后缀的宏是用于操作动态申请的CPU set(s),所谓的动态申请其实就是使用宏 CPU_ALLOC 申请,

参数setsize 可以是通过宏 CPU_ALLOC_SIZE 获得,两者的用法详见下面的例子.

相关的API只有6个, 前2个是用来设置进程的CPU亲和性,需要注意的一点是,当这2个API的第一个参数pid为0时,表示使用调用进程的进程ID;

后4个是用来设置线程的CPU亲和性。其实sched_setaffinity()也可以用来设置线程的CPU的亲和性,也就是taskset "-a"选项中提到的TID概念。

例子一:使用2种方式(带和不带_S后缀的宏)获取当前进程的CPU亲和性

c 复制代码
#define _GNU_SOURCE
#include <sched.h>
#include <unistd.h> /* sysconf */
#include <stdlib.h> /* exit */
#include <stdio.h>

int main(void)
{
    int i, nrcpus;
    cpu_set_t mask;
    unsigned long bitmask = 0;
    
    CPU_ZERO(&mask);
    
     /* Get the CPU affinity for a pid */
    if (sched_getaffinity(0, sizeof(cpu_set_t), &mask) == -1) 
    {   
        perror("sched_getaffinity");
        exit(EXIT_FAILURE);
    }
    
       /* get logical cpu number */
    nrcpus = sysconf(_SC_NPROCESSORS_CONF);
    
    for (i = 0; i < nrcpus; i++)
    {
        if (CPU_ISSET(i, &mask))
        {
            bitmask |= (unsigned long)0x01 << i;
            printf("processor #%d is set\n", i); 
        }
    }
    printf("bitmask = %#lx\n", bitmask);

    exit(EXIT_SUCCESS);
}
/*----------------------------------------------------------------*/
#define _GNU_SOURCE
#include <sched.h>
#include <unistd.h> /* sysconf */
#include <stdlib.h> /* exit */
#include <stdio.h>

int main(void)
{
    int i, nrcpus;
    cpu_set_t *pmask;
    size_t cpusize;
    unsigned long bitmask = 0;

    /* get logical cpu number */
    nrcpus = sysconf(_SC_NPROCESSORS_CONF);
    
    pmask = CPU_ALLOC(nrcpus);
    cpusize = CPU_ALLOC_SIZE(nrcpus);
    CPU_ZERO_S(cpusize, pmask);
    
    /* Get the CPU affinity for a pid */
    if (sched_getaffinity(0, cpusize, pmask) == -1) 
    {
        perror("sched_getaffinity");
        CPU_FREE(pmask);
        exit(EXIT_FAILURE);
    }
    for (i = 0; i < nrcpus; i++)
    {
        if (CPU_ISSET_S(i, cpusize, pmask))
        {
             bitmask |= (unsigned long)0x01 << i;
            printf("processor #%d is set\n", i); 
        }
    }
      printf("bitmask = %#lx\n", bitmask);
      
    CPU_FREE(pmask);
    exit(EXIT_SUCCESS);
}

执行结果如下(4核CPU):

shell 复制代码
[cpu_affinity #1]$ gcc -g -Wall cpu_affinity.c
[cpu_affinity #2]$ taskset 1 ./a.out 
processor #0 is set
bitmask = 0x1
[cpu_affinity #3]$ taskset 1 ./a.out 
processor #0 is set
bitmask = 0x1
[cpu_affinity #4]$ taskset 2 ./a.out 
processor #1 is set
bitmask = 0x2
[cpu_affinity #5]$ taskset 3 ./a.out 
processor #0 is set
processor #1 is set
bitmask = 0x3
[cpu_affinity #6]$ taskset 4 ./a.out 
processor #2 is set
bitmask = 0x4
[cpu_affinity #7]$ taskset 5 ./a.out 
processor #0 is set
processor #2 is set
bitmask = 0x5
[cpu_affinity #8]$ taskset 6 ./a.out 
processor #1 is set
processor #2 is set
bitmask = 0x6
[cpu_affinity #9]$ taskset 7 ./a.out 
processor #0 is set
processor #1 is set
processor #2 is set
bitmask = 0x7
[cpu_affinity #10]$ taskset 8 ./a.out 
processor #3 is set
bitmask = 0x8
[cpu_affinity #11]$ taskset 9 ./a.out 
processor #0 is set
processor #3 is set
bitmask = 0x9
[cpu_affinity #12]$ taskset A ./a.out 
processor #1 is set
processor #3 is set
bitmask = 0xa
[cpu_affinity #13]$ taskset B ./a.out 
processor #0 is set
processor #1 is set
processor #3 is set
bitmask = 0xb
[cpu_affinity #14]$ taskset C ./a.out 
processor #2 is set
processor #3 is set
bitmask = 0xc
[cpu_affinity #15]$ taskset D ./a.out 
processor #0 is set
processor #2 is set
processor #3 is set
bitmask = 0xd
[cpu_affinity #16]$ taskset E ./a.out 
processor #1 is set
processor #2 is set
processor #3 is set
bitmask = 0xe
[cpu_affinity #17]$ taskset F ./a.out 
processor #0 is set
processor #1 is set
processor #2 is set
processor #3 is set
bitmask = 0xf
[cpu_affinity #18]$ taskset 0 ./a.out 
sched_setaffinity: Invalid argument
failed to set pid 0's affinity.

例子二:设置进程的CPU亲和性后再获取显示CPU亲和性

c 复制代码
#define _GNU_SOURCE
#include <sched.h>
#include <unistd.h> /* sysconf */
#include <stdlib.h> /* exit */
#include <stdio.h>

int main(void)
{
    int i, nrcpus;
    cpu_set_t mask;
    unsigned long bitmask = 0;
    
    CPU_ZERO(&mask);
    
    CPU_SET(0, &mask); /* add CPU0 to cpu set */
    CPU_SET(2, &mask); /* add CPU2 to cpu set */

      /* Set the CPU affinity for a pid */
    if (sched_setaffinity(0, sizeof(cpu_set_t), &mask) == -1) 
    {   
        perror("sched_setaffinity");
        exit(EXIT_FAILURE);
    }
    
    CPU_ZERO(&mask);
    
     /* Get the CPU affinity for a pid */
    if (sched_getaffinity(0, sizeof(cpu_set_t), &mask) == -1) 
    {   
        perror("sched_getaffinity");
        exit(EXIT_FAILURE);
    }

       /* get logical cpu number */
    nrcpus = sysconf(_SC_NPROCESSORS_CONF);
    
    for (i = 0; i < nrcpus; i++)
    {
        if (CPU_ISSET(i, &mask))
        {
            bitmask |= (unsigned long)0x01 << i;
            printf("processor #%d is set\n", i); 
        }
    }
    printf("bitmask = %#lx\n", bitmask);

    exit(EXIT_SUCCESS);
}

例子三:设置线程的CPU属性后再获取显示CPU亲和性

这个例子来源于Linux的man page.

c 复制代码
#define _GNU_SOURCE
#include <pthread.h> //不用再包含<sched.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>

#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);
}

例子四:使用seched_setaffinity设置线程的CPU亲和性

c 复制代码
#define _GNU_SOURCE
#include <sched.h>
#include <stdlib.h>
#include <sys/syscall.h> // syscall

int main(void)
{
    pid_t tid;
    int i, nrcpus;
    cpu_set_t mask;
    unsigned long bitmask = 0;
    
    CPU_ZERO(&mask);
    CPU_SET(0, &mask); /* add CPU0 to cpu set */
    CPU_SET(2, &mask); /* add CPU2 to cpu set */

    // get tid(线程的PID,线程是轻量级进程,所以其本质是一个进程)
    tid = syscall(__NR_gettid); // or syscall(SYS_gettid);

      /* Set the CPU affinity for a pid */
    if (sched_setaffinity(tid, sizeof(cpu_set_t), &mask) == -1) 
    {
        perror("sched_setaffinity");
        exit(EXIT_FAILURE);
    }
    
    exit(EXIT_SUCCESS);
}
相关推荐
leoufung1 小时前
vim 多个关键字高亮插件介绍
linux·编辑器·vim
夜泉_ly2 小时前
MySQL -安装与初识
数据库·mysql
qq_529835353 小时前
对计算机中缓存的理解和使用Redis作为缓存
数据库·redis·缓存
Nerd Nirvana4 小时前
软考—系统架构设计(案例 | 论文)
linux·系统架构·软件工程·软考·计算机基础
勤奋的凯尔森同学5 小时前
webmin配置终端显示样式,模仿UbuntuDesktop终端
linux·运维·服务器·ubuntu·webmin
月光水岸New5 小时前
Ubuntu 中建的mysql数据库使用Navicat for MySQL连接不上
数据库·mysql·ubuntu
狄加山6756 小时前
数据库基础1
数据库
我爱松子鱼6 小时前
mysql之规则优化器RBO
数据库·mysql
丁卯4046 小时前
Go语言中使用viper绑定结构体和yaml文件信息时,标签的使用
服务器·后端·golang
chengooooooo6 小时前
苍穹外卖day8 地址上传 用户下单 订单支付
java·服务器·数据库