[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