预备知识

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

我们常听到的双核四线程/四核八线程指的就是支持超线程技术的CPU.

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

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

逻辑CPU数量 = 物理CPU数量 x CPU cores x 2(如果支持并开启HT) //前提是CPU的型号一致,如果不一致只能一个一个的加起来,不用直接乘以物理CPU数量
//比如你的电脑安装了一块4核CPU,并且支持且开启了超线程(HT)技术,那么逻辑CPU数量 = 1 × 4 × 2 = 8

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

# 查看物理CPU个数
➜  ~ cat /proc/cpuinfo|grep "physical id"|sort -u|wc -l
32

# 查看每个物理CPU中core的个数(即核数)
➜  ~ cat /proc/cpuinfo|grep "cpu cores"|uniq
1
# 或者
➜    cat /proc/cpuinfo | grep 'process' | sort | uniq | wc -l
1

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

# 查看CPU的名称型号
➜  ~ cat /proc/cpuinfo|grep "name"|cut -f2 -d:|uniq
Intel Xeon Processor (Skylake, IBRS)

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

ps -eo pid,args,psr
  • pid:进程 ID
  • args:该进程执行时传入的命令行参数
  • psr:分配给进程的逻辑 CPU

例子:

➜  ps -eo pid,args,psr | grep firefox
20118 /usr/lib/firefox/firefox -n  13
20208 /usr/lib/firefox/firefox -c   9
20266 /usr/lib/firefox/firefox -c  29
20329 /usr/lib/firefox/firefox -c  24
20499 /usr/lib/firefox/firefox -c   7
20565 /usr/lib/firefox/firefox -c  15
20596 /usr/lib/firefox/firefox -c  24
20760 /usr/lib/firefox/firefox -c  18
22110 /usr/lib/firefox/firefox -c  27
25857 /usr/lib/firefox/firefox -c  28
26347 /usr/lib/firefox/firefox -c  19
26899 /usr/lib/firefox/firefox -c  29

Linux 查看线程的 TID

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

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

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

使用 API 获取:

#include <sys/syscall.h>  
pid_t tid;
tid = syscall(__NR_gettid); // or syscall(SYS_gettid)  

命令行方式获取:

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

什么是 CPU 亲和性

CPU 的亲和性 (Affinity),属于一种调度属性,可以绑定进程到指定 CPU 上。 换句话说,就是进程要在指定的 CPU 上尽量长时间地运行而不被迁移到其他处理器。

为何会出现这种技术?在 SMP(Symmetric Multi-Processing 对称多处理) 架构下,调度器会试图保持进程在相同的 CPU 上运行,这意味着进程通常不会在处理器之间频繁迁移,进程迁移的频率小就意味着产生的负载小。

又如,每个 CPU 本身自己会有缓存,缓存着进程使用的信息,而进程可能会被操作系统调度到其他 CPU 上,如此,CPU 缓存命中率就低了,当绑定 CPU 后,程序就会一直在指定的 CPU 跑,不会由操作系统调度到其他 CPU 上,性能有一定的提高。

软亲和性:  就是进程要在指定的 CPU 上尽量长时间地运行而不被迁移到其他处理器,Linux 内核进程调度器天生就具有被称为 软 CPU 亲和性(affinity)的特性,这意味着进程通常不会在处理器之间频繁迁移。这种状态正是我们希望的,因为进程迁移的频率小就意味着产生的负载小。

硬亲和性:简单来说就是利用 Linux 内核提供给用户的 API,强行将进程或者线程绑定到某一个指定的 CPU 核运行。

CPU affinity 使用位掩码 (bitmask) 表示,每一位都表示一个 CPU, 置 1 表示"绑定"。最低位表示第一个逻辑 CPU, 最高位表示最后一个逻辑 CPU。

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

0b00000000000000000000000000000001
= 0x00000001
表示 processor #0

0b00000000000000000000000001010101
= 0x00000055
表示 processors #0, #2, #4, #6

