diff options
| author | Mark Brown <broonie@kernel.org> | 2026-09-14 16:07:40 +0100 |
|---|---|---|
| committer | Mark Brown <broonie@kernel.org> | 2026-09-14 16:07:40 +0100 |
| commit | f17c9807984ca28568f23d42e8c0c70e0ba54482 (patch) | |
| tree | 2b02516826acfaa05fdce1f9097635919fb276c9 /kernel | |
| parent | aec0dc241ed65208c3c93147565ffac5d15514d7 (diff) | |
| parent | e8f69fe59a104b4a97a3365dbb43c72996c2acde (diff) | |
| download | linux-next-f17c9807984ca28568f23d42e8c0c70e0ba54482.tar.gz linux-next-f17c9807984ca28568f23d42e8c0c70e0ba54482.zip | |
Merge branch 'for-next' of https://git.kernel.org/pub/scm/linux/kernel/git/tj/cgroup.git
Diffstat (limited to 'kernel')
| -rw-r--r-- | kernel/cgroup/cgroup-internal.h | 2 | ||||
| -rw-r--r-- | kernel/cgroup/cgroup-v1.c | 16 | ||||
| -rw-r--r-- | kernel/cgroup/cgroup.c | 121 | ||||
| -rw-r--r-- | kernel/cgroup/debug.c | 10 | ||||
| -rw-r--r-- | kernel/cgroup/dmem.c | 4 | ||||
| -rw-r--r-- | kernel/cgroup/namespace.c | 2 |
6 files changed, 91 insertions, 64 deletions
diff --git a/kernel/cgroup/cgroup-internal.h b/kernel/cgroup/cgroup-internal.h index 7c367c8d0cbe..233fb8b45d2c 100644 --- a/kernel/cgroup/cgroup-internal.h +++ b/kernel/cgroup/cgroup-internal.h @@ -254,7 +254,7 @@ void cgroup_procs_write_finish(struct task_struct *task, enum cgroup_attach_lock_mode lock_mode) __releases(&cgroup_threadgroup_rwsem); -void cgroup_lock_and_drain_offline(struct cgroup *cgrp); +int cgroup_lock_and_drain_offline(struct cgroup *cgrp); int cgroup_mkdir(struct kernfs_node *parent_kn, const char *name, umode_t mode); int cgroup_rmdir(struct kernfs_node *kn); diff --git a/kernel/cgroup/cgroup-v1.c b/kernel/cgroup/cgroup-v1.c index a4337c9b5287..167bf6555a49 100644 --- a/kernel/cgroup/cgroup-v1.c +++ b/kernel/cgroup/cgroup-v1.c @@ -506,8 +506,8 @@ static ssize_t __cgroup1_procs_write(struct kernfs_open_file *of, enum cgroup_attach_lock_mode lock_mode; cgrp = cgroup_kn_lock_live(of->kn, false); - if (!cgrp) - return -ENODEV; + if (IS_ERR(cgrp)) + return PTR_ERR(cgrp); task = cgroup_procs_write_start(buf, threadgroup, &lock_mode); ret = PTR_ERR_OR_ZERO(task); @@ -569,8 +569,8 @@ static ssize_t cgroup_release_agent_write(struct kernfs_open_file *of, return -EPERM; cgrp = cgroup_kn_lock_live(of->kn, false); - if (!cgrp) - return -ENODEV; + if (IS_ERR(cgrp)) + return PTR_ERR(cgrp); spin_lock(&release_agent_path_lock); strscpy(cgrp->root->release_agent_path, strstrip(buf), sizeof(cgrp->root->release_agent_path)); @@ -1097,7 +1097,9 @@ int cgroup1_reconfigure(struct fs_context *fc) int ret = 0; u32 added_mask, removed_mask; - cgroup_lock_and_drain_offline(&cgrp_dfl_root.cgrp); + ret = cgroup_lock_and_drain_offline(&cgrp_dfl_root.cgrp); + if (unlikely(ret)) + return ret; /* See what subsystems are wanted */ ret = check_cgroupfs_options(fc); @@ -1262,7 +1264,9 @@ int cgroup1_get_tree(struct fs_context *fc) if (!ns_capable(ctx->ns->user_ns, CAP_SYS_ADMIN)) return -EPERM; - cgroup_lock_and_drain_offline(&cgrp_dfl_root.cgrp); + ret = cgroup_lock_and_drain_offline(&cgrp_dfl_root.cgrp); + if (unlikely(ret)) + return ret; ret = cgroup1_root_to_use(fc); if (!ret && !percpu_ref_tryget_live(&ctx->root->cgrp.self.refcnt)) diff --git a/kernel/cgroup/cgroup.c b/kernel/cgroup/cgroup.c index 2d532bf2c0c7..16c0362330ac 100644 --- a/kernel/cgroup/cgroup.c +++ b/kernel/cgroup/cgroup.c @@ -1379,7 +1379,10 @@ static void cgroup_destroy_root(struct cgroup_root *root) trace_cgroup_destroy_root(root); - cgroup_lock_and_drain_offline(&cgrp_dfl_root.cgrp); + /* runs off a workqueue, no signal can interrupt the drain */ + ret = cgroup_lock_and_drain_offline(&cgrp_dfl_root.cgrp); + if (WARN_ON_ONCE(ret)) + cgroup_lock(); BUG_ON(atomic_read(&root->nr_cgrps)); BUG_ON(!list_empty(&cgrp->self.children)); @@ -1685,9 +1688,10 @@ void cgroup_kn_unlock(struct kernfs_node *kn) * This helper is to be used by a cgroup kernfs method currently servicing * @kn. It breaks the active protection, performs cgroup locking and * verifies that the associated cgroup is alive. Returns the cgroup if - * alive; otherwise, %NULL. A successful return should be undone by a - * matching cgroup_kn_unlock() invocation. If @drain_offline is %true, the - * cgroup is drained of offlining csses before return. + * alive; otherwise, an ERR_PTR value. A successful return should be undone by + * a matching cgroup_kn_unlock() invocation. If @drain_offline is %true, the + * cgroup is drained of offlining csses before return, and an interrupted drain + * fails with -ERESTARTSYS. * * Any cgroup kernfs method implementation which requires locking the * associated cgroup should use this helper. It avoids nesting cgroup @@ -1697,6 +1701,7 @@ void cgroup_kn_unlock(struct kernfs_node *kn) struct cgroup *cgroup_kn_lock_live(struct kernfs_node *kn, bool drain_offline) { struct cgroup *cgrp; + int ret; if (kernfs_type(kn) == KERNFS_DIR) cgrp = kn->priv; @@ -1710,19 +1715,25 @@ struct cgroup *cgroup_kn_lock_live(struct kernfs_node *kn, bool drain_offline) * break the active_ref protection. */ if (!cgroup_tryget(cgrp)) - return NULL; + return ERR_PTR(-ENODEV); kernfs_break_active_protection(kn); - if (drain_offline) - cgroup_lock_and_drain_offline(cgrp); - else + if (drain_offline) { + ret = cgroup_lock_and_drain_offline(cgrp); + if (unlikely(ret)) { + kernfs_unbreak_active_protection(kn); + cgroup_put(cgrp); + return ERR_PTR(ret); + } + } else { cgroup_lock(); + } if (!cgroup_is_dead(cgrp)) return cgrp; cgroup_kn_unlock(kn); - return NULL; + return ERR_PTR(-ENODEV); } static void cgroup_rm_file(struct cgroup *cgrp, const struct cftype *cft) @@ -3320,16 +3331,20 @@ out_finish: * @cgrp: root of the target subtree * * Because css offlining is asynchronous, userland may try to re-enable a - * controller while the previous css is still around. This function grabs - * cgroup_mutex and drains the previous css instances of @cgrp's subtree. + * controller while the previous css is still around. This function grabs + * cgroup_mutex and waits until no css in @cgrp's subtree is dying. A dying css + * offlines only after every task that still pins it has finished exiting, which + * can take arbitrarily long, so the wait is interruptible. + * + * Returns 0 with cgroup_mutex held once the subtree is drained, or -ERESTARTSYS + * without it if interrupted by a signal. */ -void cgroup_lock_and_drain_offline(struct cgroup *cgrp) - __acquires(&cgroup_mutex) +int cgroup_lock_and_drain_offline(struct cgroup *cgrp) { struct cgroup *dsct; struct cgroup_subsys_state *d_css; struct cgroup_subsys *ss; - int ssid; + int ssid, ret; restart: cgroup_lock(); @@ -3343,17 +3358,20 @@ restart: continue; cgroup_get_live(dsct); - prepare_to_wait(&dsct->offline_waitq, &wait, - TASK_UNINTERRUPTIBLE); - + ret = prepare_to_wait_event(&dsct->offline_waitq, &wait, + TASK_INTERRUPTIBLE); cgroup_unlock(); - schedule(); + if (!ret) + schedule(); finish_wait(&dsct->offline_waitq, &wait); - cgroup_put(dsct); + if (unlikely(ret)) + return ret; goto restart; } } + + return 0; } /** @@ -3650,8 +3668,8 @@ static ssize_t cgroup_subtree_control_write(struct kernfs_open_file *of, } cgrp = cgroup_kn_lock_live(of->kn, true); - if (!cgrp) - return -ENODEV; + if (IS_ERR(cgrp)) + return PTR_ERR(cgrp); for_each_subsys(ss, ssid) { if (enable & (1 << ssid)) { @@ -3790,8 +3808,8 @@ static ssize_t cgroup_type_write(struct kernfs_open_file *of, char *buf, /* drain dying csses before we re-apply (threaded) subtree control */ cgrp = cgroup_kn_lock_live(of->kn, true); - if (!cgrp) - return -ENOENT; + if (IS_ERR(cgrp)) + return PTR_ERR(cgrp); /* threaded can only be enabled */ ret = cgroup_enable_threaded(cgrp); @@ -3833,8 +3851,8 @@ static ssize_t cgroup_max_descendants_write(struct kernfs_open_file *of, return -ERANGE; cgrp = cgroup_kn_lock_live(of->kn, false); - if (!cgrp) - return -ENOENT; + if (IS_ERR(cgrp)) + return PTR_ERR(cgrp); WRITE_ONCE(cgrp->max_descendants, descendants); @@ -3876,8 +3894,8 @@ static ssize_t cgroup_max_depth_write(struct kernfs_open_file *of, return -ERANGE; cgrp = cgroup_kn_lock_live(of->kn, false); - if (!cgrp) - return -ENOENT; + if (IS_ERR(cgrp)) + return PTR_ERR(cgrp); WRITE_ONCE(cgrp->max_depth, depth); @@ -4075,8 +4093,8 @@ static ssize_t pressure_write(struct kernfs_open_file *of, char *buf, ssize_t ret = 0; cgrp = cgroup_kn_lock_live(of->kn, false); - if (!cgrp) - return -ENODEV; + if (IS_ERR(cgrp)) + return PTR_ERR(cgrp); ctx = of->priv; if (!ctx) { @@ -4192,8 +4210,8 @@ static ssize_t cgroup_pressure_write(struct kernfs_open_file *of, return -ERANGE; cgrp = cgroup_kn_lock_live(of->kn, false); - if (!cgrp) - return -ENOENT; + if (IS_ERR(cgrp)) + return PTR_ERR(cgrp); psi = cgroup_psi(cgrp); if (psi->enabled != enable) { @@ -4268,8 +4286,8 @@ static ssize_t cgroup_freeze_write(struct kernfs_open_file *of, return -ERANGE; cgrp = cgroup_kn_lock_live(of->kn, false); - if (!cgrp) - return -ENOENT; + if (IS_ERR(cgrp)) + return PTR_ERR(cgrp); cgroup_freeze(cgrp, freeze); @@ -4330,8 +4348,8 @@ static ssize_t cgroup_kill_write(struct kernfs_open_file *of, char *buf, return -ERANGE; cgrp = cgroup_kn_lock_live(of->kn, false); - if (!cgrp) - return -ENOENT; + if (IS_ERR(cgrp)) + return PTR_ERR(cgrp); /* * Killing is a process directed operation, i.e. the whole thread-group @@ -5485,8 +5503,8 @@ static ssize_t __cgroup_procs_write(struct kernfs_open_file *of, char *buf, enum cgroup_attach_lock_mode lock_mode; dst_cgrp = cgroup_kn_lock_live(of->kn, false); - if (!dst_cgrp) - return -ENODEV; + if (IS_ERR(dst_cgrp)) + return PTR_ERR(dst_cgrp); task = cgroup_procs_write_start(buf, threadgroup, &lock_mode); ret = PTR_ERR_OR_ZERO(task); @@ -6120,8 +6138,8 @@ int cgroup_mkdir(struct kernfs_node *parent_kn, const char *name, umode_t mode) return -EINVAL; parent = cgroup_kn_lock_live(parent_kn, false); - if (!parent) - return -ENODEV; + if (IS_ERR(parent)) + return PTR_ERR(parent); if (!cgroup_check_hierarchy_limits(parent)) { ret = -EAGAIN; @@ -6397,7 +6415,7 @@ int cgroup_rmdir(struct kernfs_node *kn) int ret = 0; cgrp = cgroup_kn_lock_live(kn, false); - if (!cgrp) + if (IS_ERR(cgrp)) return 0; ret = cgroup_destroy_locked(cgrp); @@ -7194,15 +7212,19 @@ static void do_cgroup_task_dead(struct task_struct *tsk) * the cgroup and task_struct can be pinned indefinitely. Bounce through lazy * irq_work to allow batching while ensuring timely completion. */ -static DEFINE_PER_CPU(struct llist_head, cgrp_dead_tasks); -static DEFINE_PER_CPU(struct irq_work, cgrp_dead_tasks_iwork); +struct cgroup_dead { + struct irq_work iwork; + struct llist_head tasks; +}; +static DEFINE_PER_CPU(struct cgroup_dead, cgroup_dead); static void cgrp_dead_tasks_iwork_fn(struct irq_work *iwork) { + struct cgroup_dead *cgrp_dead = container_of(iwork, struct cgroup_dead, iwork); struct llist_node *lnode; struct task_struct *task, *next; - lnode = llist_del_all(this_cpu_ptr(&cgrp_dead_tasks)); + lnode = llist_del_all(&cgrp_dead->tasks); llist_for_each_entry_safe(task, next, lnode, cg_dead_lnode) { do_cgroup_task_dead(task); put_task_struct(task); @@ -7214,17 +7236,20 @@ static void __init cgroup_rt_init(void) int cpu; for_each_possible_cpu(cpu) { - init_llist_head(per_cpu_ptr(&cgrp_dead_tasks, cpu)); - per_cpu(cgrp_dead_tasks_iwork, cpu) = - IRQ_WORK_INIT_LAZY(cgrp_dead_tasks_iwork_fn); + struct cgroup_dead *cgrp_dead = per_cpu_ptr(&cgroup_dead, cpu); + + init_llist_head(&cgrp_dead->tasks); + cgrp_dead->iwork = IRQ_WORK_INIT_LAZY(cgrp_dead_tasks_iwork_fn); } } void cgroup_task_dead(struct task_struct *task) { + struct cgroup_dead *cgrp_dead = this_cpu_ptr(&cgroup_dead); + get_task_struct(task); - llist_add(&task->cg_dead_lnode, this_cpu_ptr(&cgrp_dead_tasks)); - irq_work_queue(this_cpu_ptr(&cgrp_dead_tasks_iwork)); + llist_add(&task->cg_dead_lnode, &cgrp_dead->tasks); + irq_work_queue(&cgrp_dead->iwork); } #else /* CONFIG_PREEMPT_RT */ static void __init cgroup_rt_init(void) {} diff --git a/kernel/cgroup/debug.c b/kernel/cgroup/debug.c index 883347b87842..96004cd65d09 100644 --- a/kernel/cgroup/debug.c +++ b/kernel/cgroup/debug.c @@ -45,7 +45,7 @@ static int current_css_set_read(struct seq_file *seq, void *v) struct cgroup_subsys_state *css; int i, refcnt; - if (!cgroup_kn_lock_live(of->kn, false)) + if (IS_ERR(cgroup_kn_lock_live(of->kn, false))) return -ENODEV; spin_lock_irq(&css_set_lock); @@ -206,8 +206,8 @@ static int cgroup_subsys_states_read(struct seq_file *seq, void *v) int i; cgrp = cgroup_kn_lock_live(of->kn, false); - if (!cgrp) - return -ENODEV; + if (IS_ERR(cgrp)) + return PTR_ERR(cgrp); for_each_subsys(ss, i) { css = rcu_dereference_check(cgrp->subsys[ss->id], true); @@ -254,8 +254,8 @@ static int cgroup_masks_read(struct seq_file *seq, void *v) struct cgroup *cgrp; cgrp = cgroup_kn_lock_live(of->kn, false); - if (!cgrp) - return -ENODEV; + if (IS_ERR(cgrp)) + return PTR_ERR(cgrp); cgroup_masks_read_one(seq, "subtree_control", cgrp->subtree_control); cgroup_masks_read_one(seq, "subtree_ss_mask", cgrp->subtree_ss_mask); diff --git a/kernel/cgroup/dmem.c b/kernel/cgroup/dmem.c index 47a41646de4b..d49ed5d4a923 100644 --- a/kernel/cgroup/dmem.c +++ b/kernel/cgroup/dmem.c @@ -706,8 +706,8 @@ EXPORT_SYMBOL_GPL(dmem_cgroup_uncharge); * * When this function fails with -EAGAIN and @ret_limit_pool is non-null, it * will be set to the pool for which the limit is hit. This can be used for - * eviction as argument to dmem_cgroup_evict_valuable(). This reference must be freed - * with @dmem_cgroup_pool_state_put(). + * eviction as argument to dmem_cgroup_state_evict_valuable(). This reference + * must be freed with @dmem_cgroup_pool_state_put(). * * Return: 0 on success, -EAGAIN on hitting a limit, or a negative errno on failure. */ diff --git a/kernel/cgroup/namespace.c b/kernel/cgroup/namespace.c index ea4ee13936be..76660332ec64 100644 --- a/kernel/cgroup/namespace.c +++ b/kernel/cgroup/namespace.c @@ -53,8 +53,6 @@ struct cgroup_namespace *copy_cgroup_ns(u64 flags, struct ucounts *ucounts; struct css_set *cset; - BUG_ON(!old_ns); - if (!(flags & CLONE_NEWCGROUP)) { get_cgroup_ns(old_ns); return old_ns; |
