This commit is contained in:
Mark Brown
2026-07-31 16:04:09 +01:00
16 changed files with 770 additions and 243 deletions
@@ -10,7 +10,7 @@ Because VM is getting complex (one of reasons is memcg...), memcg's behavior
is complex. This is a document for memcg's internal behavior.
Please note that implementation details can be changed.
(*) Topics on API should be in Documentation/admin-guide/cgroup-v1/memory.rst)
(*) Topics on API should be in Documentation/admin-guide/cgroup-v1/memory.rst
0. How to record usage ?
========================
+34 -6
View File
@@ -1145,7 +1145,7 @@ will be referred to. All time durations are in microseconds.
This file exists whether the controller is enabled or not.
It always reports the following three stats, which account for all the
processes in the cgroup:
processes in the cgroup (including those in descendant cgroups):
- usage_usec
- user_usec
@@ -1160,6 +1160,27 @@ will be referred to. All time durations are in microseconds.
- nr_bursts
- burst_usec
Note that the above five CFS bandwidth stats are non-hierarchical;
they only account for throttling caused by this cgroup's own bandwidth
limit, not including throttling inherited from ancestor cgroups.
cpu.stat.local
A read-only flat-keyed file.
This file exists whether the controller is enabled or not.
It reports the following stat when the controller is enabled:
- throttled_usec
Unlike the ``throttled_usec`` reported by ``cpu.stat`` which
accounts for throttling caused by this cgroup's own CFS
bandwidth limit, ``cpu.stat.local`` reports the actual
throttling time incurred by this cgroup's own runqueues,
which may include throttling inherited from ancestor
cgroup bandwidth limits.
When the controller is not enabled, this stat is not reported.
cpu.weight
A read-write single value file which exists on non-root
cgroups. The default is "100".
@@ -1909,7 +1930,7 @@ The following nested keys are defined.
is allowed unless memory.swap.max is set to 0.
memory.pressure
A read-only nested-keyed file.
A read-write nested-keyed file.
Shows pressure stall information for memory. See
:ref:`Documentation/accounting/psi.rst <psi>` for details.
@@ -2169,7 +2190,7 @@ IO Interface Files
8:16 rbps=2097152 wbps=max riops=max wiops=max
io.pressure
A read-only nested-keyed file.
A read-write nested-keyed file.
Shows pressure stall information for IO. See
:ref:`Documentation/accounting/psi.rst <psi>` for details.
@@ -2279,9 +2300,9 @@ This throttling takes 2 forms:
throttled without possibly adversely affecting higher priority groups. This
includes swapping and metadata IO. These types of IO are allowed to occur
normally, however they are "charged" to the originating group. If the
originating group is being throttled you will see the use_delay and delay
fields in io.stat increase. The delay value is how many microseconds that are
being added to any process that runs in this group. Because this number can
originating group is being throttled you will see the use_delay and delay_nsec
fields in io.stat increase. The delay_nsec value is how many nanoseconds that
are being added to any process that runs in this group. Because this number can
grow quite large if there is a lot of swapping or metadata IO occurring we
limit the individual delay events to 1 second at a time.
@@ -2530,6 +2551,13 @@ Cpuset Interface Files
a need to change "cpuset.mems" with active tasks, it shouldn't
be done frequently.
For a multithreaded process, the threadgroup leader is
considered the owner of the group's memory. Memory policy
rebinding and migration will only happen with respect to the
threadgroup leader. To avoid unexpected results, non-leading
threads shouldn't be put into another cgroup whose "cpuset.mems"
doesn't fully overlap that of the threadgroup leader.
cpuset.mems.effective
A read-only multiple values file which exists on all
cpuset-enabled cgroups.
+1 -1
View File
@@ -896,7 +896,7 @@ static inline void cgroup_threadgroup_change_begin(struct task_struct *tsk)
* cgroup_threadgroup_change_end - threadgroup exclusion for cgroups
* @tsk: target task
*
* Counterpart of cgroup_threadcgroup_change_begin().
* Counterpart of cgroup_threadgroup_change_begin().
*/
static inline void cgroup_threadgroup_change_end(struct task_struct *tsk)
{
+1 -1
View File
@@ -506,7 +506,7 @@ static inline void cgroup_unlock(void)
rcu_read_lock_sched_held() || \
lockdep_is_held(&cgroup_mutex) || \
lockdep_is_held(&css_set_lock) || \
((task)->flags & PF_EXITING) || (__c))
(data_race((task)->flags) & PF_EXITING) || (__c))
#else
#define task_css_set_check(task, __c) \
rcu_dereference((task)->cgroups)
+3 -1
View File
@@ -25,7 +25,9 @@ void psi_memstall_leave(unsigned long *flags);
int psi_show(struct seq_file *s, struct psi_group *group, enum psi_res res);
struct psi_trigger *psi_trigger_create(struct psi_group *group, char *buf,
enum psi_res res, struct file *file,
struct kernfs_open_file *of);
struct kernfs_open_file *of,
bool *need_rtpoll_worker);
int psi_trigger_create_rtpoll_worker(struct psi_group *group);
void psi_trigger_destroy(struct psi_trigger *t);
__poll_t psi_trigger_poll(void **trigger_ptr, struct file *file,
+22 -1
View File
@@ -4071,6 +4071,7 @@ static ssize_t pressure_write(struct kernfs_open_file *of, char *buf,
struct psi_trigger *new;
struct cgroup *cgrp;
struct psi_group *psi;
bool need_rtpoll_worker;
ssize_t ret = 0;
cgrp = cgroup_kn_lock_live(of->kn, false);
@@ -4090,12 +4091,32 @@ static ssize_t pressure_write(struct kernfs_open_file *of, char *buf,
}
psi = cgroup_psi(cgrp);
new = psi_trigger_create(psi, buf, res, of->file, of);
new = psi_trigger_create(psi, buf, res, of->file, of,
&need_rtpoll_worker);
if (IS_ERR(new)) {
ret = PTR_ERR(new);
goto out_unlock;
}
/*
* The worker fork must run with neither cgroup_mutex nor the file's
* kernfs active reference held. The latter is broken since
* cgroup_kn_lock_live(). @of->priv may be released while unlocked, so
* recheck before publishing @new.
*/
if (need_rtpoll_worker) {
cgroup_unlock();
ret = psi_trigger_create_rtpoll_worker(psi);
cgroup_lock();
if (!ret && !of->priv)
ret = -ENODEV;
if (ret) {
psi_trigger_destroy(new);
goto out_unlock;
}
}
smp_store_release(&ctx->psi.trigger, new);
out_unlock:
+4 -8
View File
@@ -146,10 +146,9 @@ struct cpuset {
nodemask_t old_mems_allowed;
/*
* Tasks are being attached to this cpuset. Used to prevent
* zeroing cpus/mems_allowed between ->can_attach() and ->attach().
* For linking impacted cpusets during an attach operation.
*/
int attach_in_progress;
struct llist_node attach_node;
/* partition root state */
int partition_root_state;
@@ -165,7 +164,7 @@ struct cpuset {
* number of SCHED_DEADLINE tasks attached to this cpuset, so that we
* know when to rebuild associated root domain bandwidth information.
*/
int nr_deadline_tasks;
atomic_t nr_deadline_tasks;
int nr_migrate_dl_tasks;
/* DL bandwidth that needs destination reservation for this attach. */
u64 sum_migrate_dl_bw;
@@ -269,10 +268,7 @@ static inline int nr_cpusets(void)
static inline bool cpuset_is_populated(struct cpuset *cs)
{
lockdep_assert_cpuset_lock_held();
/* Cpusets in the process of attaching should be considered as populated */
return cgroup_is_populated(cs->css.cgroup) ||
cs->attach_in_progress;
return cgroup_is_populated(cs->css.cgroup);
}
/**
+340 -190
View File
File diff suppressed because it is too large Load Diff
+59 -16
View File
@@ -1134,6 +1134,12 @@ void psi_cgroup_free(struct cgroup *cgroup)
return;
cancel_delayed_work_sync(&cgroup->psi->avgs_work);
/*
* A psi_schedule_rtpoll_work() call racing the last trigger's
* destruction may have re-armed the timer after psi_trigger_destroy()
* deleted it. Spurious firing while the group is alive is harmless.
*/
timer_shutdown_sync(&cgroup->psi->rtpoll_timer);
free_percpu(cgroup->psi->pcpu);
/* All triggers must be removed by now */
WARN_ONCE(cgroup->psi->rtpoll_states, "psi: trigger leak\n");
@@ -1292,9 +1298,44 @@ int psi_show(struct seq_file *m, struct psi_group *group, enum psi_res res)
return 0;
}
/*
* Create @group's rtpoll worker after psi_trigger_create() reported the need
* for one. kthread creation depends on the whole fork path and we don't want
* all of that nested inside cgroup_mutex, so the caller must drop it and any
* other lock that forks can wait behind. If two callers race, the loser stops
* its never-woken kthread.
*/
int psi_trigger_create_rtpoll_worker(struct psi_group *group)
{
struct task_struct *task;
task = kthread_create(psi_rtpoll_worker, group, "psimon");
if (IS_ERR(task))
return PTR_ERR(task);
scoped_guard(mutex, &group->rtpoll_trigger_lock) {
if (!rcu_access_pointer(group->rtpoll_task)) {
atomic_set(&group->rtpoll_wakeup, 0);
wake_up_process(task);
rcu_assign_pointer(group->rtpoll_task, task);
/*
* Poll once to catch up on scheduling attempts dropped
* while there was no rtpoll worker.
*/
psi_schedule_rtpoll_work(group, 1, true);
return 0;
}
}
kthread_stop(task);
return 0;
}
struct psi_trigger *psi_trigger_create(struct psi_group *group, char *buf,
enum psi_res res, struct file *file,
struct kernfs_open_file *of)
struct kernfs_open_file *of,
bool *need_rtpoll_worker)
{
struct psi_trigger *t;
enum psi_states state;
@@ -1302,6 +1343,8 @@ struct psi_trigger *psi_trigger_create(struct psi_group *group, char *buf,
bool privileged;
u32 window_us;
*need_rtpoll_worker = false;
if (static_branch_likely(&psi_disabled))
return ERR_PTR(-EOPNOTSUPP);
@@ -1362,26 +1405,14 @@ struct psi_trigger *psi_trigger_create(struct psi_group *group, char *buf,
if (privileged) {
mutex_lock(&group->rtpoll_trigger_lock);
if (!rcu_access_pointer(group->rtpoll_task)) {
struct task_struct *task;
task = kthread_create(psi_rtpoll_worker, group, "psimon");
if (IS_ERR(task)) {
kfree(t);
mutex_unlock(&group->rtpoll_trigger_lock);
return ERR_CAST(task);
}
atomic_set(&group->rtpoll_wakeup, 0);
wake_up_process(task);
rcu_assign_pointer(group->rtpoll_task, task);
}
list_add(&t->node, &group->rtpoll_triggers);
group->rtpoll_min_period = min(group->rtpoll_min_period,
div_u64(t->win.size, UPDATES_PER_WINDOW));
group->rtpoll_nr_triggers[t->state]++;
group->rtpoll_states |= (1 << t->state);
*need_rtpoll_worker = !rcu_access_pointer(group->rtpoll_task);
mutex_unlock(&group->rtpoll_trigger_lock);
} else {
mutex_lock(&group->avgs_lock);
@@ -1541,6 +1572,8 @@ static ssize_t psi_write(struct file *file, const char __user *user_buf,
size_t buf_size;
struct seq_file *seq;
struct psi_trigger *new;
bool need_rtpoll_worker;
int ret;
if (static_branch_likely(&psi_disabled))
return -EOPNOTSUPP;
@@ -1565,12 +1598,22 @@ static ssize_t psi_write(struct file *file, const char __user *user_buf,
return -EBUSY;
}
new = psi_trigger_create(&psi_system, buf, res, file, NULL);
new = psi_trigger_create(&psi_system, buf, res, file, NULL,
&need_rtpoll_worker);
if (IS_ERR(new)) {
mutex_unlock(&seq->lock);
return PTR_ERR(new);
}
if (need_rtpoll_worker) {
ret = psi_trigger_create_rtpoll_worker(&psi_system);
if (ret) {
psi_trigger_destroy(new);
mutex_unlock(&seq->lock);
return ret;
}
}
smp_store_release(&seq->private, new);
mutex_unlock(&seq->lock);
+5 -5
View File
@@ -15,11 +15,6 @@ import time
import json
import math
import drgn
from drgn import container_of
from drgn.helpers.linux.list import list_for_each_entry,list_empty
from drgn.helpers.linux.radixtree import radix_tree_for_each,radix_tree_lookup
import argparse
parser = argparse.ArgumentParser(description=desc,
formatter_class=argparse.RawTextHelpFormatter)
@@ -34,6 +29,11 @@ parser.add_argument('--json', action='store_true',
help='Output in json')
args = parser.parse_args()
import drgn
from drgn import container_of
from drgn.helpers.linux.list import list_for_each_entry,list_empty
from drgn.helpers.linux.radixtree import radix_tree_for_each,radix_tree_lookup
def err(s):
print(s, file=sys.stderr, flush=True)
sys.exit(1)
@@ -8,6 +8,7 @@
#define MB(x) (x << 20)
#define NSEC_PER_USEC 1000L
#define USEC_PER_SEC 1000000L
#define NSEC_PER_SEC 1000000000L
@@ -426,7 +426,6 @@ static int test_cgcore_no_internal_process_constraint_on_threads(const char *roo
ret = KSFT_PASS;
cleanup:
cg_enter_current(root);
cg_enter_current(root);
if (child)
cg_destroy(child);
+37 -6
View File
@@ -639,6 +639,31 @@ test_cpucg_nested_weight_underprovisioned(const char *root)
return run_cpucg_nested_weight_test(root, false);
}
/*
* Best effort attempt to get the kernel's HZ value from the config.
* Return the HZ value if found otherwise return 1000 (the default) to
* indicate failure.
*/
static long
get_config_hz(void)
{
long hz = 1000;
FILE *f;
char cmd[256] = "zcat /proc/config.gz 2>/dev/null | grep '^CONFIG_HZ='";
f = popen(cmd, "r");
if (!f)
return hz;
if (fscanf(f, "CONFIG_HZ=%ld", &hz) == EOF)
goto out;
out:
pclose(f);
return hz;
}
/*
* This test creates a cgroup with some maximum value within a period, and
* verifies that a process in the cgroup is not overscheduled.
@@ -646,15 +671,18 @@ test_cpucg_nested_weight_underprovisioned(const char *root)
static int test_cpucg_max(const char *root)
{
int ret = KSFT_FAIL;
long hz = get_config_hz();
long quota_usec = 1000;
long default_period_usec = 100000; /* cpu.max's default period */
long duration_seconds = 1;
long duration_usec = duration_seconds * USEC_PER_SEC;
long duration_usec;
long usage_usec, n_periods, remainder_usec, expected_usage_usec;
char *cpucg;
char quota_buf[32];
duration_usec = duration_seconds * USEC_PER_SEC * 1000 / hz;
snprintf(quota_buf, sizeof(quota_buf), "%ld", quota_usec);
cpucg = cg_name(root, "cpucg_test");
@@ -670,8 +698,8 @@ static int test_cpucg_max(const char *root)
struct cpu_hog_func_param param = {
.nprocs = 1,
.ts = {
.tv_sec = duration_seconds,
.tv_nsec = 0,
.tv_sec = duration_usec / USEC_PER_SEC,
.tv_nsec = duration_usec % USEC_PER_SEC * NSEC_PER_USEC,
},
.clock_type = CPU_HOG_CLOCK_WALL,
};
@@ -710,15 +738,18 @@ cleanup:
static int test_cpucg_max_nested(const char *root)
{
int ret = KSFT_FAIL;
long hz = get_config_hz();
long quota_usec = 1000;
long default_period_usec = 100000; /* cpu.max's default period */
long duration_seconds = 1;
long duration_usec = duration_seconds * USEC_PER_SEC;
long duration_usec;
long usage_usec, n_periods, remainder_usec, expected_usage_usec;
char *parent, *child;
char quota_buf[32];
duration_usec = duration_seconds * USEC_PER_SEC * 1000 / hz;
snprintf(quota_buf, sizeof(quota_buf), "%ld", quota_usec);
parent = cg_name(root, "cpucg_parent");
@@ -741,8 +772,8 @@ static int test_cpucg_max_nested(const char *root)
struct cpu_hog_func_param param = {
.nprocs = 1,
.ts = {
.tv_sec = duration_seconds,
.tv_nsec = 0,
.tv_sec = duration_usec / USEC_PER_SEC,
.tv_nsec = duration_usec % USEC_PER_SEC * NSEC_PER_USEC,
},
.clock_type = CPU_HOG_CLOCK_WALL,
};
@@ -1,7 +1,13 @@
// SPDX-License-Identifier: GPL-2.0
#define _GNU_SOURCE
#include <assert.h>
#include <linux/limits.h>
#include <pthread.h>
#include <sched.h>
#include <signal.h>
#include <sys/syscall.h>
#include <unistd.h>
#include "kselftest.h"
#include "cgroup_util.h"
@@ -232,6 +238,246 @@ cleanup:
return ret;
}
static int get_cpu_affinity(cpu_set_t *mask)
{
CPU_ZERO(mask);
return sched_getaffinity(0, sizeof(*mask), mask);
}
static int cpu_set_equal(cpu_set_t *dst, unsigned long mask)
{
cpu_set_t expected;
CPU_ZERO(&expected);
assert(sizeof(mask) < CPU_SETSIZE);
for (int cpu = 0; cpu < sizeof(mask) * 8; ++cpu)
if ((1UL << cpu) & mask)
CPU_SET(cpu, &expected);
return CPU_EQUAL(&expected, dst);
}
enum test_phase {
AFFINITY_SETUP,
AFFINITY_CONTROLLER_DISABLED,
AFFINITY_COMPLETE,
AFFINITY_ERROR
};
struct thread_args {
const char *cgroup;
cpu_set_t *affinity_before;
cpu_set_t *affinity_after;
int affinity_before_ready;
};
static pthread_mutex_t test_mutex = PTHREAD_MUTEX_INITIALIZER;
static pthread_cond_t test_cond = PTHREAD_COND_INITIALIZER;
static enum test_phase test_phase;
static void *affinity_thread_fn(void *arg)
{
struct thread_args *args = (struct thread_args *)arg;
if (cg_enter_current_thread(args->cgroup))
goto fail;
if (get_cpu_affinity(args->affinity_before) != 0)
goto fail;
pthread_mutex_lock(&test_mutex);
args->affinity_before_ready = 1;
pthread_cond_broadcast(&test_cond);
while (test_phase < AFFINITY_CONTROLLER_DISABLED)
pthread_cond_wait(&test_cond, &test_mutex);
pthread_mutex_unlock(&test_mutex);
if (get_cpu_affinity(args->affinity_after) != 0)
goto fail;
return NULL;
fail:
pthread_mutex_lock(&test_mutex);
test_phase = AFFINITY_ERROR;
pthread_cond_broadcast(&test_cond);
pthread_mutex_unlock(&test_mutex);
return NULL;
}
/*
* Test that disabling cpuset controller properly updates thread affinity.
*
* This test exposes a bug in cpuset_attach() where threads in child cgroups
* don't get their affinity updated when the cpuset controller is disabled.
*
* Setup:
* - Create parent cgroup with cpuset.cpus=0-1
* - Create child A with cpuset.cpus=0-1
* - Create child B with cpuset.cpus=1
* - Place multithreaded process: group leader + thread_a in A, thread_b in B
* - Disable cpuset controller on parent
*
* Expected: thread_b's affinity should expand from {1} to {0-1}
* Buggy: thread_b's affinity remains {1}
*/
static int test_cpuset_affinity_on_controller_disable(const char *root)
{
char *parent = NULL, *child_a = NULL, *child_b = NULL;
pthread_t thread_a, thread_b;
int thread_a_created = 0, thread_b_created = 0;
cpu_set_t affinity_a_before, affinity_a_after;
cpu_set_t affinity_b_before, affinity_b_after;
int ret = KSFT_FAIL;
parent = cg_name(root, "cpuset_affinity_test");
if (!parent)
goto cleanup;
if (cg_create(parent))
goto cleanup;
if (cg_write(parent, "cgroup.type", "threaded"))
goto cleanup;
child_a = cg_name(parent, "A");
if (!child_a)
goto cleanup;
if (cg_create(child_a))
goto cleanup;
if (cg_write(child_a, "cgroup.type", "threaded"))
goto cleanup;
child_b = cg_name(parent, "B");
if (!child_b)
goto cleanup;
if (cg_create(child_b))
goto cleanup;
if (cg_write(child_b, "cgroup.type", "threaded"))
goto cleanup;
/* Now enable cpuset controller in parent */
if (cg_write(parent, "cgroup.subtree_control", "+cpuset"))
goto skip;
/*
* Set CPU affinity constraints
* Skip the test if the setting of "cpuset.cpus" fails as the test
* system may not have CPU 1.
*/
if (cg_write(parent, "cpuset.cpus", "0-1"))
goto skip;
if (cg_write(child_a, "cpuset.cpus", "0-1"))
goto skip;
if (cg_write(child_b, "cpuset.cpus", "1"))
goto skip;
/* Move group leader (main thread) to child A */
if (cg_enter_current(child_a))
goto cleanup;
/* Create threads - they will move themselves to their respective cgroups */
test_phase = AFFINITY_SETUP;
struct thread_args args_a = {
.cgroup = child_a,
.affinity_before = &affinity_a_before,
.affinity_after = &affinity_a_after,
.affinity_before_ready = 0,
};
if (pthread_create(&thread_a, NULL, affinity_thread_fn, &args_a))
goto cleanup;
thread_a_created = 1;
struct thread_args args_b = {
.cgroup = child_b,
.affinity_before = &affinity_b_before,
.affinity_after = &affinity_b_after,
.affinity_before_ready = 0,
};
if (pthread_create(&thread_b, NULL, affinity_thread_fn, &args_b))
goto cleanup_threads;
thread_b_created = 1;
pthread_mutex_lock(&test_mutex);
while ((test_phase < AFFINITY_ERROR) &&
(args_a.affinity_before_ready + args_b.affinity_before_ready < 2))
pthread_cond_wait(&test_cond, &test_mutex);
/* If a thread failed during setup, bail out */
if (test_phase == AFFINITY_ERROR) {
pthread_mutex_unlock(&test_mutex);
goto cleanup_threads;
}
pthread_mutex_unlock(&test_mutex);
if (!cpu_set_equal(&affinity_a_before, 0x3)) {
ksft_print_msg("FAIL: thread_a initial affinity incorrect\n");
goto cleanup_threads;
}
if (!cpu_set_equal(&affinity_b_before, 0x2)) {
ksft_print_msg("FAIL: thread_b initial affinity incorrect\n");
goto cleanup_threads;
}
/* Disable cpuset controller - this should trigger affinity update */
if (cg_write(parent, "cgroup.subtree_control", "-cpuset"))
goto cleanup_threads;
/* Signal threads to save their final affinity and exit */
pthread_mutex_lock(&test_mutex);
test_phase = AFFINITY_CONTROLLER_DISABLED;
pthread_cond_broadcast(&test_cond);
pthread_mutex_unlock(&test_mutex);
pthread_join(thread_a, NULL);
pthread_join(thread_b, NULL);
/* Verify thread affinities AFTER disabling controller */
if (!cpu_set_equal(&affinity_a_after, 0x3)) {
ksft_print_msg("FAIL: thread_a final affinity incorrect\n");
goto cleanup;
}
if (!cpu_set_equal(&affinity_b_after, 0x3)) {
ksft_print_msg("FAIL: thread_b affinity did not expand to {0-1}\n");
goto cleanup;
}
ret = KSFT_PASS;
goto cleanup;
skip:
ret = KSFT_SKIP;
goto cleanup;
cleanup_threads:
pthread_mutex_lock(&test_mutex);
test_phase = AFFINITY_COMPLETE;
pthread_cond_broadcast(&test_cond);
pthread_mutex_unlock(&test_mutex);
if (thread_a_created)
pthread_join(thread_a, NULL);
if (thread_b_created)
pthread_join(thread_b, NULL);
cleanup:
/* Move back to root before cleanup */
cg_enter_current(root);
cg_destroy(child_b);
free(child_b);
cg_destroy(child_a);
free(child_a);
cg_destroy(parent);
free(parent);
return ret;
}
#define T(x) { x, #x }
struct cpuset_test {
@@ -241,6 +487,7 @@ struct cpuset_test {
T(test_cpuset_perms_object_allow),
T(test_cpuset_perms_object_deny),
T(test_cpuset_perms_subtree),
T(test_cpuset_affinity_on_controller_disable),
};
#undef T
@@ -495,13 +495,20 @@ REMOTE_TEST_MATRIX=(
# Narrowing cpuset.cpus to previously sibling-excluded CPUs should
# not return CPUs that were never actually owned.
" C1-4:P1 . C1-2:P1 C1-3:P2 . . \
. . . C3 . . p1:4|c11:1-2|c12:3 \
. . . C3 . . p1:4|c11:1-2|c12:3 \
p1:P1|c11:P1|c12:P2 3"
# Expanding cpuset.cpus to include a previously sibling-excluded CPU
# after the sibling has become a member should correctly request it.
" C1-4:P1 . C1-2:P1 C1-3:P2 . . \
. . P0 C2-3 . . p1:1,4|c11:1|c12:2-3 \
. . P0 C2-3 . . p1:1,4|c11:1|c12:2-3 \
p1:P1|c11:P0|c12:P2 2-3"
# Cpusets with empty cpuset.cpus should inherit parent's effective_cpus
" C1-4:P1 C5-6 C1-2 . C5 . \
. P1 P1 . . . p1:3-4|p2:5-6|c11:1-2|c12:3-4|c21:5|c22:5-6 \
p1:P1|p2:P1|c11:P1"
" C1-4:P1 C5-6 C1-2 . C5 . \
. P1 P1 . O5=0 . p1:3-4|p2:6|c11:1-2|c12:3-4|c21:6|c22:6 \
p1:P1|p2:P1|c11:P1"
)
#
@@ -199,7 +199,10 @@ out:
int main(int argc, char **argv)
{
char root[PATH_MAX];
int ret = EXIT_SUCCESS, has_memory_hugetlb_acc;
int has_memory_hugetlb_acc;
ksft_print_header();
ksft_set_plan(1);
has_memory_hugetlb_acc = proc_mount_contains("memory_hugetlb_accounting");
if (has_memory_hugetlb_acc < 0)
@@ -211,7 +214,7 @@ int main(int argc, char **argv)
if (get_hugepage_size() != 2048) {
ksft_print_msg("test_hugetlb_memcg requires 2MB hugepages\n");
ksft_test_result_skip("test_hugetlb_memcg\n");
return ret;
ksft_finished();
}
if (cg_find_unified_root(root, sizeof(root), NULL))
@@ -233,10 +236,9 @@ int main(int argc, char **argv)
ksft_test_result_skip("test_hugetlb_memcg\n");
break;
default:
ret = EXIT_FAILURE;
ksft_test_result_fail("test_hugetlb_memcg\n");
break;
}
return ret;
ksft_finished();
}