cgroup: Add CPUSetPartition= setting

Add support for configuring cpuset partition type via the
CPUSetPartition= unit file setting. This controls the kernel's
cpuset.cpus.partition cgroup attribute.

The setting takes one of "member", "root", or "isolated". This is
useful for real-time workloads that require dedicated CPU resources
without interference from other processes.

When set, systemd will write the partition type to the
cpuset.cpus.partition cgroup file. If the kernel rejects the value
(e.g., due to partition hierarchy rules), a warning is logged and the
unit continues with the kernel's default partition type.

Co-developed-by: Claude Sonnet 4.5 <noreply@anthropic.com>
Signed-off-by: Gabriele Monaco <gmonaco@redhat.com>
This commit is contained in:
glemco
2026-05-14 08:55:44 +02:00
parent 8e6ad359e3
commit 43b53679da
14 changed files with 209 additions and 12 deletions
+62
View File
@@ -178,6 +178,8 @@ void cgroup_context_init(CGroupContext *c) {
.tasks_max = CGROUP_TASKS_MAX_UNSET,
.cpuset_partition = _CPUSET_PARTITION_INVALID,
.moom_swap = MANAGED_OOM_AUTO,
.moom_mem_pressure = MANAGED_OOM_AUTO,
.moom_preference = MANAGED_OOM_PREFERENCE_NONE,
@@ -508,6 +510,7 @@ void cgroup_context_dump(Unit *u, FILE* f, const char *prefix) {
"%sStartupAllowedCPUs: %s\n"
"%sAllowedMemoryNodes: %s\n"
"%sStartupAllowedMemoryNodes: %s\n"
"%sCPUSetPartition: %s\n"
"%sIOWeight: %" PRIu64 "\n"
"%sStartupIOWeight: %" PRIu64 "\n"
"%sMemoryMin: %" PRIu64 "%s\n"
@@ -546,6 +549,7 @@ void cgroup_context_dump(Unit *u, FILE* f, const char *prefix) {
prefix, strempty(startup_cpuset_cpus),
prefix, strempty(cpuset_mems),
prefix, strempty(startup_cpuset_mems),
prefix, strna(cpuset_partition_to_string(c->cpuset_partition)),
prefix, c->io_weight,
prefix, c->startup_io_weight,
prefix, c->memory_min, format_cgroup_memory_limit_comparison(u, "MemoryMin", cda, sizeof(cda)),
@@ -1132,6 +1136,53 @@ static void cgroup_apply_cpuset(Unit *u, const CPUSet *cpus, const char *name) {
(void) set_attribute_and_warn(u, name, buf);
}
static int cgroup_cpuset_partition_invalid(const char *partition) {
_cleanup_free_ char *part_str = NULL, *invalid = NULL;
int r;
assert(partition);
/* An invalid line looks like <partition> invalid (<reason>) */
r = extract_many_words(&partition, /* separators= */ NULL, /* flags= */ 0, &part_str, &invalid);
if (r < 0)
return r;
if (r < 2)
return false;
return streq_ptr(invalid, "invalid");
}
static void cgroup_apply_cpuset_partition(Unit *u, const char *name, const char *partition) {
_cleanup_free_ char *buf = NULL;
CGroupRuntime *crt;
int r;
assert(u);
assert(name);
assert(partition);
if (set_attribute_and_warn(u, name, partition) < 0)
return;
/* We are writing and then reading back, crt is already checked while writing */
crt = ASSERT_PTR(unit_get_cgroup_runtime(u));
r = cg_get_attribute(crt->cgroup_path, name, &buf);
if (r < 0) {
log_unit_full_errno(u, LOG_LEVEL_CGROUP_WRITE(r), r, "Failed to read back '%s' attribute on '%s' as '%.*s': %m",
name, empty_to_root(crt->cgroup_path), (int) strcspn(partition, NEWLINE), partition);
return;
}
r = cgroup_cpuset_partition_invalid(buf);
if (r < 0)
log_unit_full_errno(u, LOG_LEVEL_CGROUP_WRITE(r), r, "Failed to read back '%s' attribute on '%s' as '%.*s': %m",
name, empty_to_root(crt->cgroup_path), (int) strcspn(partition, NEWLINE), partition);
else if (r)
log_unit_warning(u, "Failed to set '%s' attribute on '%s' to '%.*s': %s",
name, empty_to_root(crt->cgroup_path), (int) strcspn(partition, NEWLINE), partition, buf);
}
static bool cgroup_context_has_io_config(CGroupContext *c) {
assert(c);
@@ -1464,6 +1515,9 @@ static void cgroup_context_apply(
if ((apply_mask & CGROUP_MASK_CPUSET) && !is_local_root) {
cgroup_apply_cpuset(u, cgroup_context_allowed_cpus(c, state), "cpuset.cpus");
cgroup_apply_cpuset(u, cgroup_context_allowed_mems(c, state), "cpuset.mems");
if (c->cpuset_partition >= 0)
cgroup_apply_cpuset_partition(u, "cpuset.cpus.partition", cpuset_partition_to_string(c->cpuset_partition));
}
/* The 'io' controller attributes are not exported on the host's root cgroup (being a pure cgroup v2
@@ -4582,6 +4636,14 @@ static const char* const cgroup_device_policy_table[_CGROUP_DEVICE_POLICY_MAX] =
DEFINE_STRING_TABLE_LOOKUP(cgroup_device_policy, CGroupDevicePolicy);
static const char* const cpuset_partition_table[_CPUSET_PARTITION_MAX] = {
[CPUSET_PARTITION_MEMBER] = "member",
[CPUSET_PARTITION_ROOT] = "root",
[CPUSET_PARTITION_ISOLATED] = "isolated",
};
DEFINE_STRING_TABLE_LOOKUP(cpuset_partition, CPUSetPartition);
static const char* const cgroup_pressure_watch_table[_CGROUP_PRESSURE_WATCH_MAX] = {
[CGROUP_PRESSURE_WATCH_NO] = "no",
[CGROUP_PRESSURE_WATCH_YES] = "yes",
+10
View File
@@ -47,6 +47,14 @@ typedef enum FreezerAction {
_FREEZER_ACTION_INVALID = -EINVAL,
} FreezerAction;
typedef enum CPUSetPartition {
CPUSET_PARTITION_MEMBER,
CPUSET_PARTITION_ROOT,
CPUSET_PARTITION_ISOLATED,
_CPUSET_PARTITION_MAX,
_CPUSET_PARTITION_INVALID = -EINVAL,
} CPUSetPartition;
typedef enum CGroupDevicePermissions {
/* We reuse the same bit meanings the kernel's BPF_DEVCG_ACC_xyz definitions use */
CGROUP_DEVICE_MKNOD = 1 << 0,
@@ -136,6 +144,7 @@ typedef struct CGroupContext {
CPUSet startup_cpuset_cpus;
CPUSet cpuset_mems;
CPUSet startup_cpuset_mems;
CPUSetPartition cpuset_partition;
uint64_t io_weight;
uint64_t startup_io_weight;
@@ -478,6 +487,7 @@ int unit_get_cpuset(Unit *u, CPUSet *cpus, const char *name);
int unit_cgroup_freezer_action(Unit *u, FreezerAction action);
DECLARE_STRING_TABLE_LOOKUP(freezer_action, FreezerAction);
DECLARE_STRING_TABLE_LOOKUP(cpuset_partition, CPUSetPartition);
CGroupRuntime* cgroup_runtime_new(void);
CGroupRuntime* cgroup_runtime_free(CGroupRuntime *crt);
+31
View File
@@ -26,6 +26,7 @@ BUS_DEFINE_PROPERTY_GET(bus_property_get_tasks_max, "t", CGroupTasksMax, cgroup_
BUS_DEFINE_PROPERTY_GET_ENUM(bus_property_get_cgroup_pressure_watch, cgroup_pressure_watch, CGroupPressureWatch);
static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_cgroup_device_policy, cgroup_device_policy, CGroupDevicePolicy);
static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_cpuset_partition, cpuset_partition, CPUSetPartition);
static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_managed_oom_mode, managed_oom_mode, ManagedOOMMode);
static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_managed_oom_preference, managed_oom_preference, ManagedOOMPreference);
@@ -385,6 +386,7 @@ const sd_bus_vtable bus_cgroup_vtable[] = {
SD_BUS_PROPERTY("StartupAllowedCPUs", "ay", property_get_cpuset, offsetof(CGroupContext, startup_cpuset_cpus), 0),
SD_BUS_PROPERTY("AllowedMemoryNodes", "ay", property_get_cpuset, offsetof(CGroupContext, cpuset_mems), 0),
SD_BUS_PROPERTY("StartupAllowedMemoryNodes", "ay", property_get_cpuset, offsetof(CGroupContext, startup_cpuset_mems), 0),
SD_BUS_PROPERTY("CPUSetPartition", "s", property_get_cpuset_partition, offsetof(CGroupContext, cpuset_partition), 0),
SD_BUS_PROPERTY("IOAccounting", "b", bus_property_get_bool, offsetof(CGroupContext, io_accounting), 0),
SD_BUS_PROPERTY("IOWeight", "t", NULL, offsetof(CGroupContext, io_weight), 0),
SD_BUS_PROPERTY("StartupIOWeight", "t", NULL, offsetof(CGroupContext, startup_io_weight), 0),
@@ -1493,6 +1495,35 @@ int bus_cgroup_set_property(
return 1;
} else if (streq(name, "CPUSetPartition")) {
const char *partition_str;
CPUSetPartition p;
r = sd_bus_message_read(message, "s", &partition_str);
if (r < 0)
return r;
if (isempty(partition_str))
p = _CPUSET_PARTITION_INVALID;
else {
p = cpuset_partition_from_string(partition_str);
if (p < 0)
return sd_bus_error_setf(reterr_error, SD_BUS_ERROR_INVALID_ARGS,
"Invalid CPUSetPartition value: %s", partition_str);
}
if (!UNIT_WRITE_FLAGS_NOOP(flags)) {
c->cpuset_partition = p;
unit_invalidate_cgroup(u, CGROUP_MASK_CPUSET);
if (p == _CPUSET_PARTITION_INVALID)
unit_write_settingf(u, flags, name, "%s=", name);
else
unit_write_settingf(u, flags, name, "%s=%s", name, partition_str);
}
return 1;
} else if (streq(name, "DeviceAllow")) {
const char *path, *rwm;
unsigned n = 0;
+10
View File
@@ -121,6 +121,12 @@ static int exec_cgroup_context_serialize(const CGroupContext *c, FILE *f) {
if (r < 0)
return r;
if (c->cpuset_partition >= 0) {
r = serialize_item(f, "exec-cgroup-context-cpuset-partition", cpuset_partition_to_string(c->cpuset_partition));
if (r < 0)
return r;
}
if (c->io_weight != CGROUP_WEIGHT_INVALID) {
r = serialize_item_format(f, "exec-cgroup-context-io-weight", "%" PRIu64, c->io_weight);
if (r < 0)
@@ -513,6 +519,10 @@ static int exec_cgroup_context_deserialize(CGroupContext *c, FILE *f) {
r = parse_cpu_set(val, &c->startup_cpuset_mems);
if (r < 0)
return r;
} else if ((val = startswith(l, "exec-cgroup-context-cpuset-partition="))) {
c->cpuset_partition = cpuset_partition_from_string(val);
if (c->cpuset_partition < 0)
return -EINVAL;
} else if ((val = startswith(l, "exec-cgroup-context-io-weight="))) {
r = safe_atou64(val, &c->io_weight);
if (r < 0)
+1
View File
@@ -215,6 +215,7 @@
{{type}}.StartupAllowedCPUs, config_parse_unit_cpu_set, 0, offsetof({{type}}, cgroup_context.startup_cpuset_cpus)
{{type}}.AllowedMemoryNodes, config_parse_unit_cpu_set, 0, offsetof({{type}}, cgroup_context.cpuset_mems)
{{type}}.StartupAllowedMemoryNodes, config_parse_unit_cpu_set, 0, offsetof({{type}}, cgroup_context.startup_cpuset_mems)
{{type}}.CPUSetPartition, config_parse_cpuset_partition, 0, offsetof({{type}}, cgroup_context.cpuset_partition)
{{type}}.CPUAccounting, config_parse_warn_compat, DISABLED_LEGACY, 0
{{type}}.CPUWeight, config_parse_cg_cpu_weight, 0, offsetof({{type}}, cgroup_context.cpu_weight)
{{type}}.StartupCPUWeight, config_parse_cg_cpu_weight, 0, offsetof({{type}}, cgroup_context.startup_cpu_weight)
+1
View File
@@ -131,6 +131,7 @@ DEFINE_CONFIG_PARSE(config_parse_socket_protocol, parse_socket_protocol);
DEFINE_CONFIG_PARSE(config_parse_exec_secure_bits, secure_bits_from_string);
DEFINE_CONFIG_PARSE_ENUM(config_parse_collect_mode, collect_mode, CollectMode);
DEFINE_CONFIG_PARSE_ENUM(config_parse_device_policy, cgroup_device_policy, CGroupDevicePolicy);
DEFINE_CONFIG_PARSE_ENUM_WITH_DEFAULT(config_parse_cpuset_partition, cpuset_partition, CPUSetPartition, _CPUSET_PARTITION_INVALID);
DEFINE_CONFIG_PARSE_ENUM(config_parse_exec_keyring_mode, exec_keyring_mode, ExecKeyringMode);
DEFINE_CONFIG_PARSE_ENUM(config_parse_protect_proc, protect_proc, ProtectProc);
DEFINE_CONFIG_PARSE_ENUM(config_parse_proc_subset, proc_subset, ProcSubset);
+1
View File
@@ -170,6 +170,7 @@ CONFIG_PARSER_PROTOTYPE(config_parse_mount_node);
CONFIG_PARSER_PROTOTYPE(config_parse_concurrency_max);
CONFIG_PARSER_PROTOTYPE(config_parse_bind_network_interface);
CONFIG_PARSER_PROTOTYPE(config_parse_exec_memory_thp);
CONFIG_PARSER_PROTOTYPE(config_parse_cpuset_partition);
/* gperf prototypes */
const struct ConfigPerfItem* load_fragment_gperf_lookup(const char *key, GPERF_LEN_TYPE length);
+1
View File
@@ -258,6 +258,7 @@ int unit_cgroup_context_build_json(sd_json_variant **ret, const char *name, void
JSON_BUILD_PAIR_FINITE_USEC("CPUQuotaPeriodUSec", c->cpu_quota_period_usec),
JSON_BUILD_PAIR_CALLBACK_NON_NULL("AllowedCPUs", cpuset_build_json, &c->cpuset_cpus),
JSON_BUILD_PAIR_CALLBACK_NON_NULL("StartupAllowedCPUs", cpuset_build_json, &c->startup_cpuset_cpus),
JSON_BUILD_PAIR_ENUM("CPUSetPartition", cpuset_partition_to_string(c->cpuset_partition)),
/* Memory Accounting and Control */
SD_JSON_BUILD_PAIR_BOOLEAN("MemoryAccounting", c->memory_accounting),