DPDK应用如何隔离CPU核
结论
不考虑 k8s 容器场景,DPDK 的"核隔离"不是单一机制,而是从内核启动参数 → 用户态调度/中断 → DPDK EAL 线程绑定三层叠加的结果:
- 内核层先把 tick、RCU、调度负载均衡、IRQ 这些"内核杂活"从目标核上搬走;
- 用户态层再用 cpuset、irqbalance、手动绑中断、systemd CPUAffinity 保证没有进程和中断漂移进来;
- DPDK EAL 层最后用
pthread_setaffinity_np()把每个 worker 线程钉死在指定物理核上。
三者缺一不可——只做 DPDK 绑核而不做内核隔离,隔离核上依然会被 tick、RCU、软中断、网卡中断打扰;只做内核隔离而不绑核,DPDK 线程甚至不一定落在预期核上。

分析基于的版本
- Linux 内核:5.15+(cgroup v2
cpuset.cpus.partition=isolated、isolcpus=managed_irq、housekeeping CPU 机制); - DPDK:22.11 LTS / 24.11 LTS(EAL
-l/-c/--lcores/-Sservice core 参数); - 参考文档:Linux 内核官方
admin-guide/cpu-isolation.rst、DPDK 官方《EAL parameters》《Programmer's Guide / EAL》、Intel E810 DPDK 配置指南。
一、第一层:内核启动参数(从根源降噪)
这一层在 GRUB linux 行上加参数,假设要隔离 2-7 号核:
isolcpus=managed_irq,2-7 nohz_full=2-7 rcu_nocbs=2-7 irqaffinity=0-1 tsc=reliable nosmt
各参数作用:
| 参数 | 作用 |
|---|---|
isolcpus=2-7 |
把这些 CPU 移出调度域,不参与负载均衡,未显式绑核的进程、kthread、unbound workqueue 不会调度上来 |
nohz_full=2-7 |
这些核上只要只跑一个用户态任务,就停掉 100Hz/1000Hz 的周期 tick,避免每毫秒一次时钟中断。需要 CONFIG_NO_HZ_FULL=y |
rcu_nocbs=2-7 |
这些核上的 RCU 回调不在本地执行,转发到 housekeeping 核上的 rcuoN kthread 处理,否则 RCU grace period 仍会在隔离核上打断 |
irqaffinity=0-1 |
设定默认 IRQ 亲和性,让新出现的中断默认落在 housekeeping 核(0-1),不落到隔离核 |
isolcpus=managed_irq,2-7 |
对 managed IRQ(如网卡多队列)尝试强制改亲和性 |
tsc=reliable |
时钟源稳定,关闭 clocksource watchdog 自检中断 |
nosmt |
关超线程,避免同一物理核上的另一个硬件线程抢执行资源 |
注意:
isolcpus只是"调度器隔离"提示,硬件中断仍可能打到这些核上,所以必须配合irqaffinity和第二层一起做。这是最容易踩的坑——以为 isolcpus 就万事大吉,结果/proc/interrupts里隔离核上还在跳中断计数。
二、第二层:用户态进程与中断隔离
内核参数挡不住所有东西,还要在运行时把可能漏进来的活动清走。
2.1 cpuset(cgroup v2,现代推荐)
老的 isolcpus= 是启动时写死的,现代内核推荐用 cgroup v2 的 cpuset 隔离分区,运行时可调:
echo +cpuset > /sys/fs/cgroup/cgroup.subtree_control
mkdir /sys/fs/cgroup/dpdk
echo 2-7 > /sys/fs/cgroup/dpdk/cpuset.cpus
echo isolated > /sys/fs/cgroup/dpdk/cpuset.cpus.partition
把 DPDK 主进程的 pid 写入 /sys/fs/cgroup/dpdk/cgroup.procs,它和它创建的线程就被圈进这个隔离分区。
2.2 中断隔离
- irqbalance 排除:编辑
/etc/sysconfig/irqbalance或/etc/irqbalance.conf,设置IRQBALANCE_BANNED_CPULIST=0,1(housekeeping 核),让 irqbalance 守护进程不把中断搬到 2-7。 - 手动绑网卡中断:
多队列网卡要分别给每队列 IRQ 设置亲和性。# 0b11 即只在 CPU0、CPU1 上响应 for r in /proc/irq/*/smp_affinity; do echo 3 > $r; done - RPS/RFS 不下发隔离核:确认
/sys/class/net/<iface>/queues/rx-*/rps_cpus的掩码不包含隔离核,避免协议栈软中断在隔离核上触发。
2.3 其他系统服务别漂进来
systemctl set-property <service> CPUAffinity=0,1,把系统服务固定在 housekeeping 核;- 或用
taskset -c 0,1 ./other_app手动启动其他进程; - DPDK 主进程自己也可以用
taskset兜底,但 EAL 内部绑核已经足够。
三、第三层:DPDK EAL 自身的线程绑定
前两层把环境清干净后,DPDK 自己通过 EAL 把工作线程钉上去。
3.1 指定用哪些核
启动时必传核参数:
-l 2-7(corelist,推荐,最直观);-c 0xfc(coremask,bit 位对应 CPU 号);--lcores='1@2,2@3,3@4,4@5,5@6,6@7'(lcore ID 与物理 CPU 显式映射,适合 NUMA 错位或需要重编号时)。
--lcores 还支持更灵活的写法:--lcores='(1-3)@(31-33)' 表示 lcore 1/2/3 绑定到 CPU 31/32/33 这个集合,由 OS 在集合内调度。
3.2 内部模型
rte_eal_init() 为 -l 列表里的每个物理核创建一个 pthread,然后调用 pthread_setaffinity_np() 把这个 pthread 绑定到对应物理核。每个 pthread 有自己的 TLS _lcore_id,通常与物理 CPU ID 一一对应。这是一个严格的 1:1 线程-物理核模型,目的就是让内核调度器没有迁移的余地。
3.3 角色分工
- main lcore(默认 lcore 0):跑初始化、信号处理、控制面,通常放 housekeeping 核或一个不忙的核;
- worker lcore:跑
rte_eal_mp_remote_launch()启动的数据包处理循环(RX → 处理 → TX),这就是被隔离的那些核; - service core(
-S指定):跑异步事件(如 link status 中断、timer),避免打扰数据面 worker。
3.4 NUMA 对齐
worker 核最好和 NIC 在同一个 NUMA node,用 --numa-mem 指定巨页在本地 node 分配,避免跨 NUMA 访存——否则核虽然"隔离"干净了,但每个包都要跨 node 读内存,性能一样出不来。
四、配套优化(不属于"核隔离"但常一起做)
- 巨页:
echo 2048 > /proc/sys/vm/nr_hugepages,挂hugetlbfs,避免 TLB miss; - mlock / 不换页:DPDK 默认会 mlock 巨页,自己的应用也应
mlockall(MCL_CURRENT|MCL_FUTURE); - CPU 调频:隔离核设为
performancegovernor,避免频率切换引入抖动; - C-state:
processor.max_cstate=1 intel_idle.max_cstate=1,限制深睡,减少唤醒延迟; - SMI:x86 上 BIOS 的 System Management Interrupt 不受 OS 控制,需在 BIOS 里关 SMI 源(如 BIOS 手风琴、冷备份)。
五、一句话总结
内核启动参数把"内核噪音"从隔离核搬走 → cpuset / irqbalance 把"用户态进程和中断"挡在隔离核外 → DPDK EAL 用 pthread_setaffinity_np() 把每个 worker 线程钉在指定物理核上,最终让隔离核上只跑一个用户态数据包循环,没有 tick、没有 RCU、没有中断、没有调度迁移。这三层合起来才是 DPDK 意义上完整的"核隔离"。
浙公网安备 33010602011771号