Comparison Item | cgroup v1 | cgroup v2 |
Hierarchical Structure | Multi-level structure, where the same process may appear in different cgroups simultaneously, resulting in confusing semantics. | A unified hierarchy model where all controllers share the same cgroup tree. |
Thread Granularity | Unstable control, with unpredictable behavior when parent and child cgroup threads compete for resources. | Introduces the Threaded Mode, allowing different threads of the same process to be assigned to different cgroups. |
Interface Naming | Inconsistent field units and naming (such as memory.limit_in_bytes vs cpu.cfs_quota_us), resulting in a steep learning curve. | Unified interface naming ( *.max, *.current, *.events, *.stat), with new advanced features such as io.latency and memory.pressure |
Ecosystem Status | A large user base, but systemd does not support cgroup v1 by default starting from v256. | Upstream communities and major distributions have fully transitioned to cgroup v2 (RHEL 9 uses cgroup v2 by default); TencentOS Server 4 uses cgroup v2 by default ( unified mode). |
/sys/fs/cgroup, and write operations require root.mount | grep cgroup to determine whether the current mode is cgroup2 (unified) or multiple cgroup mount points (legacy) to avoid misoperations.systemctl --version first to confirm.unified mode).no internal process constraint rule of cgroup v2 requires that when a cgroup has controllers enabled (such as memory.max), it cannot directly contain processes at the same time. Processes must be placed in leaf cgroups.threaded, it cannot be restored to domain. The parent of a threaded cgroup must be a valid domain cgroup or threaded cgroup.cpu.max, max means no limit, and $MAX=0 means CPU usage is completely prohibited. Do not mistakenly set it to 0 100000, as this will prevent processes from running.memory.swap.max to 0 completely disables swap usage, which may cause processes to be OOM-killed when memory is insufficient. Set a reasonable value based on the actual memory characteristics of your business.io.latency is an IO latency target mechanism introduced in cgroup v2. When a device exceeds the target latency, the kernel prioritizes the IO requests of this cgroup, making it suitable for latency-sensitive workloads.*.stat and *.current) are read-only. Do not attempt to write to them. memory.reclaim is a write-only interface, and writing the number of bytes triggers proactive memory reclamation.CPUQuota and MemoryLimit in existing systemd units remain valid under cgroup v2, and systemd automatically translates them to cgroup v2 interfaces. However, scripts that directly operate on /sys/fs/cgroup need to update their interface paths./etc/docker/daemon.json that no special --cgroup-driver parameter is required (under cgroup v2, systemd automatically uses the systemd driver)./sys/fs/cgroup/memory/...) have invalid paths under cgroup v2, so you need to review and update them in advance.Boot Parameter | Value | Description |
systemd.unified_cgroup_hierarchy=1 | 0/1 | 1=Use cgroup v2 (unified). 0=Use cgroup v1 (legacy). Default value: 1 (TencentOS Server 4). |
systemd.legacy_systemd_cgroup_controller=1 | 0/1 | 1=cgroup v1 controllers are managed by the systemd unified proxy. 0=cgroup v1 controllers are managed directly by the kernel. This value is valid only in legacy (cgroup v1) mode. |
SYSTEMD_CGROUP_ENABLE_LEGACY_FORCE=1 | 0/1 | 1=Force enable cgroup v1. 0=Not enforced (default). systemd v256 and later versions cannot use cgroup v1. |
Subsystem | cgroup v1 Interface | cgroup v2 Interface | Description |
cpu | cpu.cfs_quota_us + cpu.cfs_period_us | cpu.max | Merged into a single interface, with the format "$MAX $PERIOD" |
cpu | cpu.cfs_burst_us | cpu.max.burst | Burst CPU Quota |
cpu | cpu.shares | cpu.weight | Weight (1-10000), effective only when the device is busy. |
cpu | cpuacct.stat + cpuacct.usage | cpu.stat | Unified Statistics Interface |
cpu | cpu.rt_period_us + cpu.rt_runtime_us | - | Deleted. Real-time scheduling is no longer controlled by cgroup. |
cpuset | cpuset.effective_cpus | cpuset.cpus.effective | Effective CPU List |
cpuset | cpuset.effective_mems | cpuset.mems.effective | Effective Memory Node List |
cpuset | cpuset.memory_pressure | memory.pressure | Merged into the memory subsystem. |
memory | memory.limit_in_bytes | memory.max | Memory Hard Limit |
memory | memory.soft_limit_in_bytes | memory.low | Memory Soft Limit (Guaranteed Amount) |
memory | memory.usage_in_bytes | memory.current | Current Memory Usage |
memory | memory.max_usage_in_bytes | memory.peak | Historical Maximum Usage |
memory | memory.memsw.limit_in_bytes | memory.swap.max | swap Limit |
memory | memory.swappiness | - | Deleted. Use memory.swap.max instead. |
memory | memory.oom_control | - | Deleted. Use the memory.oom.kill event instead. |
memory | memory.kmem.* | - | Deleted. Kernel memory is no longer accounted for separately. |
blkio | blkio.throttle.read_bps_device and 3 others | io.max | Merged into a single interface |
blkio | blkio.throttle.io_service* | io.stat | Statistics Interface |
blkio | blkio.bfq.io_service* | - | Deleted |
blkio | - | io.latency | New: IO latency target |
hugetlb | hugetlb.<size>.usage_in_bytes | hugetlb.<size>.current | Current Usage |
hugetlb | hugetlb.<size>.limit_in_bytes | hugetlb.<size>.max | Limit |
hugetlb | hugetlb.<size>.max_usage_in_bytes | hugetlb.<size>.events | Event Notification |
rdma | - | rdma.max / rdma.current | New: RDMA resource limit |
pids | - | pids.peak | New: historical maximum number of processes |
"$MAX $PERIOD". For example, "500000 100000" means a maximum of 50 ms of CPU time within a 100 ms period (equivalent to 0.5 cores).cpu.max is in microseconds (μs). $MAX is the allowed runtime within a period, and $PERIOD is the scheduling period length. Cores = $MAX / $PERIOD. Common conversions:100000 100000 (100 ms / 100 ms)50000 100000 or 500000 1000000200000 100000max 100000 (max means no CPU usage limit, with the period still being 100 ms)<cgroup_name> is the name of the cgroup you created):cat /sys/fs/cgroup/<cgroup_name>/cpu.max
500000 1000000
echo "500000 1000000" > /sys/fs/cgroup/<cgroup_name>/cpu.max
cat /sys/fs/cgroup/<cgroup_name>/cpu.max again to confirm that the setting has taken effect. Output example:500000 1000000
echo "max 100000" > /sys/fs/cgroup/<cgroup_name>/cpu.max
cat /sys/fs/cgroup/<cgroup_name>/cpu.max. Output example:max 100000
API | Description | Behavior After Exceeded |
memory.max | Hard Limit | Trigger OOM Killer |
memory.high | Soft Limit | Triggers memory reclamation, affecting performance but not killing processes. |
memory.low | Guaranteed Amount | Does not reclaim the cgroup memory when usage is below this value. |
memory.swap.max | swap Limit | Rejects swap allocation when the limit is exceeded. |
"$MAJ:MIN rbps=<N> wbps=<N> riops=<N> wiops=<N>".echo "8:16 rbps=10485760 wbps=10485760 riops=500 wiops=500" > /sys/fs/cgroup/<cgroup_name>/io.max
cat /sys/fs/cgroup/<cgroup_name>/io.max
8:16 rbps=10485760 wbps=10485760 riops=500 wiops=500
cpu, cpuset, perf_event, and pids.API | Value | Description |
cgroup.type | domain/threaded | Read/write: Set the cgroup type. |
cp /etc/default/grub /etc/default/grub.bak.$(date +%F-%H%M);# View the current cgroup mount modemount | grep cgroup# Expected output for v2 (unified): cgroup2 on /sys/fs/cgroup type cgroup2 (...)# Expected output for v1 (legacy): cgroup on /sys/fs/cgroup/cpuset type cgroup (cpuset), cgroup on /sys/fs/cgroup/memory type cgroup (memory) ... (multiple mount points)# Further confirm cgroup v2 by using statstat -fc %T /sys/fs/cgroup/# Expected output for v2: cgroup2fs# Expected output for v1: tmpfs
mount | grep cgroup shows only one line of cgroup2 on /sys/fs/cgroup, the system is already using cgroup v2 and no migration is required. If multiple cgroup on /sys/fs/cgroup/<controller> entries are displayed, the system is using cgroup v1, and you need to proceed with the following steps.# Back up the original configurationcp /etc/default/grub /etc/default/grub.bak.$(date +%F-%H%M)# Expected output: No errors. You can run ls -l /etc/default/grub.bak.* to confirm.# Append or modify the following parameters in GRUB_CMDLINE_LINUX (ensure unified_cgroup_hierarchy=1)# Add directly with grubby (recommended to avoid manual editing errors)grubby --update-kernel=ALL --args="systemd.unified_cgroup_hierarchy=1 systemd.legacy_systemd_cgroup_controller=0"# Expected output: No errors.# Check the current kernel boot parameters to confirm that they have been appended.grubby --info=ALL | grep -E 'args|kernel'# Expected output: args should contain systemd.unified_cgroup_hierarchy=1
grubby --info=ALL | grep args should contain systemd.unified_cgroup_hierarchy=1.# Regenerate grub.cfg (use grub2-mkconfig on x86_64)grub2-mkconfig -o /boot/grub2/grub.cfg# Expected output: Generating grub configuration file ... done# Paths may differ on aarch64. Use:# grub2-mkconfig -o /etc/grub2-efi.cfg or grub2-mkconfig -o /boot/efi/EFI/centos/grub.cfg# Verify the generated result (confirm that the configuration file contains unified_cgroup_hierarchy=1)grep unified_cgroup_hierarchy /boot/grub2/grub.cfg# Expected output: systemd.unified_cgroup_hierarchy=1 can be matched.
# Restart the system (perform this within a maintenance window)sync && reboot# Expected output: The system restarts. After logging in again, perform the next verification step.
uptime to confirm that the system has started normally.# Verification 1: Verify mount (a single cgroup2 mount is expected)mount | grep cgroup# Expected output: cgroup2 on /sys/fs/cgroup type cgroup2 (...)# Verification 2: Check the file system type with statstat -fc %T /sys/fs/cgroup/# Expected output: cgroup2fs# Verification 3: Controller mount status (under v2, controllers are exposed through /sys/fs/cgroup/cgroup.controllers)cat /sys/fs/cgroup/cgroup.controllers# Expected output: cpuset cpu io memory hugetlb pids rdma (the specific list depends on the kernel configuration)# Verification 4: The v1 interface has been unmounted (subdirectories such as /sys/fs/cgroup/memory should not exist)ls /sys/fs/cgroup/ | grep -E '^(cpu|memory|blkio|cpuset)$'# Expected output: Empty (the v1 subdirectories should no longer exist)
mount | grep cgroup
cgroup2 on /sys/fs/cgroup type cgroup2 (rw,nosuid,nodev,noexec,relatime)
cgroup2 mount is returned. In legacy (cgroup v1) mode, multiple cgroup mount points (cpu, memory, ...) are returned.cat /proc/cgroups
#subsys_name hierarchy num_cgroups enabledcpuset 0 188 1cpu 0 188 1cpuacct 0 188 1blkio 0 188 1memory 0 188 1devices 0 188 1freezer 0 188 1net_cls 0 188 1perf_event 0 188 1net_prio 0 188 1hugetlb 0 188 1pids 0 188 1rdma 0 188 1misc 0 188 1
systemd.legacy_systemd_cgroup_controller=1systemd.unified_cgroup_hierarchy=0SYSTEMD_CGROUP_ENABLE_LEGACY_FORCE=1
mount | grep cgroup
cgroup on /sys/fs/cgroup/cpuset type cgroup (rw,nosuid,nodev,noexec,relatime,cpuset)cgroup on /sys/fs/cgroup/cpu type cgroup (rw,nosuid,nodev,noexec,relatime,cpu)cgroup on /sys/fs/cgroup/memory type cgroup (rw,nosuid,nodev,noexec,relatime,memory)...
systemd-cgls
Control group /:-.slice├─user.slice (#1203)│ └─user-0.slice (#41553)│ └─session-1.scope...└─system.slice (#65)├─sshd.service (#2701)│ └─674 sshd: /usr/sbin/sshd -D...
systemctl start <service_name>
systemctl set-property <service_name> CPUQuota=50% MemoryLimit=1G
systemctl show <service_name> | grep -E "CPUQuota|MemoryLimit" to view the effective limits.<cgroup_name> with the actual name, such as webapp), then set CPU, memory, and I/O limits in sequence, and add the process to the cgroup:mkdir /sys/fs/cgroup/<cgroup_name>echo "500000 1000000" > /sys/fs/cgroup/<cgroup_name>/cpu.maxecho 1073741824 > /sys/fs/cgroup/<cgroup_name>/memory.maxecho "8:16 rbps=10485760 wbps=10485760" > /sys/fs/cgroup/<cgroup_name>/io.maxecho $PID > /sys/fs/cgroup/<cgroup_name>/cgroup.procs
cat /sys/fs/cgroup/<cgroup_name>/cpu.statcat /sys/fs/cgroup/<cgroup_name>/memory.currentcat /sys/fs/cgroup/<cgroup_name>/io.stat
cpu.stat as an example, the value increases as the process runs; memory.current should be less than or equal to memory.max):usage_usec 1532600user_usec 810400system_usec 722200nr_periods 12nr_throttled 6throttled_usec 3900000
systemd-run --unit=test-cpu --slice=test-.slice -p CPUQuota=50% -p MemoryLimit=512M /bin/bash -c "while true; do true; done"
Running as unit: test-cpu.service
systemctl status test-cpusystemd-cgls test-.slice
systemctl status test-cpu:● test-cpu.service - /bin/bash -c while true; do true; doneLoaded: loaded (/run/systemd/transient/test-cpu.service; transient)Active: active (running) since Sat 2026-08-22 11:00:00 CST; 5s agoMain PID: 2475 (bash)Tasks: 1 (limit: 48621)Memory: 512.0M (max: 512.0M)CPU: 2.5s
echo threaded > /sys/fs/cgroup/<cgroup_name>/cgroup.typeecho $TID > /sys/fs/cgroup/<cgroup_name>/worker_threads/cgroup.threads
cat /sys/fs/cgroup/<cgroup_name>/cgroup.type to confirm that the type has changed. Output example:threaded
cpu.max and memory.max:cat /sys/fs/cgroup/<cgroup_name>/cpu.stat command should output statistical fields such as usage_usec, user_usec, and system_usec, and the values increase as the process runs.cat /sys/fs/cgroup/<cgroup_name>/memory.current command should output the current memory usage in bytes, which should be less than or equal to memory.max.memory.max, the oom count in memory.events should increase, and the process is killed by the OOM Killer.unified mode), and switching back to cgroup v1 is not recommended. If cgroup v1 is required, add systemd.unified_cgroup_hierarchy=0 to the GRUB boot parameters and restart the system.no internal process constraint rule of cgroup v2 requires that when a cgroup has controllers enabled (such as memory.max), it cannot directly contain processes at the same time. Processes must be placed in leaf cgroups. This is one of the biggest behavioral differences from cgroup v1.threaded, it cannot be restored to domain. The parent of a threaded cgroup must be a valid domain cgroup or threaded cgroup.cpu.max, max means no limit, and $MAX=0 means CPU usage is completely prohibited. Do not mistakenly set it to 0 100000, as this will prevent processes from running.memory.swap.max to 0 completely disables swap usage, which may cause processes to be OOM-killed when memory is insufficient. Set a reasonable value based on the actual memory characteristics of your business.io.latency is an IO latency target mechanism introduced in cgroup v2. When a device exceeds the target latency, the kernel prioritizes the IO requests of this cgroup, making it suitable for latency-sensitive workloads.*.stat and *.current) are read-only. Do not attempt to write to them. memory.reclaim is a write-only interface, and writing the number of bytes triggers proactive memory reclamation.CPUQuota and MemoryLimit in existing systemd units remain valid under cgroup v2, and systemd automatically translates them to cgroup v2 interfaces. However, scripts that directly operate on /sys/fs/cgroup need to update their interface paths./etc/docker/daemon.json that no special --cgroup-driver parameter is required (under cgroup v2, systemd automatically uses the systemd driver).systemctl --version first to confirm./sys/fs/cgroup/memory/...) have invalid paths under cgroup v2, so you need to review and update them in advance./etc/docker/daemon.json to ensure that --exec-opt native.cgroupdriver=cgroupfs is not explicitly specified (the systemd driver should be used under cgroup v2). After the upgrade, restart the container runtime: systemctl restart docker or systemctl restart containerd.memory.kmem.limit_in_bytes read/write failure or "No such file or directory"?
A: This is a kernel memory accounting interface in cgroup v1, which has been removed in cgroup v2 (kernel memory is no longer accounted for separately and is unified into memory.max). Solution: Update the application or container runtime configuration to remove references to memory.kmem.* and use memory.max to control total memory (including kernel memory).# The following commands produce no output when executed successfully.# Remove unified_cgroup_hierarchy=1 and add the v1 parameter.grubby --update-kernel=ALL --args="systemd.unified_cgroup_hierarchy=0 systemd.legacy_systemd_cgroup_controller=1 SYSTEMD_CGROUP_ENABLE_LEGACY_FORCE=1"# Regenerate the grub configuration.grub2-mkconfig -o /boot/grub2/grub.cfg# Restart.sync && reboot
mount | grep cgroup
cgroup on /sys/fs/cgroup/cpuset type cgroup (rw,nosuid,nodev,noexec,relatime,cpuset)cgroup on /sys/fs/cgroup/cpu type cgroup (rw,nosuid,nodev,noexec,relatime,cpu)...
SYSTEMD_CGROUP_ENABLE_LEGACY_FORCE=1 is set, the system can no longer be downgraded to cgroup v1. In this case, adapt your services to run in cgroup v2 compatibility mode, or replace the machine with one that has an older systemd version.echo "500000 100000" > cpu.max causes the process CPU to be completely limited and the process fails to run?
A: The parameters are misunderstood. 500000 100000 means that 500 ms of CPU time is allowed within a 100 ms period, which equals 5 cores (not 0.5 cores). To limit CPU usage to 0.5 cores, use 50000 100000 or 500000 1000000. Conversion formula: number of cores = $MAX / $PERIOD. Setting $MAX=0 completely prohibits CPU usage and is a dangerous configuration.no internal process constraint of cgroup v2. Move the processes to a leaf cgroup.+cpu +memory +io to the parent cgroup.subtree_control to re-enable them.cat /sys/fs/cgroup/cgroup.controllers to view the controllers that can be enabled.cat /sys/fs/cgroup/cgroup.subtree_control to view the controllers enabled on the parent (and compare them with the list of controllers that can be enabled to check whether any are missing).echo "+cpu +memory +io" > /sys/fs/cgroup/cgroup.subtree_control
cat /sys/fs/cgroup/cgroup.subtree_control again to confirm that the output contains cpu memory io.stat -fc %T /sys/fs/cgroup/ outputs tmpfs instead of cgroup2fs?
A: The system is still in cgroup v1 mode, and the migration has not succeeded. After the migration is complete, this command should return cgroup2fs (cgroup v1 mode returns tmpfs). Troubleshoot the issue in the following areas:grubby --info=ALL | grep args to confirm that systemd.unified_cgroup_hierarchy=1 has been added to the kernel boot parameters.grub2-mkconfig -o /boot/grub2/grub.cfg to regenerate the configuration and have rebooted the system.systemctl --version to confirm that the systemd version is 239 or later.피드백