Linux低延迟服务器调优
概述
本文基于饶萌大佬的《Linux低延迟服务器系统调优》一文总结整理,自行补充了一些细节。大佬博文指路:
低延迟关键不在于低,而在于稳定,稳定即可预期,可掌控,这对高频领域来说尤其重要。谈及Linux低延迟技术时,人们经常提到“Kernel Bypass”(内核旁路),即绕过内核,这是因为内核处理不仅慢且延迟不稳定。因此一个延迟要求很高的实时任务是不能触碰内核的,“避免触碰”是一个比Bypass更高的要求:不能以任何方式进入内核。中断(Interrupt)是进入内核的方式之一,本文的关键点也在于避免关键线程被中断。即使中断发生时线程是空闲的,但重新回到用户态后CPU缓存被污染了,下一次处理请求的延迟也会变得不稳定。
绑定核心
中断是CPU Core收到的,可以让关键线程绑定在某个Core上,然后避免各种中断源(IRQ)向这个Core发送中断。
绑定程序在一个核上运行,有两种方法:taskset
和sched_setaffinity
。
CPU Affinity
中文译作“CPU亲和力”,是指在CMP结构下,能够将一个或多个进程绑定到一个或多个处理器上运行。
查看进程分配的CPU Core
可以使用taskset
命令查看:
1 | taskset -c -p <pid> |
运行结构:
1 | pid 17147's current affinity list: 3-5 |
该CPU亲和力列表表明该进程可能会被安排在3-5中任意一个CPU Core上。
更具体地查看某进程当前正运行在哪个CPU Core上,我们可以使用top
命令查看:
1 | top -p <uid> |
taskset
使用taskset
命令将进程绑定到指定核:
1 | taskset -cp <core> <pid> |
如:
1 | taskset -cp 1,2,3 31693 |
该例会将PID为31693的进程绑定到1-3核上运行。
sched_setaffinity
编写代码时,我们可以通过sched_setaffinity()
函数设置CPU亲和力的掩码,从而将该线程或者进程与指定的CPU绑定。
一个CPU的亲和力掩码用一个cpu_set_t
结构体来表示一个CPU集合,下面这几个宏分别对掩码集进行操作:
1 | CPU_ZERO(cpu_set_t* cpusetp); // 清空一个集合 |
使用sched_setaffinity()
与sched*_*getaffinity()
等函数需要引进头文件sched.h
。
- 头文件:
sched.h
int sched_setaffinity(pid_t pid, size_t cpusetsize, const cpu_set_t* mask)
该函数将PID为pid的进程设置为运行在mask指定的CPU Core上。
* 若pid为0,则表示指定当前进程。
* cpusetsize为mask的大小,通常为sizeof(cpu_set_t)
。
* cpuset即用cpu_set_t
结构体表示的CPU Core集合。
* 函数返回0表示成功,失败则返回-1。int sched_getaffinity(pid_t pid, size_t cpusetsize, cpu_set_t* mask)
函数获取PID为pid的进程CPU亲和力掩码,并保存在mask结构体中,即获得指定pid当前可以运行在哪些CPU上。
* 若pid为0,则表示指定当前进程。
* cpusetsize为mask的大小,通常为sizeof(cpu_set_t)
。
* cpuset即用cpu_set_t
结构体表示的CPU Core集合。
* 函数返回0表示成功,失败则返回-1。
使用示例:
1 |
|
注意:若使用到pthread,则需要将pthread.h放到sched.h之后,并在sched.h之前声明#define __USE_GNU,否则会出现undefined reference CPU_ZERO等错误。
屏蔽硬中断(硬盘、网卡)
中断源(IRQ)向CPU Core发送中断,CPU Core调用中断处理程序对中断进程处理。我们可以通过改写/proc/irq/*/smp_affinity
文件,避免中断源(IRQ)向某些CPU Core发送中断。该方法对硬盘、网卡等设备引起的硬中断有效。
查看设备中断数据
通过查看/proc/interrupts
文件可查看设备中断数据:
1 | CPU0 CPU1 CPU2 CPU3 CPU4 CPU5 CPU6 CPU7 |
- 第一列是IRQ号
- 第二列开始表示某CPU内核被多少次中断。
SMP_AFFINITY
SMP,即symmetric multiprocessing(对称多处理器),通过多个处理器处理程序的方式。smp_affinity文件处理一个IRQ的中断亲和性。在smp_affinity文件结合每个IRQ号在/proc/irq/{IRQ_NUMBER}/smp_affinity
文件。该文件的值是一个16进制掩码表示系统的所有CPU核。
例如,Solarflare网卡的设备名为eth1,其中断如下:
1 | grep eth1 /proc/interrupts |
输出如下:
1 | 64: 95218 168786727 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 IR-PCI-MSI-edge eth1-0 |
可以看到eth1设备共有三个IRQ号:64,65,66,其相对应的smp_afffinity文件时:
1 | cat /proc/irq/64/smp_affinity |
输出:
1 | 0000,00000000,00000000,0000000f |
smp_affinity是16进制表示,f就是二进制的1111,表示0-3这四个CPU Core都会参与处理中断。
假设我们想要指定仅0-1这两个核心处理中断,则可以写入如下数据到smp_affinity:
1 | echo 3 > /proc/irq/64/smp_affinity |
屏蔽软中断(Work queue)
workqueue是自kernel2.6引入的一种任务执行机制,和softirq,tasklet并称下半部(bottom half)三剑客。workqueue在进程上下文异步执行任务,能够进行睡眠。可以通过改写/sys/devices/virtual/workqueue/*/cpumask
文件实现屏蔽Work queue的软中断。
/sys/devices/virtual/workqueue/cpumask
文件中记录了全局的cpumask,可以影响所有的workqueue。文件内容格式与smp_affinity相同:
1 | cat /sys/devices/virtual/workqueue/cpumask |
输出如下:
1 | 0000,00000000,00000000,00000007 |
代表0-2这三个CPU Core用来处理work queue,我们可以通过写入”f”来仅让与核心进程无关的0-3这四个CPU Core参与work queue处理:
1 | echo 'f' > /sys/devices/virtual/workqueue/cpumask |
屏蔽软中断(Local Timer Interrupt)
Linux的scheduler time slice是通过LOC实现的,如果我们让一个线程独占一个CPU Core,就不需要scheduler在这个CPU Core上切换进程。可以通过isolcpus
系统启动选项隔离一些核,让他们只能被绑定的线程使用。同时,我们还可以启用“adaptive-ticks”模式,达到减少独占线程收到LOC频率的效果,这可以通过nohz_full
和rcu_nocbs
启动选项实现。
假设令6-8三个核心屏蔽软中断,我们需要在系统启动选项中加入:
1 | nohz=on nohz_full=6-8 rcu_nocbs=6-8 |
进入adaptive-ticks模式后,如果CPU Core上的running task只有一个时,系统向其发送的LOC频率会显著降低,但LOC不能被完全屏蔽,系统内核的一些操作比如计算CPU负载等仍然需要周期性的LOC。
更多
中断和smp_affinity:
SolarFlare资源汇总: