CPU独占内核运行方式实现,并指定线程到特定CPU上执行
1. 隔离指定的CPU,避免其余线程run在被隔离的CPU上
隔离CPU的方法:修改Linux内核的启动参数isolcpus
直接更改GRUB配置文件:
# 打开GRUB配置文件 /etc/default/grub
sudo vim /etc/default/grub
# 找到名为 GRUB_CMDLINE_LINUX 的行。该行包含了内核启动参数。
#在双引号中的参数列表中添加 isolcpus 参数,并指定要隔离的CPU核心。例如,如果要隔离核心1,可以这样设置:
GRUB_CMDLINE_LINUX="isolcpus=1"
#还可以在GRUB中设置 'nohz_full=1' 减少系统定时器中断,以提高系统的性能和能效。
#在启用 nohz_full=1 后,需要注意一些时序相关的问题,例如定时器相关的系统调用的行为可能会受到影响。此外,一些应用程序可能依赖于系统定时器的正常工作,可能需要进行适当的测试和调整。
GRUB_CMDLINE_LINUX="isolcpus=1 nohz_full=1"
# 保存文件并退出编辑器。更新GRUB配置,以使更改生效。
sudo update-grub
# 在Centos下上面的更新GRUB配置是无效的
# 需要更新 GRUB 配置,按照以下步骤操作:
# 修改 /etc/default/grub 文件,添加或修改相应的内核参数。
# 运行以下命令以重新生成 GRUB 配置文件:
grub2-mkconfig -o /boot/grub2/grub.cfg
# 重新启动系统,使得修改生效。
sudo reboot
2. 绑定所有的interrupts(中断)到非隔离的CPU上,避免被隔离的CPU收到interrupt.
因为被隔离的CPU虽然没有线程run在上面,但是仍会收到interrupt,绑定所有的interrupts(中断)到非隔离的CPU上
特定IRQ的亲和度值储存在 ‘/proc/irq/IRP_NUMBER/smp_affinity’ 文件中。此文件仅ROOT用户可见。储存的值是一个十六进制位掩码(hexadecimal bit-mask),代表着系统的所有CPU核心.
实际上,在我们将isolcpus参数写入GRUB配置文件后,用户空间完全不会占用这个被隔离的CPU,内核空间Linux会将某些中断IRQ的smp_affinity都会避开所要隔离的CPU号,但是仍有一部分中断没有被绑定,例如: Single function call interrupts, Local timer interrupts等
只要有了一个线程在所隔离的CPU上run,那么timer tick就会开始跑,这个timer tick也会频繁打断这一个线程,从而造成大量的上 下文切换,所以我们可以优化一下这种情况,也就是在GRUB配置中增加:(一个线程独占CPU时优化)
GRUB_CMDLINE_LINUX="nohz_full=2"
添加与不添加区别:
nohz_full=1
和nohz_full=2
之间的区别在于如何处理中断。在nohz_full=1
模式下,中断仍然会在每个处理器的计时器到期时被触发。而在nohz_full=2
模式下,Linux 内核会尝试将中断委托给少数几个特定的处理器,从而减少了中断的数量,提高了系统的性能。
3. C++绑定线程运行在指定CPU
设定某个线程的CPU亲和性
//POSIX 线程库提供的函数,设置线程的 CPU 亲和性
int pthread_setaffinity_np(pthread_t thread, size_t cpusetsize, const cpu_set_t *cpuset);
//获取线程的 CPU 亲和性
int pthread_getaffinity_np(pthread_t thread, size_t cpusetsize, cpu_set_t *cpuset);
#include <pthread.h>
#include <iostream>
int main() {
pthread_t thread;
cpu_set_t cpuset;
int cpu_core = 1; // 绑定隔离的1号CPU
// 获取当前线程的标识符
thread = pthread_self();
// 初始化 CPU 集合
CPU_ZERO(&cpuset);
// 设置要绑定的 CPU 核心
CPU_SET(cpu_core, &cpuset);
// 设置线程的 CPU 亲和性
int result = pthread_setaffinity_np(thread, sizeof(cpu_set_t), &cpuset);
if (result != 0) {
std::cerr << "Error setting thread CPU affinity" << std::endl;
return 1;
}
std::cout << "Thread CPU affinity set successfully" << std::endl;
// 这里可以放置线程的逻辑代码
//TODO
return 0;
}
也可以通过taskset来使线程/进程绑定到隔离的CPU上:
查看进程当前运行在哪个CPU上:
taskset -p [pid]
指定进程运行在第二个CPU(CPU1)上:
taskset -cp 1 进程号
测试结果:
隔离了CPU1,没有改之前,CPU0是满的
改了之后,CPU0与CPU1都满了
验证:版本:CentOS Linux release 7.9.2009 (Core)
'Test1.cc'
#include <iostream>
#include <unistd.h>
int main()
{
fork();
while(1);
return 0;
}
默认状态执行Test1.cc
:
设置isolcpus=1
后的cpu占用率
可以看出CPU1被隔离了
现在我们运行下面代码,设定创建的线程绑定所隔离的CPU1
可以看出来该线程在隔离的CPU1上运行
有些中断不能被更改,有些可以
验证 版本 Ubuntu 22.04.2 LTS
设置了isolcpus=1 nohz_full=1
已经将CPU1隔离,正常用户空间不会使用到CPU1
taskset能生效,将一个进程绑定进入CPU1
将两个进程都添加进入CPU1执行:
将线程绑定指定的CPU1执行(已经隔离了CPU1):