cpuset是唯一能硬性绑定进程到指定物理CPU核心的机制,因其通过cpuset.cpus强制调度器仅在授权核心上fork和migrate进程,而taskset等仅为软限制。

为什么 cpuset 是唯一能真正绑定物理核心的机制
Linux 中 cgroup v1 的 cpuset 子系统(或 cgroup v2 的 cpuset controller)是唯一能将进程**硬性限制在指定物理 CPU 核心上运行**的机制。其他方式如 taskset 或容器启动时的 --cpus(基于 cpu controller 的权重/配额)都只是软限制:前者可被调度器绕过,后者根本不限制核心范围,只控制 CPU 时间占比。
关键点在于:cpuset.cpus 写入的是实际的物理 CPU 编号(从 0 开始),且内核会强制调度器只在这些核心上 fork 和 migrate 进程——哪怕目标核心正忙,也不会跨到未授权核心上执行。
手动创建并配置 cpuset cgroup(v1 示例)
假设目标物理核心为 2 和 3(需先用 lscpu 或 cat /proc/cpuinfo | grep "processor" 确认编号),操作如下:
mkdir -p /sys/fs/cgroup/cpuset/mycontainer echo 2-3 > /sys/fs/cgroup/cpuset/mycontainer/cpuset.cpus echo 0 > /sys/fs/cgroup/cpuset/mycontainer/cpuset.mems # 必须设置 mems,即使单 NUMA 节点
注意:cpuset.mems 不可为空,否则写入 tasks 会失败;若机器有多个 NUMA 节点,需填对应内存节点编号(如 0-1)。
将已有进程(如容器主进程 PID=1234)绑定过去:
echo 1234 > /sys/fs/cgroup/cpuset/mycontainer/tasks
验证是否生效:
taskset -p 1234 # 输出应为 0x0000000c(即二进制 ...001100,对应 core 2&3) cat /proc/1234/status | grep Cpus_allowed_list # 应显示 2-3
Docker 容器启动时直接应用 cpuset
Docker 默认使用 cgroup v2(现代发行版),但命令行参数对 cpuset 的支持一致。启动容器时传入:
docker run --cpuset-cpus="2,3" --cpuset-mems="0" nginx
等价于在 cgroup v2 下自动创建 controller 并写入 cpuset.cpus 和 cpuset.mems。注意:
-
--cpuset-cpus支持逗号分隔("2,3")或连字符("2-3"),但不能混用 -
--cpuset-mems在单 NUMA 节点机器上填0即可;多节点需匹配cpuset.cpus所在节点(用numactl --hardware查) - 该参数**不兼容**
--platform或某些 rootless 模式,若报错invalid argument,先检查/sys/fs/cgroup/cgroup.controllers是否启用了cpuset
常见失效原因和排查要点
绑定后仍看到进程跑在其他核心上?大概率是以下情况之一:
- 容器内进程主动调用
sched_setaffinity()(如某些 Java JVM 启动参数、Golang 的GOMAXPROCS设置),覆盖了 cgroup 限制——需在容器内禁用或对齐 -
cpuset.mems设置错误(比如填了不存在的节点号),导致内核静默忽略cpuset.cpus限制 - 使用了
systemd管理容器(如 Podman + systemd),需确认.service文件中未通过AllowedCPUs=等参数二次干预 - 宿主机启用了 CPU 频率调节器(如
ondemand),虽不影响核心绑定,但可能让top显示“CPU 利用率低”而误判未生效——应以taskset -p或/proc/PID/status为准
最可靠的验证永远是读 /proc/[pid]/status 里的 Cpus_allowed_list,而不是依赖监控工具的采样结果。


















