[PATCH 27/36] tuna: Add --cpuset option to isolate command
John Kacur <[email protected]> Fri, 10 Jul 2026 10:15:05 -0400
| Newsgroups | org.kernel.vger.linux-rt-users |
|---|---|
| Message-ID | <[email protected]> |
Add support for cpuset-based CPU isolation using cgroup v2 isolated
partitions. This provides stronger, kernel-enforced CPU isolation
compared to traditional affinity-based isolation.
The new --cpuset option creates an isolated cpuset (partition=isolated)
for RT applications after performing traditional affinity-based isolation.
An optional --cpuset-housekeeping option creates a second isolated cpuset
and migrates non-blocklisted processes from the root cgroup to it.
This implements a 3-tier CPU model:
- Isolated cpuset: CPUs for RT applications (kernel-enforced exclusive)
- Housekeeping cpuset: CPUs for system tasks (optional, isolated partition)
- Root cgroup: Remaining CPUs for normal workloads
Features:
- NUMA-aware memory node assignment for both cpusets
- Automatic process migration with blocklist protection
- Backward compatible (opt-in via --cpuset flag)
- Customizable cpuset names
Usage:
tuna isolate -c 0-3 --cpuset
Creates tuna_isolated cpuset with partition=isolated
tuna isolate -c 0-3 --cpuset my_rt
Creates my_rt cpuset with custom name
tuna isolate -c 0-3 --cpuset --cpuset-housekeeping 4-7
Creates both tuna_isolated and tuna_housekeeping cpusets,
migrates processes to housekeeping
tuna isolate -c 0-3 --cpuset my_rt --cpuset-housekeeping my_hk 4-7
Custom names for both cpusets
Both isolated and housekeeping cpusets use partition=isolated for
kernel-enforced CPU exclusivity. Critical system processes (systemd,
systemd-logind, etc.) are protected by the existing blocklist and
remain in the root cgroup.
Assisted-by: Claude:claude-sonnet-4-5
Signed-off-by: John Kacur <[email protected]>
---
tuna-cmd.py | 69 +++++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 69 insertions(+)
diff --git a/tuna-cmd.py b/tuna-cmd.py
index b50691fa2d05..ce34aee8e909 100755
--- a/tuna-cmd.py
+++ b/tuna-cmd.py
@@ -182,6 +182,11 @@ def gen_parser():
isolate_group.add_argument('-c', '--cpus', **MODS['cpus'])
isolate_group.add_argument('-S', '--sockets', **MODS['sockets'])
isolate_group.add_argument('-N', '--nohz_full', **MODS['nohz_full'])
+ isolate.add_argument('--cpuset', type=str, nargs='?', const='tuna_isolated',
+ metavar='NAME', default=None,
+ help='Use cpusets for isolation with optional name (default: tuna_isolated, partition=isolated)')
+ isolate.add_argument('--cpuset-housekeeping', nargs='+', metavar='...',
+ help='Create housekeeping cpuset: [NAME] CPU-LIST (default name: tuna_housekeeping, partition=isolated)')
include_group = include.add_mutually_exclusive_group(required=True)
include_group.add_argument('-c', '--cpus', **MODS['cpus'])
@@ -1613,8 +1618,72 @@ def main():
tuna.include_cpus(args.cpu_list, utils.get_nr_cpus())
elif args.command in ['isolate', 'i']:
+ # Parse --cpuset-housekeeping arguments
+ housekeeping_name = None
+ housekeeping_cpus = None
+
+ if args.cpuset_housekeeping:
+ # Validate that --cpuset is also specified
+ if not args.cpuset:
+ print("Error: --cpuset-housekeeping requires --cpuset", file=sys.stderr)
+ sys.exit(1)
+
+ # Parse: [NAME] CPU-LIST or just CPU-LIST
+ if len(args.cpuset_housekeeping) == 1:
+ housekeeping_name = 'tuna_housekeeping'
+ housekeeping_cpus = args.cpuset_housekeeping[0]
+ elif len(args.cpuset_housekeeping) == 2:
+ housekeeping_name = args.cpuset_housekeeping[0]
+ housekeeping_cpus = args.cpuset_housekeeping[1]
+ else:
+ print("Error: --cpuset-housekeeping takes 1 or 2 arguments: [NAME] CPU-LIST", file=sys.stderr)
+ sys.exit(1)
+
+ # Traditional affinity-based isolation (existing behavior)
tuna.isolate_cpus(args.cpu_list, utils.get_nr_cpus())
+ # Create cpusets if requested
+ if args.cpuset:
+ try:
+ # Check cgroup v2 support
+ cpusets_init = cpuset.CpusetsInit()
+ if not cpusets_init.supported:
+ print("Error: cgroup v2 cpuset controller not available", file=sys.stderr)
+ sys.exit(1)
+
+ # Get NUMA nodes for isolated CPUs
+ isolated_numa = cpuset.get_numa_nodes_for_cpus(args.cpu_list)
+
+ # Create isolated cpuset (partition=isolated, empty)
+ print(f"Creating isolated cpuset '{args.cpuset}' with CPUs {tuna.collapse_cpulist(args.cpu_list)}")
+ isolated_cpuset = cpuset.Cpuset(args.cpuset)
+ isolated_cpuset.write_memnode(isolated_numa)
+ isolated_cpuset.assign_cpus(tuna.collapse_cpulist(args.cpu_list))
+ isolated_cpuset.write_cpu_exclusive(True) # partition=isolated
+ print(f"Isolated cpuset '{args.cpuset}' created successfully")
+
+ # Create housekeeping cpuset if requested
+ if housekeeping_cpus:
+ # Parse housekeeping CPU list from string to list
+ hk_cpu_list = tuna.cpustring_to_list(housekeeping_cpus)
+ hk_numa = cpuset.get_numa_nodes_for_cpus(hk_cpu_list)
+
+ print(f"Creating housekeeping cpuset '{housekeeping_name}' with CPUs {housekeeping_cpus}")
+ hk_cpuset = cpuset.Cpuset(housekeeping_name)
+ hk_cpuset.write_memnode(hk_numa)
+ hk_cpuset.assign_cpus(housekeeping_cpus)
+ hk_cpuset.write_cpu_exclusive(True) # partition=isolated
+
+ # Migrate processes from root to housekeeping
+ print(f"Migrating processes to housekeeping cpuset...")
+ tm = cpuset.TaskMigrate(cpusets_init, hk_cpuset)
+ migrated, failed = tm.migrate()
+ print(f"Migrated {migrated} processes to '{housekeeping_name}' (failed: {failed})")
+
+ except Exception as e:
+ print(f"Error creating cpusets: {e}", file=sys.stderr)
+ sys.exit(1)
+
elif args.command in ['run', 'r']:
tuna.run_command(args.run_command, args.priority[0], args.priority[1], args.cpu_list, args.background)
--
2.54.0