cgroups 无法直接限制“虚拟机”抽象概念的磁盘写速,只能限制其宿主机进程(如 qemu-system-x86_64)对物理块设备的 I/O;需将 QEMU 进程纳入 cgroup v2 io.max 组,按设备主次号(如 8:0)设置 wbps 字节速率限速,并配置 QEMU cache='none' 才能确保生效。

cgroups 不能直接限制“虚拟机”这个抽象概念的磁盘写入速度——它限制的是进程或进程组对块设备的 I/O 行为。而虚拟机(如 QEMU/KVM)本质是宿主机上的一个或多个用户态进程(qemu-system-x86_64),其磁盘 I/O 最终由这些进程发起。所以真正要做的,是把 QEMU 进程纳入 cgroup v2 的 io.max 控制器下,并针对其实际访问的块设备设写入带宽上限。
确认宿主机启用 cgroup v2 io 控制器
cgroup v1 的 blkio.throttle.write_iops_device 在现代内核(≥5.0)和默认调度器(kyber、mq-deadline)下基本不生效,且无 IOPS 硬限能力;只有 cgroup v2 的 io.max 是内核级、可靠、默认启用的方案。
- 运行
mount | grep cgroup2,输出中必须含type cgroup2且挂载点为/sys/fs/cgroup - 运行
cat /proc/cgroups | awk '$1=="io" && $4==1',有输出才表示io控制器已启用 - 若无输出,需在内核启动参数中添加
cgroup_no_v1=all并重启
查准虚拟机实际使用的块设备主次号
QEMU 虚拟机磁盘后端可能是文件(如 /var/lib/libvirt/images/win10.qcow2)或裸设备(如 /dev/sdb)。但 io.max 只认底层物理块设备,不是文件路径。
- 如果后端是文件:用
ls -l /dev/disk/by-path/或findmnt -D /var/lib/libvirt/images找到该文件所在分区对应的物理设备(如/dev/sda2) - 然后查主次号:
ls -l /dev/sda→ 输出类似brw-rw---- 1 root disk 8, 0,即设备号为8:0 - 如果后端是直通 NVMe 设备:
ls -l /dev/nvme0n1→ 主次号通常是259:0
⚠️ 填错设备号(如把 /dev/sda2 当成 8:2)会导致限速完全无效——Linux 中分区没有独立主次号,8:2 不存在,正确的是整个盘的号 8:0
创建 cgroup 并设置 wbps 限速值
不要试图设 “IOPS”,io.max 不统计完成请求数,max_iops_per_second 字段只是调度提示,不可靠。唯一可硬限、内核强制拦截的是字节速率(wbps)。
- 创建组:
sudo mkdir -p /sys/fs/cgroup/vm-write-limit - 写入限速规则(例如限制为 30 MB/s):
echo "8:0 wbps=31457280" | sudo tee /sys/fs/cgroup/vm-write-limit/io.max - 注意单位:30 × 1024 × 1024 = 31,457,280 字节/秒
如果虚拟机同时读多写少,可追加读限:echo "8:0 rbps=10485760 wbps=31457280" | sudo tee /sys/fs/cgroup/vm-write-limit/io.max
将 QEMU 进程 PID 加入该 cgroup
Libvirt 默认不会把虚拟机进程自动放进自定义 cgroup,必须手动迁移:
- 查找目标虚拟机的 QEMU 进程 PID:
pgrep -f "qemu.*win10"或virsh list --all后用virsh dompid win10 - 迁移(注意:进程必须未被其他
iocgroup 管理):echo $PID | sudo tee /sys/fs/cgroup/vm-write-limit/cgroup.procs - 验证归属:
cat /proc/$PID/cgroup | grep io应看到vm-write-limit路径
多线程 QEMU 进程会整体受限;若失败,检查 /sys/fs/cgroup/*/cgroup.procs 是否已有该 PID(一个进程只能在一个 io cgroup 中)
验证是否生效时,别只看 iostat -x 1 /dev/sda 的平均 wkB/s——它受缓存、合并、队列深度干扰太大。真正可靠的手段是:
- 直接读
io.stat:cat /sys/fs/cgroup/vm-write-limit/io.stat | grep wbps,看输出是否稳定接近你设的值 - 用 direct I/O 模拟压测:
dd if=/dev/zero of=/var/lib/libvirt/images/test bs=1M count=1000 oflag=direct conv=fdatasync,观察该 cgroup 下的wbps统计是否顶格
最易被忽略的一点:QEMU 默认使用缓存(cache=writeback),大量写先落页缓存,不触发限速。务必在虚拟机 XML 中显式配置 cache='none' 或 cache='directsync',否则限速形同虚设。


















