3、操作系统调优:内核旁路、中断亲和性、CPU隔离
操作系统调优,说白了就是让Linux内核别来添乱。
我刚开始做量化交易系统那会儿,总觉得代码写得够快就行了。后来发现,辛辛苦苦优化到微秒级的逻辑,被操作系统一个上下文切换就给干到毫秒级去了。嗯,那时候我才意识到——操作系统本身,就是最大的延迟来源。
3.1 内核旁路:绕过那个“中间商”
先说说内核旁路。你想想看,传统网络收发包要走什么路径?
- 网卡收到数据 → 中断通知内核 → 内核协议栈处理 → 拷贝到用户态
- 这一套下来,少说几十微秒没了
对于高频交易来说,这简直是灾难。我个人习惯的做法是:直接绕过内核,让用户态程序接管网卡。
核心思路:使用DPDK、Solarflare的OpenOnload、或Mellanox的VMA这类技术,把网卡的控制权从内核手里抢过来。
举个例子,DPDK的工作方式是这样的:
// 传统方式:socket recv
recv(sockfd, buf, len, 0); // 内核参与,延迟高
// DPDK方式:轮询网卡
struct rte_mbuf *mbuf;
while (1) {
mbuf = rte_eth_rx_burst(port, queue, &pkts, 1);
if (mbuf) {
// 直接处理,无内核介入
process_packet(pkts);
}
}
我在项目中遇到过一件事:某次做行情网关优化,用传统socket方式,延迟在50微秒左右。换成DPDK后,直接压到了5微秒以内。10倍的差距,就只是绕过了内核而已。
避坑指南:我曾经以为DPDK是万能的,后来发现它也有代价——你得自己实现TCP/IP协议栈。如果只是做UDP行情接收,问题不大;但要做TCP交易,建议用OpenOnload这种半内核旁路方案。
3.2 中断亲和性:让CPU专心干一件事
中断亲和性,说白了就是告诉操作系统:这个网卡的中断,只许发给这个CPU核。
为什么要这么做?
- 默认情况下,中断是随机分配到各个CPU的
- 这会导致CPU缓存频繁失效(cache miss)
- 每次中断换到不同核,L1/L2缓存都得重新加载
我建议的做法是:
- 先查看网卡中断的IRQ号
- 然后绑定到指定的CPU核上
# 查看网卡中断
cat /proc/interrupts | grep eth0
# 绑定中断到CPU 0-3
echo 0f > /proc/irq/<IRQ_NUM>/smp_affinity
这里有个细节:smp_affinity用的是位图。比如0f表示CPU 0-3,01表示只绑CPU 0。我个人习惯把网卡中断绑到物理核上,避开超线程核。
注意:我曾经踩过一个坑——把中断和业务线程绑到了同一个核上。结果中断一来,业务线程被抢占了,延迟反而更高。正确的做法是:中断绑在核A,业务线程绑在核B,各干各的。
3.3 CPU隔离:给交易线程一个“专属房间”
CPU隔离,就是让某些CPU核完全脱离操作系统的调度。普通进程别想用这些核,只有你指定的线程才能跑。
为什么要隔离?
- Linux默认的调度器是公平的
- 它会时不时把你的交易线程换出去,让别的进程跑一会儿
- 这一换,可能就是几十微秒的延迟抖动
我常用的配置方法:
# 在grub配置中添加isolcpus参数
GRUB_CMDLINE_LINUX="isolcpus=2,3"
# 更新grub后重启
update-grub
reboot
重启后,CPU 2和3就变成了“孤岛”。普通进程不会用它们。然后你用taskset把交易线程绑上去:
# 把进程绑定到CPU 2
taskset -c 2 ./trading_engine
进阶技巧:光隔离还不够,我建议配合rcu_nocbs和nohz_full一起用。这两个参数能让隔离核彻底摆脱内核的定时器中断和RCU回调。配置如下:
GRUB_CMDLINE_LINUX="isolcpus=2,3 nohz_full=2,3 rcu_nocbs=2,3"
我记得有一次帮朋友调一个期货交易系统。他们用了8核机器,但延迟总是有20-30微秒的抖动。我一看,所有线程都在所有核上乱跑。后来我帮他们做了CPU隔离:2个核跑行情,2个核跑交易,1个核跑风控,剩下3个留给系统。抖动直接降到了5微秒以内。
小建议:隔离核的数量不要贪多。留1-2个核给操作系统,否则系统本身会变得不稳定。我一般留2个核给内核和日常任务。
3.4 知识体系总览
下面这张图,是我自己总结的操作系统调优核心逻辑:
这三招用好了,你的交易系统就能在操作系统层面站稳脚跟。下一层,我们就要深入到CPU和内存的硬件调优了。
一句话总结:内核旁路解决“谁处理数据”的问题,中断亲和性解决“谁通知你”的问题,CPU隔离解决“谁抢你CPU”的问题。三管齐下,延迟自然就降下来了。