核心隔离与线程绑定
「把线程钉到核上」这一句话背后藏着三种机制,工作在三个不同层面——单独使用任何一种都没有效果。
本页解释 GRUB_CMDLINE_LINUX 启动参数、taskset 与 numactl 各自控制什么、它们如何组合成一套
可用的方案,以及如何精确地绑定一个 Aeron media driver。至于钉到 哪里(哪个 CCD/NUMA 节点),
见 NUMA 与缓存局部性;本页讲的是 怎么钉。
启动参数把核从调度器里挖出来 → 这些核空着taskset 把你的线程放上去 → 别的东西进不来numactl 补上内存这一维 → 页面就住在 NIC 旁边它们是一套方案的三层,不是三个互相替代的选项。
第一层 —— 启动参数:挖出来并消音
Section titled “第一层 —— 启动参数:挖出来并消音”写在 GRUB_CMDLINE_LINUX;作用于整机;只能重启更改。
| 参数 | 作用 |
|---|---|
isolcpus=domain,<list> | 把这些 CPU 从「通用 SMP 均衡与调度算法」中移除——调度器永远不会把任何任务 放 上去。运行时不可逆。 |
isolcpus=managed_irq,… | 额外让 managed 类设备中断避开这些核(尽力而为)。 |
nohz_full=<list> | 停掉这些 CPU 上的调度时钟 tick——但仅当该 CPU 上 只有一个可运行任务 时生效,且残余约 1 Hz 的 tick 仍在。隐含 RCU offload。boot CPU 被强制排除。 |
rcu_nocbs=<list> | 把 RCU 回调挪到 rcuox/N 内核线程上执行(与 nohz_full 重复,但按惯例都写上——tuned 的 cpu-partitioning 配置两者都设)。 |
irqaffinity=<list> | 新分配 IRQ 的默认亲和掩码——让 非 managed 中断留在管家核上。 |
三个常被忽略的事实:
isolcpus官方已标注弃用(kernel-parameters.txt:「use cpusets instead」),却仍是最简单 可靠的选项——Red Hat 的 tunedcpu-partitioning在你启用no_balance_cores=时仍会输出它 (默认它走更软的亲和清扫——见下文 tuned 一节)。 现代等价物是 cgroup v2 isolated partition(cpuset.cpus.partition = isolated):同样效果—— 不参与负载均衡、排除在 unbound workqueue 之外——但运行时可逆。- 隔离 ≠ 加速。
isolcpus只是把核 清空。如果你从不往上放任何东西,就只是把机器变小了—— 还把所有未绑定的线程挤到了更少的核上。 - systemd 的
CPUAffinity=(/etc/systemd/system.conf)是 软 版本:所有服务继承一个排除 安静核的默认掩码,但调度器与 tick 不受影响。与启动参数 叠加 使用(tuned 正是这么做的)。
第二层 —— taskset:把线程放上挖出的核
Section titled “第二层 —— taskset:把线程放上挖出的核”运行时,按进程或按线程(-p <tid>),封装 sched_setaffinity(2)。
man 页里那条承重事实:启用 isolcpus 后,「把进程调度到被隔离 CPU 上的唯一途径是
sched_setaffinity() 或 cpuset 机制。」被隔离的核 天生是空的——taskset 是你的线程登上去的
方式,也是 唯一 能上去的方式。
塑造整套方案的两个性质:
- 亲和性会被继承(跨
fork/exec)——所以taskset -c 8-15 java …约束的是 JVM 的 每一个 线程,包括 GC 和 JIT。这反而是特性:先把整个进程关进管家核,再把 指名的热线程 一个 TID 一个 TID 地挪出去。 - 亲和性是限制,不是预留。 没有
isolcpus/cpuset 时,其他进程照样可能被调度到「你的」核上。taskset本身不隔离任何东西。
第三层 —— numactl:内存这一维
Section titled “第三层 —— numactl:内存这一维”taskset 挪的是线程;线程的 页面 住哪儿它管不着。numactl 补上 NUMA 策略:
| 选项 | 含义 |
|---|---|
--membind=<node>(-m) | 硬绑定:只从该节点分配(不够就失败,不回退) |
--preferred=<node> | 软偏好,可回退 |
--cpunodebind=<node>(-N) | 只在该节点的 CPU 上运行——节点粒度 |
--physcpubind=<cpus>(-C) | 只在指定 CPU 上运行——核粒度(等价 taskset) |
--localalloc(-l) | 在线程当前所在节点分配(默认策略) |
陷阱:--localalloc 只有在线程 同时被钉住 时才有用——在错误节点上 first-touch 的页面会永久
留在那里。而且 numactl 无法对运行中的进程改策略(taskset -p 能改活线程的核;内存得用
migratepages 或重启)。
| 启动参数 | taskset | numactl | |
|---|---|---|---|
| 何时生效 | 启动(改动需重启) | 启动或运行中(-p,按 TID) | 仅启动时 |
| 控制什么 | 调度器均衡哪些核;tick/RCU;默认 IRQ 目标 | 一个进程/线程可用哪些 CPU | CPU(节点或核粒度)以及内存放置 |
| 不做什么 | 不放置任何任务;单独使用不加速 | 不预留核;不管内存 | 不排除别人;不能改运行中的策略 |
| Aeron 用法 | 为 driver sender/receiver + 热应用线程挖核 | 按 TID 绑定 Java driver 线程 | 用 --membind 把 driver/应用按到 NIC 的节点上 |
浪费过调试时间的误解
Section titled “浪费过调试时间的误解”- 「taskset 隔离了一个核。」 不——它只限制 那个进程。排他要靠
isolcpus、isolated cgroup partition,或全机 systemdCPUAffinity=。 - 「单加 isolcpus 就能变快。」 不——它只是清空核;收益来自 组合。实测(Mark Price):
亲和 + isolcpus 把线程间最大延迟从 11.5 ms 压到 14.8 µs;IRQ 引导与
nosoftlockup再把 残余抖动从 15 µs 压到 2.5 µs。 - 「numactl 可以取代 taskset。」 部分成立:
-C在启动时是核粒度,但-N是一整个节点, 而且只有taskset -p能改 运行中 线程的归属。 - 「nohz_full 去掉了所有 tick。」 只在恰好一个可运行任务时生效,且残余约 1 Hz 仍在。
- 「isolcpus 能挡中断。」 只挡
managed_irq类,且尽力而为。ENA 的队列 IRQ 走/proc/irq/N/smp_affinity_list——要自己引导。(irqbalance 默认自动屏蔽 isolated 与 nohz_full 的核——按 ENA 指南,保留守护进程、别禁用。)
Aeron 自身提供什么
Section titled “Aeron 自身提供什么”- C driver(
aeronmd):内建按 agent 绑定 ——AERON_CONDUCTOR_CPU_AFFINITY、AERON_SENDER_CPU_AFFINITY、AERON_RECEIVER_CPU_AFFINITY(默认 −1 = 不绑定),通过 单核掩码的sched_setaffinity实现。 - Java driver:没有亲和属性。 Aeron 官方基准测试就是外部绑定的——解析名为
driver-conducto、sender、receiver的线程 TID,再taskset -p -c <core> <tid>。 (线程名被内核comm截断到 15 字符——所以是driver-conducto。) - 官方 harness 的启动行就是这套分层方案本身:
numactl --membind=$NODE --cpunodebind=$NODE --physcpubind=<管家核> <driver>,然后把热线程 逐个钉到专用的隔离核上。 - Aeron wiki 的线程指引:忙线程数 ≤ 空闲核数时用
DEDICATED模式;conductor「可以跑在脏 CPU 上」——把隔离核留给 sender/receiver 和应用的 duty-cycle 线程。
算例 —— c7i.metal 机器
Section titled “算例 —— c7i.metal 机器”96 vCPU = 48 核 × 2 超线程(CPU N 的兄弟是 N+48——用 lscpu -e 核实;NUMA 布局用
numactl --hardware 确认)。规划:CPU 2–7 隔离给 Aeron + 热应用线程,兄弟核 50–55
隔离但留空,让忙等线程独占物理核;其余全部做管家核。
# 1 · /etc/default/grub(然后 grub2-mkconfig -o /boot/grub2/grub.cfg && 重启)GRUB_CMDLINE_LINUX="... isolcpus=managed_irq,domain,2-7,50-55 \ nohz_full=2-7,50-55 rcu_nocbs=2-7,50-55 irqaffinity=0-1,8-49,56-95 \ intel_idle.max_cstate=1 processor.max_cstate=1 nosoftlockup"cat /sys/devices/system/cpu/isolated # 重启后核实:2-7,50-55# 2 · 把 ENA 队列 IRQ 引到 driver 核「附近」(而非之上)的管家核grep Tx-Rx /proc/interrupts # 找 ENA IRQecho 8-15 | sudo tee /proc/irq/<N>/smp_affinity_list # 逐个队列 IRQ# 3a · C media driver —— 内建绑定,钉上隔离核AERON_THREADING_MODE=DEDICATED \AERON_CONDUCTOR_CPU_AFFINITY=2 AERON_SENDER_CPU_AFFINITY=3 AERON_RECEIVER_CPU_AFFINITY=4 \numactl --membind=0 --cpunodebind=0 aeronmd &# 3b · Java media driver —— 先关进管家核,再按 TID 把热线程挪出去numactl --membind=0 --physcpubind=8-15 java io.aeron.driver.MediaDriver &DRIVER_PID=$!for t in "driver-conducto:2" "sender:3" "receiver:4"; do tid=$(ps Ho tid,comm -p $DRIVER_PID | awk -v n="${t%:*}" '$2~n{print $1; exit}') taskset -p -c "${t#*:}" "$tid"done# 4 · 应用 JVM:堆放在 NIC 的节点,辅助线程留在管家核,# 热 duty-cycle 线程钉到剩余隔离核numactl --membind=0 --physcpubind=8-23 java -jar app.jar &taskset -p -c 5 <producer_tid> # 或进程内用 OpenHFT Java-Thread-Affinitytaskset -p -c 6 <consumer_tid>分配核时的放置规则(哪里——见 NUMA 与缓存局部性):
producer 线程与 driver sender 留在 同一个 L3 域(它们共享 term buffer 的缓存行——Intel 上是
同 socket,AMD 上是同 CCD);两个忙等线程永远不要放在同一物理核的 HT 兄弟上;双 socket 机器先找
NIC 的节点(cat /sys/class/net/eth0/device/numa_node),再把 所有东西——隔离核、IRQ、
--membind——都放到那个节点。
不重启的运行时隔离
Section titled “不重启的运行时隔离”动不了(或不想动)GRUB?第一层的大部分能力可以在运行时获得。三个选项,从强到弱:
选项 1 —— cgroup v2 isolated partition(isolcpus 的等价物,可逆)
Section titled “选项 1 —— cgroup v2 isolated partition(isolcpus 的等价物,可逆)”# 运行时把核 2-4 划入 isolated partition——无需重启sudo mkdir -p /sys/fs/cgroup/aeronecho "+cpuset" | sudo tee /sys/fs/cgroup/cgroup.subtree_controlecho 2-4 | sudo tee /sys/fs/cgroup/aeron/cpuset.cpusecho isolated | sudo tee /sys/fs/cgroup/aeron/cpuset.cpus.partitioncat /sys/fs/cgroup/aeron/cpuset.cpus.partition # 必须读回 "isolated"——若是 "isolated invalid" # 说明 partition 条件不满足(核被兄弟 cgroup 占用)
# 把 media driver 放进去,然后照常按线程绑定echo <driver_pid> | sudo tee /sys/fs/cgroup/aeron/cgroup.procs按内核文档,isolated partition 的 CPU 不参与调度器负载均衡、排除在 unbound workqueue 之外——
功能上等同 isolcpus=domain,但可逆(echo member 撤销),而且是官方钦定的路径:isolcpus
正是 为了让位于 cpuset partition 而被弃用的。systemd 机器上建议通过 systemd 表达(下一选项),
避免两者争抢 cgroup 树。
选项 2 —— systemd CPUAffinity(软排他,两行配置)
Section titled “选项 2 —— systemd CPUAffinity(软排他,两行配置)”# /etc/systemd/system.conf——所有服务/登录会话继承一个「排除」安静核的掩码CPUAffinity=0-1,5-95
# aeron 服务的 unit 文件把核要回来:[Service]AllowedCPUs=2-4这正是 tuned cpu-partitioning 的默认做法:systemd 拉起的任何东西都上不了安静核,但这些核仍在
调度器的均衡域内,显式调用 sched_setaffinity 的进程仍能闯入。单一用途的机器上够用;重启后仍生效。
选项 3 —— 亲和清扫(蛮力,会失效)
Section titled “选项 3 —— 亲和清扫(蛮力,会失效)”for pid in $(ps -eo pid --no-headers); do sudo taskset -a -p -c 0-1,5-95 "$pid" 2>/dev/nulldonetuned 的 scheduler 插件内部就是这么做的——但手搓版有竞态:之后新生的进程继承父进程掩码,会逃逸。 要与选项 2 搭配,否则随时间失效。
任何运行时方案都给不了的东西
Section titled “任何运行时方案都给不了的东西”tick 与 RCU 消音(nohz_full、rcu_nocbs)只能在启动时设置——运行时隔离下,你的核上仍有约
250–1000 Hz 的调度 tick。并且在所有变体里,per-CPU 内核线程都赶不走、设备 IRQ 都跟着
/proc/irq/N/smp_affinity_list 走——无论选了哪种隔离机制,都要单独引导(见算例第 2 步)。
打包好的替代品:tuned cpu-partitioning
Section titled “打包好的替代品:tuned cpu-partitioning”在 RHEL 系(含 Amazon Linux)上,上面整套手工方案被打包成一个可回滚的 profile。设一个变量、重启即可:
dnf install tuned tuned-profiles-cpu-partitioningecho "isolated_cores=2-7,50-55" >> /etc/tuned/cpu-partitioning-variables.conftuned-adm profile cpu-partitioning && reboot对照 profile 源码核实,它执行的就是同一套步骤:向内核 cmdline 追加 nohz_full=/rcu_nocbs=/
nosoftlockup、引导 unbound workqueue(含 early-boot 的 dracut 钩子)、写入 systemd
CPUAffinity=、设置 IRQBALANCE_BANNED_CPUS、把 已有 进程与可移动 IRQ 清扫出隔离核,并
(通过其包含的 network-latency/latency-performance 层)把 C-state 压到 C1、设
busy_poll=50、关闭 THP 与 kernel.numa_balancing。
选用之前要知道的两件事:
- 默认它 不 用
isolcpus——排他是「软」的(亲和清扫 + systemd 默认掩码),核仍在调度器的 均衡域内,profile 也因此可以在运行时完全回滚(tuned-adm profile <other>)。要内核级的硬保证, 再设no_balance_cores=——这会加上真正的isolcpus=,并接受重启前不可逆。 - 它仍然不放置任何东西。 重启之后,第二/三层还得你自己做——绑定 driver 线程
(
AERON_*_CPU_AFFINITY/taskset -p)、numactl --membind,与上文完全一致。tuned 负责 清空与消音;它从不安置你的工作负载。
EC2 注意:该 profile 的 cmdline 含 intel_pstate=disable;想保留 intel_pstate 的团队可在子
profile(include=cpu-partitioning)里覆盖 cmdline_cpu_part。
- kernel-parameters.txt ——
isolcpus(flags 与弃用标注)、nohz_full、rcu_nocbs、irqaffinity。 - NO_HZ / adaptive ticks · per-CPU kthreads · cgroup v2 isolated partitions。
- Man 页:taskset(1) · sched_setaffinity(2)(继承性、 isolcpus 注记)· numactl(8) · cpuset(7) · systemd-system.conf(5)。
- tuned:cpu-partitioning 配置 · 其 man 页 · network-latency / latency-performance 各层。
- ENA Best Practices —— IRQ 屏蔽、taskset/numactl 引导、C-state GRUB 行。
- Mark Price,Reducing System Jitter 第 1 篇 / 第 2 篇 —— 实测数字来源。
- Aeron:Best Practices wiki ·
benchmarks harness(
pin_thread()/numactl 模式)· C driver 亲和环境变量见aeronmd.h/aeron_thread.c。