0b11111111111111111111111111111111
= 0xFFFFFFFF
表示所有 processors (#0 through #31)

使用taskset命令设置 CPU 亲和性

命令行形式

taskset [options] mask command [arg]...
taskset [options] -p [mask] pid

参数解析 [OPTIONS]taskset 的可选参数

  • -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 显示帮助信息

  • -V, --version 显示版本信息

  • mask : cpu 亲和性,当没有-c选项时,其值前无论有没有0x标记都是 16 进制的,当有-c选项时,其值是十进制的。

  • command : 命令或者可执行程序

  • pid : 进程 ID,可以通过ps/top/pidof等命令获取

[arg] command 的参数

实例

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

taskset [-c] mask command [arg]...
# 举例: 使用CPU0运行ls命令显示/etc/init.d下的所有内容
taskset -c 0 ls -al /etc/init.d/

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

taskset -p pid
# 举例:查看init进程(PID=1)的CPU亲和性
taskset -p 1

改变已经运行进程的 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 #查看是否更改成功
➜  ~ ps -eo pid,args,psr | grep top
2501 nautilus-desktop             24
2634 /usr/libexec/xdg-desktop-po  18
2658 /usr/libexec/xdg-desktop-po  11
23848 top                           6
➜  ~ taskset -cp 10 23848
pid 23848's current affinity list: 0-31
pid 23848's new affinity list: 10
➜  ~ ps -eo pid,args,psr | grep top
 2501 nautilus-desktop             24
 2634 /usr/libexec/xdg-desktop-po  18
 2658 /usr/libexec/xdg-desktop-po  11
23848 top                          10

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

程序 API 实现硬亲和性

以下实验使用的源码可以从这个仓库获取。

以下是一些设置亲和性时会用到的宏定义及函数:

#define _GNU_SOURCE
#include <sched.h>
#include <pthread.h> //注意<pthread.h>包含<sched.h>

/* MACRO */
        // 对 CPU 集初始化,将其设置为空集
        void CPU_ZERO(cpu_set_t *set);
        void CPU_ZERO_S(size_t setsize, cpu_set_t *set);
        
        // 将 CPU 加入到 CPU 集中
        void CPU_SET(int cpu, cpu_set_t *set);
        void CPU_SET_S(int cpu, size_t setsize, cpu_set_t *set);
        
        // 将 CPU 从 CPU 集中移除
        void CPU_CLR(int cpu, cpu_set_t *set);
        void CPU_CLR_S(int cpu, size_t setsize, cpu_set_t *set);
        
        // 判断 CPU 是否在 CPU 集中
        int CPU_ISSET(int cpu, cpu_set_t *set);
        int CPU_ISSET_S(int cpu, size_t setsize, cpu_set_t *set);
        
        // 计算 CPU 集的大小
        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 */
    /*该函数设置进程为 pid 的这个进程,让它运行在 mask 所设定的 CPU 上。如果 pid 的值为 0,
    *则表示指定的是当前进程,使当前进程运行在 mask 所设定的那些 CPU 上.
    *第二个参数 cpusetsize 是 mask 所指定的数的长度。通常设定为 sizeof(cpu_set_t).
    *如果当前 pid 所指定的进程此时没有运行在 mask 所指定的任意一个 CPU 上,
    *则该指定的进程会从其它 CPU 上迁移到 mask 的指定的一个 CPU 上运行.
    */
    int sched_setaffinity(pid_t pid, size_t cpusetsize, cpu_set_t *mask);
    /*该函数获得 pid 所指示的进程的 CPU 位掩码,并将该掩码返回到 mask 所指向的结构中.
    *即获得指定 pid 当前可以运行在哪些 CPU 上.
    *同样,如果 pid 的值为 0.也表示的是当前进程
    */
    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);

程序中会使用syscall来获取一些内核数据,syscall是执行一个系统调用,根据指定的参数number和所有系统调用的接口来确定调用哪个系统调用,用于用户空间跟内核之间的数据交换,下面是syscall函数原型及一些常用的number

//syscall - indirect system call
#define _GNU_SOURCE         /* See feature_test_macros(7) */
#include <unistd.h>
#include <sys/syscall.h>   /* For SYS_xxx definitions */

int syscall(int number, ...);

