Commit 334c3679 authored by Tejun Heo's avatar Tejun Heo

cgroup: reimplement rebind_subsystems() using cgroup_apply_control() and friends

rebind_subsystem() open codes quite a bit of css and interface file
manipulations.  It tries to be fail-safe but doesn't quite achieve it.
It can be greatly simplified by using the new css management helpers.
This patch reimplements rebind_subsytsems() using
cgroup_apply_control() and friends.

* The half-baked rollback on file creation failure is dropped.  It is
  an extremely cold path, failure isn't critical, and, aside from
  kernel bugs, the only reason it can fail is memory allocation
  failure which pretty much doesn't happen for small allocations.

* As cgroup_apply_control_disable() is now used to clean up root
  cgroup on rebind, make sure that it doesn't end up killing root
  csses.

* All callers of rebind_subsystems() are updated to use
  cgroup_lock_and_drain_offline() as the apply_control functions
  require drained subtree.

* This leaves cgroup_refresh_subtree_ss_mask() without any user.
  Removed.

* css_populate_dir() and css_clear_dir() no longer needs
  @cgrp_override parameter.  Dropped.

* While at it, add WARN_ON() to rebind_subsystem() calls which are
  expected to always succeed just in case.

While the rules visible to userland aren't changed, this
reimplementation not only simplifies rebind_subsystems() but also
allows it to disable and enable csses recursively.  This can be used
to implement more flexible rebinding.
Signed-off-by: default avatarTejun Heo <tj@kernel.org>
Acked-by: default avatarZefan Li <lizefan@huawei.com>
parent 03970d3c
...@@ -221,6 +221,8 @@ static struct cftype cgroup_legacy_base_files[]; ...@@ -221,6 +221,8 @@ static struct cftype cgroup_legacy_base_files[];
static int rebind_subsystems(struct cgroup_root *dst_root, u16 ss_mask); static int rebind_subsystems(struct cgroup_root *dst_root, u16 ss_mask);
static void cgroup_lock_and_drain_offline(struct cgroup *cgrp); static void cgroup_lock_and_drain_offline(struct cgroup *cgrp);
static int cgroup_apply_control(struct cgroup *cgrp);
static void cgroup_finalize_control(struct cgroup *cgrp, int ret);
static void css_task_iter_advance(struct css_task_iter *it); static void css_task_iter_advance(struct css_task_iter *it);
static int cgroup_destroy_locked(struct cgroup *cgrp); static int cgroup_destroy_locked(struct cgroup *cgrp);
static struct cgroup_subsys_state *css_create(struct cgroup *cgrp, static struct cgroup_subsys_state *css_create(struct cgroup *cgrp,
...@@ -1160,13 +1162,13 @@ static void cgroup_destroy_root(struct cgroup_root *root) ...@@ -1160,13 +1162,13 @@ static void cgroup_destroy_root(struct cgroup_root *root)
struct cgroup *cgrp = &root->cgrp; struct cgroup *cgrp = &root->cgrp;
struct cgrp_cset_link *link, *tmp_link; struct cgrp_cset_link *link, *tmp_link;
mutex_lock(&cgroup_mutex); cgroup_lock_and_drain_offline(&cgrp_dfl_root.cgrp);
BUG_ON(atomic_read(&root->nr_cgrps)); BUG_ON(atomic_read(&root->nr_cgrps));
BUG_ON(!list_empty(&cgrp->self.children)); BUG_ON(!list_empty(&cgrp->self.children));
/* Rebind all subsystems back to the default hierarchy */ /* Rebind all subsystems back to the default hierarchy */
rebind_subsystems(&cgrp_dfl_root, root->subsys_mask); WARN_ON(rebind_subsystems(&cgrp_dfl_root, root->subsys_mask));
/* /*
* Release all the links from cset_links to this hierarchy's * Release all the links from cset_links to this hierarchy's
...@@ -1351,19 +1353,6 @@ static u16 cgroup_calc_subtree_ss_mask(struct cgroup *cgrp, u16 subtree_control) ...@@ -1351,19 +1353,6 @@ static u16 cgroup_calc_subtree_ss_mask(struct cgroup *cgrp, u16 subtree_control)
return cur_ss_mask; return cur_ss_mask;
} }
/**
* cgroup_refresh_subtree_ss_mask - update subtree_ss_mask
* @cgrp: the target cgroup
*
* Update @cgrp->subtree_ss_mask according to the current
* @cgrp->subtree_control using cgroup_calc_subtree_ss_mask().
*/
static void cgroup_refresh_subtree_ss_mask(struct cgroup *cgrp)
{
cgrp->subtree_ss_mask =
cgroup_calc_subtree_ss_mask(cgrp, cgrp->subtree_control);
}
/** /**
* cgroup_kn_unlock - unlocking helper for cgroup kernfs methods * cgroup_kn_unlock - unlocking helper for cgroup kernfs methods
* @kn: the kernfs_node being serviced * @kn: the kernfs_node being serviced
...@@ -1459,12 +1448,10 @@ static void cgroup_rm_file(struct cgroup *cgrp, const struct cftype *cft) ...@@ -1459,12 +1448,10 @@ static void cgroup_rm_file(struct cgroup *cgrp, const struct cftype *cft)
/** /**
* css_clear_dir - remove subsys files in a cgroup directory * css_clear_dir - remove subsys files in a cgroup directory
* @css: taget css * @css: taget css
* @cgrp_override: specify if target cgroup is different from css->cgroup
*/ */
static void css_clear_dir(struct cgroup_subsys_state *css, static void css_clear_dir(struct cgroup_subsys_state *css)
struct cgroup *cgrp_override)
{ {
struct cgroup *cgrp = cgrp_override ?: css->cgroup; struct cgroup *cgrp = css->cgroup;
struct cftype *cfts; struct cftype *cfts;
if (!(css->flags & CSS_VISIBLE)) if (!(css->flags & CSS_VISIBLE))
...@@ -1479,14 +1466,12 @@ static void css_clear_dir(struct cgroup_subsys_state *css, ...@@ -1479,14 +1466,12 @@ static void css_clear_dir(struct cgroup_subsys_state *css,
/** /**
* css_populate_dir - create subsys files in a cgroup directory * css_populate_dir - create subsys files in a cgroup directory
* @css: target css * @css: target css
* @cgrp_overried: specify if target cgroup is different from css->cgroup
* *
* On failure, no file is added. * On failure, no file is added.
*/ */
static int css_populate_dir(struct cgroup_subsys_state *css, static int css_populate_dir(struct cgroup_subsys_state *css)
struct cgroup *cgrp_override)
{ {
struct cgroup *cgrp = cgrp_override ?: css->cgroup; struct cgroup *cgrp = css->cgroup;
struct cftype *cfts, *failed_cfts; struct cftype *cfts, *failed_cfts;
int ret; int ret;
...@@ -1526,7 +1511,6 @@ static int rebind_subsystems(struct cgroup_root *dst_root, u16 ss_mask) ...@@ -1526,7 +1511,6 @@ static int rebind_subsystems(struct cgroup_root *dst_root, u16 ss_mask)
{ {
struct cgroup *dcgrp = &dst_root->cgrp; struct cgroup *dcgrp = &dst_root->cgrp;
struct cgroup_subsys *ss; struct cgroup_subsys *ss;
u16 tmp_ss_mask;
int ssid, i, ret; int ssid, i, ret;
lockdep_assert_held(&cgroup_mutex); lockdep_assert_held(&cgroup_mutex);
...@@ -1541,46 +1525,6 @@ static int rebind_subsystems(struct cgroup_root *dst_root, u16 ss_mask) ...@@ -1541,46 +1525,6 @@ static int rebind_subsystems(struct cgroup_root *dst_root, u16 ss_mask)
return -EBUSY; return -EBUSY;
} while_each_subsys_mask(); } while_each_subsys_mask();
/* skip creating root files on dfl_root for inhibited subsystems */
tmp_ss_mask = ss_mask;
if (dst_root == &cgrp_dfl_root)
tmp_ss_mask &= ~cgrp_dfl_inhibit_ss_mask;
do_each_subsys_mask(ss, ssid, tmp_ss_mask) {
struct cgroup *scgrp = &ss->root->cgrp;
int tssid;
ret = css_populate_dir(cgroup_css(scgrp, ss), dcgrp);
if (!ret)
continue;
/*
* Rebinding back to the default root is not allowed to
* fail. Using both default and non-default roots should
* be rare. Moving subsystems back and forth even more so.
* Just warn about it and continue.
*/
if (dst_root == &cgrp_dfl_root) {
if (cgrp_dfl_visible) {
pr_warn("failed to create files (%d) while rebinding 0x%x to default root\n",
ret, ss_mask);
pr_warn("you may retry by moving them to a different hierarchy and unbinding\n");
}
continue;
}
do_each_subsys_mask(ss, tssid, tmp_ss_mask) {
if (tssid == ssid)
break;
css_clear_dir(cgroup_css(scgrp, ss), dcgrp);
} while_each_subsys_mask();
return ret;
} while_each_subsys_mask();
/*
* Nothing can fail from this point on. Remove files for the
* removed subsystems and rebind each subsystem.
*/
do_each_subsys_mask(ss, ssid, ss_mask) { do_each_subsys_mask(ss, ssid, ss_mask) {
struct cgroup_root *src_root = ss->root; struct cgroup_root *src_root = ss->root;
struct cgroup *scgrp = &src_root->cgrp; struct cgroup *scgrp = &src_root->cgrp;
...@@ -1589,8 +1533,12 @@ static int rebind_subsystems(struct cgroup_root *dst_root, u16 ss_mask) ...@@ -1589,8 +1533,12 @@ static int rebind_subsystems(struct cgroup_root *dst_root, u16 ss_mask)
WARN_ON(!css || cgroup_css(dcgrp, ss)); WARN_ON(!css || cgroup_css(dcgrp, ss));
css_clear_dir(css, NULL); /* disable from the source */
src_root->subsys_mask &= ~(1 << ssid);
WARN_ON(cgroup_apply_control(scgrp));
cgroup_finalize_control(scgrp, 0);
/* rebind */
RCU_INIT_POINTER(scgrp->subsys[ssid], NULL); RCU_INIT_POINTER(scgrp->subsys[ssid], NULL);
rcu_assign_pointer(dcgrp->subsys[ssid], css); rcu_assign_pointer(dcgrp->subsys[ssid], css);
ss->root = dst_root; ss->root = dst_root;
...@@ -1602,20 +1550,20 @@ static int rebind_subsystems(struct cgroup_root *dst_root, u16 ss_mask) ...@@ -1602,20 +1550,20 @@ static int rebind_subsystems(struct cgroup_root *dst_root, u16 ss_mask)
&dcgrp->e_csets[ss->id]); &dcgrp->e_csets[ss->id]);
spin_unlock_bh(&css_set_lock); spin_unlock_bh(&css_set_lock);
src_root->subsys_mask &= ~(1 << ssid);
scgrp->subtree_control &= ~(1 << ssid);
cgroup_refresh_subtree_ss_mask(scgrp);
/* default hierarchy doesn't enable controllers by default */ /* default hierarchy doesn't enable controllers by default */
dst_root->subsys_mask |= 1 << ssid; dst_root->subsys_mask |= 1 << ssid;
if (dst_root == &cgrp_dfl_root) { if (dst_root == &cgrp_dfl_root) {
static_branch_enable(cgroup_subsys_on_dfl_key[ssid]); static_branch_enable(cgroup_subsys_on_dfl_key[ssid]);
} else { } else {
dcgrp->subtree_control |= 1 << ssid; dcgrp->subtree_control |= 1 << ssid;
cgroup_refresh_subtree_ss_mask(dcgrp);
static_branch_disable(cgroup_subsys_on_dfl_key[ssid]); static_branch_disable(cgroup_subsys_on_dfl_key[ssid]);
} }
ret = cgroup_apply_control(dcgrp);
if (ret)
pr_warn("partial failure to rebind %s controller (err=%d)\n",
ss->name, ret);
if (ss->bind) if (ss->bind)
ss->bind(css); ss->bind(css);
} while_each_subsys_mask(); } while_each_subsys_mask();
...@@ -1807,7 +1755,7 @@ static int cgroup_remount(struct kernfs_root *kf_root, int *flags, char *data) ...@@ -1807,7 +1755,7 @@ static int cgroup_remount(struct kernfs_root *kf_root, int *flags, char *data)
return -EINVAL; return -EINVAL;
} }
mutex_lock(&cgroup_mutex); cgroup_lock_and_drain_offline(&cgrp_dfl_root.cgrp);
/* See what subsystems are wanted */ /* See what subsystems are wanted */
ret = parse_cgroupfs_options(data, &opts); ret = parse_cgroupfs_options(data, &opts);
...@@ -1840,7 +1788,7 @@ static int cgroup_remount(struct kernfs_root *kf_root, int *flags, char *data) ...@@ -1840,7 +1788,7 @@ static int cgroup_remount(struct kernfs_root *kf_root, int *flags, char *data)
if (ret) if (ret)
goto out_unlock; goto out_unlock;
rebind_subsystems(&cgrp_dfl_root, removed_mask); WARN_ON(rebind_subsystems(&cgrp_dfl_root, removed_mask));
if (opts.release_agent) { if (opts.release_agent) {
spin_lock(&release_agent_path_lock); spin_lock(&release_agent_path_lock);
...@@ -1991,7 +1939,7 @@ static int cgroup_setup_root(struct cgroup_root *root, u16 ss_mask) ...@@ -1991,7 +1939,7 @@ static int cgroup_setup_root(struct cgroup_root *root, u16 ss_mask)
} }
root_cgrp->kn = root->kf_root->kn; root_cgrp->kn = root->kf_root->kn;
ret = css_populate_dir(&root_cgrp->self, NULL); ret = css_populate_dir(&root_cgrp->self);
if (ret) if (ret)
goto destroy_root; goto destroy_root;
...@@ -2070,7 +2018,7 @@ static struct dentry *cgroup_mount(struct file_system_type *fs_type, ...@@ -2070,7 +2018,7 @@ static struct dentry *cgroup_mount(struct file_system_type *fs_type,
goto out_mount; goto out_mount;
} }
mutex_lock(&cgroup_mutex); cgroup_lock_and_drain_offline(&cgrp_dfl_root.cgrp);
/* First find the desired set of subsystems */ /* First find the desired set of subsystems */
ret = parse_cgroupfs_options(data, &opts); ret = parse_cgroupfs_options(data, &opts);
...@@ -3123,7 +3071,7 @@ static int cgroup_apply_control_enable(struct cgroup *cgrp) ...@@ -3123,7 +3071,7 @@ static int cgroup_apply_control_enable(struct cgroup *cgrp)
} }
if (cgroup_control(dsct) & (1 << ss->id)) { if (cgroup_control(dsct) & (1 << ss->id)) {
ret = css_populate_dir(css, NULL); ret = css_populate_dir(css);
if (ret) if (ret)
return ret; return ret;
} }
...@@ -3162,10 +3110,11 @@ static void cgroup_apply_control_disable(struct cgroup *cgrp) ...@@ -3162,10 +3110,11 @@ static void cgroup_apply_control_disable(struct cgroup *cgrp)
if (!css) if (!css)
continue; continue;
if (!(cgroup_ss_mask(dsct) & (1 << ss->id))) { if (css->parent &&
!(cgroup_ss_mask(dsct) & (1 << ss->id))) {
kill_css(css); kill_css(css);
} else if (!(cgroup_control(dsct) & (1 << ss->id))) { } else if (!(cgroup_control(dsct) & (1 << ss->id))) {
css_clear_dir(css, NULL); css_clear_dir(css);
if (ss->css_reset) if (ss->css_reset)
ss->css_reset(css); ss->css_reset(css);
} }
...@@ -5159,7 +5108,7 @@ static int cgroup_mkdir(struct kernfs_node *parent_kn, const char *name, ...@@ -5159,7 +5108,7 @@ static int cgroup_mkdir(struct kernfs_node *parent_kn, const char *name,
if (ret) if (ret)
goto out_destroy; goto out_destroy;
ret = css_populate_dir(&cgrp->self, NULL); ret = css_populate_dir(&cgrp->self);
if (ret) if (ret)
goto out_destroy; goto out_destroy;
...@@ -5231,7 +5180,7 @@ static void kill_css(struct cgroup_subsys_state *css) ...@@ -5231,7 +5180,7 @@ static void kill_css(struct cgroup_subsys_state *css)
* This must happen before css is disassociated with its cgroup. * This must happen before css is disassociated with its cgroup.
* See seq_css() for details. * See seq_css() for details.
*/ */
css_clear_dir(css, NULL); css_clear_dir(css);
/* /*
* Killing would put the base ref, but we need to keep it alive * Killing would put the base ref, but we need to keep it alive
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment