CPU 亲和性与中断亲和性

预备知识

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

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

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

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

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 查看物理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 上

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

例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
➜  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 获取:

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

命令行方式获取:

1
2
3
4
# 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 进制,具体如下。

1
2
3
4
5
6
7
8
9
10
11
0b00000000000000000000000000000001
= 0x00000001
表示 processor #0

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

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

使用taskset命令设置 CPU 亲和性

命令行形式

1
2
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 亲和性运行一个新程序

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

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

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

改变已经运行进程的 CPU 亲和性

1
2
3
4
5
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 #查看是否更改成功
1
2
3
4
5
6
7
8
9
10
11
12
13
  ~ 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 实现硬亲和性

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
#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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
//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 获得,两者的用法详见下面的例子。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
#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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
# 直接运行,不设置亲和性,获取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 亲和性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
#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);
}
1
2
3
4
5
➜  Affinity git:(main) ✗ ./bin/main 
processor #0 is set
processor #2 is set
processor #3 is set
bitmask = 0xd

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
#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);
}
1
2
3
4
5
6
7
8
9
10
➜  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 亲和性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
#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);
}
1
2
3
➜  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查看守护进程的运行状态
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    ➜  ~ 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 文件中。


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

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

    1
    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 - 博客园

VSCode 设置终端为 Gitbash SoC 存储器比较
You need to set install_url to use ShareThis. Please set it in _config.yml.

评论

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×