// 查看缓存内存页面的大小;打印用%ld 长整型。
sysconf(_SC_PAGESIZE);  
// 查看内存的总页数;打印用%ld 长整型。
sysconf(_SC_PHYS_PAGES) 
// 查看可以利用的总页数;打印用%ld 长整型。
sysconf(_SC_AVPHYS_PAGES) 
// 查看 CPU 的个数;打印用%ld 长整。
sysconf(_SC_NPROCESSORS_CONF) 
// 查看在使用的 CPU 个数;打印用%ld 长整。
sysconf(_SC_NPROCESSORS_ONLN) 
// 计算内存大小。
(long long)sysconf(_SC_PAGESIZE) * (long long)sysconf(_SC_PHYS_PAGES) 
// 查看最大登录名长度;打印用%ld 长整。
sysconf(_SC_LOGIN_NAME_MAX) 
// 查看最大主机长度;打印用%ld 长整。
sysconf(_SC_HOST_NAME_MAX) 
// 每个进程运行时打开的文件数目;打印用%ld 长整。
sysconf(_SC_OPEN_MAX)  
// 查看每秒中跑过的运算速率;打印用%ld 长整。
sysconf(_SC_CLK_TCK) 

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

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

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

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

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

#undef WITH_S

#ifdef WITH_S

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

    /* 获取逻辑 CPU 个数 */
    nrcpus = sysconf(_SC_NPROCESSORS_CONF);

    pmask = CPU_ALLOC(nrcpus);
    cpusize = CPU_ALLOC_SIZE(nrcpus);
    CPU_ZERO_S(cpusize, pmask);

    /* 获取 CPU 亲和性 */
    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);
}
#else
int main(void)
{
    int i, nrcpus;
    cpu_set_t mask;
    unsigned long bitmask = 0;

    CPU_ZERO(&mask);

    /* 获取 CPU 亲和性 */
    if (sched_getaffinity(0, sizeof(cpu_set_t), &mask) == -1) {
        perror("sched_getaffinity");
        exit(EXIT_FAILURE);
    }

    /* 获取逻辑 CPU 个数 */
    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);
}
#endif
# 直接运行,不设置亲和性,获取CPU亲和性为所有CPU都会被设置
➜  Affinity git:(main)   ./bin/main 
processor #0 is set
processor #1 is set
processor #2 is set
processor #3 is set
processor #4 is set
processor #5 is set
processor #6 is set
processor #7 is set
processor #8 is set
processor #9 is set
processor #10 is set
processor #11 is set
processor #12 is set
processor #13 is set
processor #14 is set
processor #15 is set
processor #16 is set
processor #17 is set
processor #18 is set
processor #19 is set
processor #20 is set
processor #21 is set
processor #22 is set
processor #23 is set
processor #24 is set
processor #25 is set
processor #26 is set
processor #27 is set
processor #28 is set
processor #29 is set
processor #30 is set
processor #31 is set
bitmask = 0xffffffff

# 使用taskset设置亲和性,将main程序绑定到第1个CPU上,mask转化为16进制为0x1
➜  Affinity git:(main)  taskset 1 ./bin/main
processor #0 is set
bitmask = 0x1

# 使用taskset设置亲和性,将main程序绑定到第1,3,5,7个CPU上,mask转化为16进制为0x55
➜  Affinity git:(main)  taskset 55 ./bin/main
processor #0 is set
processor #2 is set
processor #4 is set
processor #6 is set
bitmask = 0x55

# 使用taskset设置亲和性,将main程序绑定到第1,2,3,4个CPU上,mask转化为16进制为0xf
➜  Affinity git:(main)  taskset F ./bin/main
processor #0 is set
processor #1 is set
processor #2 is set
processor #3 is set
bitmask = 0xf

设置进程的 CPU 亲和性后再获取显示 CPU 亲和性

#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 */
    CPU_SET(3, &mask); /* add CPU3 to cpu set */

    /* 设置 CPU 亲和性 */
    if (sched_setaffinity(0, sizeof(cpu_set_t), &mask) == -1) 
    {   
        perror("sched_setaffinity");
        exit(EXIT_FAILURE);
    }
    
    CPU_ZERO(&mask);
    
    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);
}
➜  Affinity git:(main) ✗ ./bin/main 
processor #0 is set
processor #2 is set
processor #3 is set
bitmask = 0xd

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

#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);
}
➜  Affinity git:(main) ./bin/main
Set returned by pthread_getaffinity_np() contained:
    CPU 0
    CPU 1
    CPU 2
    CPU 3
    CPU 4
    CPU 5
    CPU 6
    CPU 7

使用 seched_setaffinity 设置线程的 CPU 亲和性

