AIGC标识 DPDK应用如何隔离CPU核

结论

不考虑 k8s 容器场景,DPDK 的"核隔离"不是单一机制,而是从内核启动参数 → 用户态调度/中断 → DPDK EAL 线程绑定三层叠加的结果:

  • 内核层先把 tick、RCU、调度负载均衡、IRQ 这些"内核杂活"从目标核上搬走;
  • 用户态层再用 cpuset、irqbalance、手动绑中断、systemd CPUAffinity 保证没有进程和中断漂移进来;
  • DPDK EAL 层最后用 pthread_setaffinity_np() 把每个 worker 线程钉死在指定物理核上。

三者缺一不可——只做 DPDK 绑核而不做内核隔离,隔离核上依然会被 tick、RCU、软中断、网卡中断打扰;只做内核隔离而不绑核,DPDK 线程甚至不一定落在预期核上。

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 / -S service 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 中断隔离

  1. irqbalance 排除:编辑 /etc/sysconfig/irqbalance 或 /etc/irqbalance.conf,设置 IRQBALANCE_BANNED_CPULIST=0,1(housekeeping 核),让 irqbalance 守护进程不把中断搬到 2-7。
  2. 手动绑网卡中断:
    # 0b11 即只在 CPU0、CPU1 上响应
    for r in /proc/irq/*/smp_affinity; do echo 3 > $r; done
    
    多队列网卡要分别给每队列 IRQ 设置亲和性。
  3. 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 调频:隔离核设为 performance governor,避免频率切换引入抖动;
  • 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 意义上完整的"核隔离"。

posted on 2026-09-20 08:58  王景迁  阅读(14)  评论(0)    收藏  举报

导航