mirror of
https://github.com/linux-msm/laptops-kernel.git
synced 2026-08-13 14:19:53 -07:00
Merge branch 'for-next' of https://git.kernel.org/pub/scm/linux/kernel/git/tj/cgroup.git
This commit is contained in:
@@ -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 ?
|
||||
========================
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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
@@ -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
@@ -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:
|
||||
|
||||
@@ -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
File diff suppressed because it is too large
Load Diff
+59
-16
@@ -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);
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user