#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 */

    // 获取线程 id
    tid = syscall(__NR_gettid); // or syscall(SYS_gettid);

    // 对指定线程 id 设置 CPU 亲和性
    if (sched_setaffinity(tid, sizeof(cpu_set_t), &mask) == -1) 
    {
        perror("sched_setaffinity");
        exit(EXIT_FAILURE);
    }

    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);
        }
    }
    
    exit(EXIT_SUCCESS);
}
➜  Affinity git:(main) ./bin/main
processor #0 is set
processor #2 is set

什么是中断亲和性

计算机中,中断是一种电信号,由硬件产生并直接送到中断控制器上,再由中断控制器向 CPU 发送中断信号,CPU 检测到信号后,中断当前工作转而处理中断信号。CPU 会通知操作系统已经产生中断,操作系统就会对中断进行处理。 这里有篇推文:CPU 明明 8 个核,网卡为啥拼命折腾一号核?生动的解释了中断亲和性。

默认情况下,Linux 中断响应会被平均分配到所有 CPU 核心上,势必会发生写新的数据和指令缓存,并与 CPU 核心上原有进程产生冲突,造成中断响应延迟,影响进程处理时间。为了解决这个问题,可以将中断(或进程)绑定到指定 CPU 核心上,中断(或进程)所需要指令代码和数据有更大概率位于指定 CPU 本地数据和指令缓存内,而不必进行新的写缓存,从而提高中断响应(或进程)的处理速度。

中断亲和性的使用场景

对于文件服务器、Web 服务器,把不同的网卡 IRQ 均衡绑定到不同的 CPU 上将会减轻某 CP 的负载,提高多个 CPU 整体处理中断的能力; 对于数据库服务器,把磁盘控制器绑到一个 CPU、把网卡绑定到另一个 CPU 将会提高数据库的响应时间、优化性能。
合理的根据自己的生产环境和应用的特点来平衡 IRQ 中断有助于提高系统的整体吞吐能力和性能。

中断绑定流程

  1. 关闭中断平衡守护进程 中断平衡守护进程(irqbalance daemon)会周期性地将中断平均地公平地分配给各个 CPU 核心,默认开启。为了实现中断绑定,首先需要将中断平衡守护进程关闭。

    • systemctl status irqbalance查看守护进程的运行状态
    ➜  ~ systemctl status irqbalance
    
    ● irqbalance.service - irqbalance daemon
    Loaded: loaded (/lib/systemd/system/irqbalance.service; enabled; vendor preset: enable
    Active: active (running) since Thu 2022-05-19 14:46:20 CST; 1 weeks 1 days ago
    Main PID: 1062 (irqbalance)
        Tasks: 2 (limit: 4915)
    CGroup: /system.slice/irqbalance.service
            └─1062 /usr/sbin/irqbalance --foreground
    
    5月 19 14:46:20 zdd systemd[1]: Started irqbalance daemon.
    
    • systemctl stop irqbalance关闭中断平衡守护进程,中断响应默认都会由 CPU0 核心处理。或者systemctl disable irqbalance取消中断平衡守护进程开机重启。因为关闭中断平衡守护进程过于强硬,可以在不关闭中断平衡守护进程条件下,让某些 CPU 核心脱离中断平衡守护进程的管理。
  2. 绑定中断 中断绑定时,需要关闭系统中断平衡守护进程systemctl stop irqbalance计算机当前各种中断响应情况在 /proc/interrupts 文件中。

    Responsive Image

    第一列是中断 ID 号,CPU N 列是中断在第 n 个 CPU 核心上的响应次数,倒数第二列是中断类型,最后一列是描述。

    利用 echo 命令将 CPU 掩码写入 /proc/irq/中断 ID/smp_affinity 文件中,即可实现修改某一中断的 CPU 亲和性。例如

    echo 0x0004 > /proc/irq /50/smp_affinity
    

参考资料

Linux 中 CPU 亲和性 (affinity) - LubinLew - 博客园 操作系统底层技术——CPU 亲和性_mb60ed33cfc44fa 的技术博客_51CTO 博客 linux 进程、线程与 CPU 的亲和性(affinity)_wx61d68abba262d 的技术博客_51CTO 博客 CPU 明明 8 个核,网卡为啥拼命折腾一号核? Processor affinity - Wikipedia 什么?一个核同时执行两个线程? linux 进程、线程与 CPU 的亲和性(affinity) - zhangwju - 博客园