第三章:操作系统内核调优
说到低延迟交易系统,很多人第一反应是硬件要快、网卡要贵。嗯,这没错。但我要说,操作系统这层要是没调好,再贵的硬件也是白搭。我见过不少团队,花了几十万买FPGA加速卡,结果Linux内核的默认配置就把延迟吃掉了大半。
这一章,咱们就聊聊Linux内核调优的几个关键点。说白了,就是让操作系统别来添乱。
3.1 irqbalance:中断的平衡艺术
先说说irqbalance。这是个啥?它负责把硬件中断请求(IRQ)分配到不同的CPU核心上。默认情况下,Linux会开启irqbalance服务,让中断均匀分布。
听起来挺合理对吧?但在低延迟场景下,这恰恰是个问题。
我个人习惯的做法是:关掉irqbalance。然后手动把网卡中断绑定到指定的CPU核心上。
# 停止irqbalance服务
systemctl stop irqbalance
systemctl disable irqbalance
# 查看网卡中断号
cat /proc/interrupts | grep eth0
# 手动绑定中断到CPU 2(假设中断号是78)
echo 2 > /proc/irq/78/smp_affinity
这里有个坑,我曾经踩过:smp_affinity用的是位掩码。CPU 0对应bit 0,CPU 1对应bit 1,以此类推。所以CPU 2的掩码是4(二进制100),而不是2。嗯,这个细节容易搞错。
3.2 isolcpus:把CPU圈起来
接下来是isolcpus。这个内核参数的作用,是把指定的CPU核心从Linux的进程调度器中隔离出来。
你想想看,默认情况下,Linux调度器会在所有核心上跑来跑去。你的交易线程正在CPU 3上跑着,突然系统要处理一个定时器中断,啪,把你的线程踢到CPU 4上去了。这一踢,缓存全没了,延迟直接飙升。
isolcpus就是解决这个问题的。它在内核启动时就把某些核心标记为「隔离区」,普通进程和中断不会跑上去。
配置方法很简单,在GRUB启动参数里加一行:
isolcpus=2,3 nohz_full=2,3 rcu_nocbs=2,3
这表示CPU 2和3被隔离。我一般还会加上nohz_full和rcu_nocbs,后面会细说。
记得更新GRUB配置:
grub2-mkconfig -o /boot/grub2/grub.cfg
重启后,用taskset把交易进程绑定到隔离核心上:
taskset -c 2,3 ./trading_engine
3.3 nohz_full:关掉时钟中断
好,核心隔离了,但还有个问题:时钟中断。
Linux内核默认每秒会产生1000次时钟中断(取决于HZ配置)。每次中断都会打断CPU上正在运行的线程。对于隔离的核心来说,这简直是灾难。
nohz_full参数的作用,就是让指定的核心完全关闭时钟中断。只有在有任务切换或外部事件时,才会产生中断。
完整的GRUB配置示例:
GRUB_CMDLINE_LINUX="isolcpus=2,3 nohz_full=2,3 rcu_nocbs=2,3 intel_pstate=disable processor.max_cstate=1"
这里我还加了intel_pstate=disable和processor.max_cstate=1,目的是禁用CPU的深度睡眠状态。为什么?因为CPU从睡眠状态唤醒需要时间,这个延迟不可控。
3.4 CPU亲和性:把线程钉死
隔离了核心,还得把线程绑上去。这就是CPU亲和性(affinity)做的事。
除了前面提到的taskset,在代码里也可以设置:
#include <sched.h>
cpu_set_t cpuset;
CPU_ZERO(&cpuset);
CPU_SET(2, &cpuset); // 绑定到CPU 2
pthread_setaffinity_np(thread, sizeof(cpu_set_t), &cpuset);
我习惯在交易引擎启动时,把每个关键线程都显式绑定。比如行情接收线程绑到CPU 2,订单管理线程绑到CPU 3。这样每个线程都有自己的专属核心,互不干扰。
3.5 内存大页:减少TLB缺失
最后聊聊HugePages。这是个老生常谈的话题,但确实重要。
默认情况下,Linux内存页大小是4KB。对于交易系统这种需要大量内存访问的场景,TLB(页表缓存)很容易被撑爆。每次TLB缺失,CPU都要去查页表,这个开销在微秒级别——对于交易系统来说,已经很大了。
HugePages把页大小提升到2MB甚至1GB。同样的内存空间,需要的页表项大大减少,TLB命中率自然就上去了。
配置方法:
# 预留1024个2MB的大页
echo 1024 > /proc/sys/vm/nr_hugepages
# 查看是否成功
cat /proc/meminfo | grep HugePages
然后在程序里使用大页:
// 使用mmap分配大页内存
void *ptr = mmap(NULL, size, PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS | MAP_HUGETLB, -1, 0);
或者通过libhugetlbfs库透明地使用:
export LD_PRELOAD=libhugetlbfs.so
export HUGETLB_MORECORE=yes
./trading_engine
3.6 本章知识体系
说了这么多,我画张图帮你理清思路:
这张图把四个调优方向串起来了。核心思路就一个:让交易线程独占资源,减少一切不必要的干扰。
嗯,这一章的内容就这些。调优是个细致活,每个参数都要根据你的硬件和业务场景来调整。我建议你先在测试环境里验证,确认没问题了再上生产。
一句话总结:关掉irqbalance,隔离核心,关掉时钟中断,用上大页。这四步做完,你的Linux内核就基本「干净」了。
公众号:蓝海资料掘金营,微信deep3321