mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2026-08-30 20:30:26 -04:00
Merge tag 'cgroup-for-7.2-rc6-fixes' of git://git.kernel.org/pub/scm/linux/kernel/git/tj/cgroup
Pull cgroup fixes from Tejun Heo: - A pressure trigger's poll timer could be re-armed while the last trigger was being torn down and then fire after the cgroup was freed. Tie the timer to the cgroup's lifetime and shut it down when the cgroup is freed. - Writing to a pressure file forked a worker kthread while holding the cgroup mutex, creating lock dependencies from the mutex to the whole fork path. A pressure write racing a sched_ext scheduler enable, which blocks forks before grabbing the mutex, deadlocked. Fork the worker with the mutex dropped. - Documentation fix for io.latency behavior on non-rotational devices. * tag 'cgroup-for-7.2-rc6-fixes' of git://git.kernel.org/pub/scm/linux/kernel/git/tj/cgroup: Docs/admin-guide/cgroup-v2: document io.latency rotational vs non-rotational behavior sched/psi: Shut down rtpoll_timer in psi_cgroup_free() sched/psi: Create the psimon kthread outside of cgroup_mutex
This commit is contained in:
@@ -2239,9 +2239,12 @@ IO Latency
|
||||
~~~~~~~~~~
|
||||
|
||||
This is a cgroup v2 controller for IO workload protection. You provide a group
|
||||
with a latency target, and if the average latency exceeds that target the
|
||||
controller will throttle any peers that have a lower latency target than the
|
||||
protected workload.
|
||||
with a latency target, and if the group misses its target the controller will
|
||||
throttle any peers that have a lower latency target than the protected
|
||||
workload. How a miss is detected depends on the device: on rotational devices
|
||||
the average latency over the window must exceed the target, while on
|
||||
non-rotational devices a miss is counted once enough of the IOs in the window
|
||||
individually exceed the target.
|
||||
|
||||
The limits are only applied at the peer level in the hierarchy. This means that
|
||||
in the diagram below, only groups A, B, and C will influence each other, and
|
||||
@@ -2258,10 +2261,12 @@ So the ideal way to configure this is to set io.latency in groups A, B, and C.
|
||||
Generally you do not want to set a value lower than the latency your device
|
||||
supports. Experiment to find the value that works best for your workload.
|
||||
Start at higher than the expected latency for your device and, with
|
||||
blkcg_debug_stats enabled, watch the avg_lat value in io.stat for your
|
||||
workload group to get an idea of the latency you see during normal operation.
|
||||
Use the avg_lat value as a basis for your real setting, setting at 10-15%
|
||||
higher than the value in io.stat.
|
||||
blkcg_debug_stats enabled, observe io.stat for your workload group to get an
|
||||
idea of the latency you see during normal operation. On rotational devices,
|
||||
use the avg_lat value as a basis for your real setting, setting it 10-15%
|
||||
higher. On non-rotational devices io.stat reports no average latency; set
|
||||
the target based on your device and use the missed/total fields to verify it
|
||||
is being met.
|
||||
|
||||
How IO Latency Throttling Works
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
@@ -2303,19 +2308,36 @@ IO Latency Interface Files
|
||||
the blkcg_debug_stats module parameter is enabled (it is disabled by
|
||||
default).
|
||||
|
||||
The reported latency fields depend on the device. Rotational devices
|
||||
report avg_lat and win; non-rotational devices report missed and total
|
||||
instead. missed and total are live counters for the current window and
|
||||
may change between reads.
|
||||
|
||||
depth
|
||||
This is the current queue depth for the group.
|
||||
|
||||
avg_lat
|
||||
This is an exponential moving average with a decay rate of 1/exp
|
||||
bound by the sampling interval. The decay rate interval can be
|
||||
calculated by multiplying the win value in io.stat by the
|
||||
corresponding number of samples based on the win value.
|
||||
(Rotational devices only.) This is an exponential moving
|
||||
average with a decay rate of 1/exp bound by the sampling
|
||||
interval. The decay rate interval can be calculated by
|
||||
multiplying the win value in io.stat by the corresponding number
|
||||
of samples based on the win value.
|
||||
|
||||
win
|
||||
The sampling window size in milliseconds. This is the minimum
|
||||
duration of time between evaluation events. Windows only elapse
|
||||
with IO activity. Idle periods extend the most recent window.
|
||||
(Rotational devices only.) The sampling window size in
|
||||
milliseconds. This is the minimum duration of time between
|
||||
evaluation events. Windows only elapse with IO activity. Idle
|
||||
periods extend the most recent window.
|
||||
|
||||
missed
|
||||
(Non-rotational devices only.) The number of IOs in the
|
||||
current window whose latency exceeded the target. A group is
|
||||
considered to be missing its target once missed reaches a
|
||||
certain ratio of total.
|
||||
|
||||
total
|
||||
(Non-rotational devices only.) The total number of IOs
|
||||
accounted in the current window.
|
||||
|
||||
IO Priority
|
||||
~~~~~~~~~~~
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -3996,6 +3996,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);
|
||||
@@ -4015,12 +4016,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:
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user