Skip to contentSkip to conversation

cgroup: stable threadgroup during attach & subsys methods consolidation

PATCH In review Tejun Heo 9 participants 120 messages Nov 1, 2011 – Dec 13, 2011 16 messages not in the archive

Subsystems (11)
lore
Share & tools
lore

Tejun Heo

author maintainer
#0 lore raw

PATCH 1/1 cgroup: stable threadgroup during attach & subsys methods consolidation

Hello,

NOT FOR THIS MERGE WINDOW.

This patchset is combination of the following two patchsets.

[1] cgroup: extend threadgroup locking
[2] cgroup: introduce cgroup_taskset and consolidate subsys methods, take#2

Changes from the last postings are

* 0001-cgroup-add-cgroup_root_mutex.patch replaces mutex ordering reversal patch, which Oleg found out to be broken. Instead, a new sub-mutex cgroup_root_mutex is introduced to break circular dependency.

* Rebased on top of the current linus/master.

* Other minor changes to reflect comments from reviews.

* Reviewed/Acked-by's added.

This patchset addresses the following two issues.

1. cgroup currently only blocks new threads from joining the target threadgroup during migration, and on-going migration could race against exec and exit leading to interesting problems - the symmetry between various attach methods, task exiting during method execution, ->exit() racing against attach methods, migrating task switching basic properties during exec and so on.

This is resolved by extending threadgroup locking such that it covers all operations which can alter the threadgroup - fork, exit and exec, and update cgroup to take advantage of it. rwsem read ops are added to exit path but exec is excluded by grabbing the existing cred_guard_mutex from threadgroup locking helper.

This makes threadgroup locking complete and resolves cgroup issues stemming from the target taskset being unstable.

2. cgroup has grown quite some number of subsys methods. Some of them are overlapping, inconsistent with each other and called under different conditions depending on whether they're called for a single task or whole process. Unfortunately, these callbacks are complicated and incomplete at the same time.

* ->attach_task() is called after migration for task attach but before for process.

* Ditto for ->pre_attach().

* ->can_attach_task() is called for every task in the thread group

     ->but attach_task() skips the ones which don't actually change
     ->cgroups.

* Task attach becomes noop if the task isn't actually moving. Process attach is always performed.

* ->attach_task() doesn't (or at least aren't supposed to) have access to the old cgroup.

* During cancel, there's no way to access the affected tasks.

This patchset introduces cgroup_taskset along with some accessors and iterator, updates methods to use it, consolidates usages and drops superflous methods.

0001-cgroup-add-cgroup_root_mutex.patch 0002-threadgroup-rename-signal-threadgroup_fork_lock-to-g.patch 0003-threadgroup-extend-threadgroup_lock-to-cover-exit-an.patch 0004-cgroup-always-lock-threadgroup-during-migration.patch 0005-cgroup-subsys-attach_task-should-be-called-after-mig.patch 0006-cgroup-improve-old-cgroup-handling-in-cgroup_attach_.patch 0007-cgroup-introduce-cgroup_taskset-and-use-it-in-subsys.patch 0008-cgroup-don-t-use-subsys-can_attach_task-or-attach_ta.patch 0009-cgroup-cpuset-don-t-use-ss-pre_attach.patch 0010-cgroup-kill-subsys-can_attach_task-pre_attach-and-at.patch

0001-0004 implement stable thread group.

0005-0010 introduce taskset and consolidate subsys callbacks.

This patchset is on top of the current linus/master 839d881074 "Merge branch 'i2c-for-linus' of ..." and also available in the following git branch.

git://git.kernel.org/pub/scm/linux/kernel/git/tj/misc.git cgroup-cleanup

If this looks okay, I think it would be best to route this through pm tree as there are and will be intersecting changes (mostly around cgroup_freezer).

diffstat follows.

 Documentation/cgroups/cgroups.txt |   51 ++----
 block/blk-cgroup.c                |   45 +++--
 include/linux/cgroup.h            |   31 ++-
 include/linux/init_task.h         |    9 -
 include/linux/sched.h             |   62 +++++--
 kernel/cgroup.c                   |  320 +++++++++++++++++++++++---------------
 kernel/cgroup_freezer.c           |   27 +--
 kernel/cpuset.c                   |  105 +++++-------
 kernel/events/core.c              |   13 -
 kernel/exit.c                     |   17 +-
 kernel/fork.c                     |    8 
 kernel/sched.c                    |   31 ++-
 mm/memcontrol.c                   |   16 -
 security/device_cgroup.c          |    7 
 14 files changed, 430 insertions(+), 312 deletions(-)

Thanks.

--
tejun

[1] http://thread.gmane.org/gmane.linux.kernel/1187853
[2] http://thread.gmane.org/gmane.linux.kernel/1184375

author maintainer
#1 lore raw

PATCH 1/10 cgroup: add cgroup_root_mutex

cgroup wants to make threadgroup stable while modifying cgroup hierarchies which will introduce locking dependency on cred_guard_mutex from cgroup_mutex. This unfortunately completes circular dependency.

A. cgroup_mutex -> cred_guard_mutex -> s_type->i_mutex_key -> namespace_sem B. namespace_sem -> cgroup_mutex

B is from cgroup_show_options() and this patch breaks it by introducing another mutex cgroup_root_mutex which nests inside cgroup_mutex and protects cgroupfs_root.

Cc Oleg Nesterov <[email protected]>
---
 kernel/cgroup.c |   64 ++++++++++++++++++++++++++++++++++++-------------------
 1 files changed, 42 insertions(+), 22 deletions(-)
kernel/cgroup.c +42 -22
index 453100a..efa5886 100644
@@ -63,7 +63,24 @@
 
 #include <linux/atomic.h>
 
+/*
+ * cgroup_mutex is the master lock.  Any modification to cgroup or its
+ * hierarchy must be performed while holding it.
+ *
+ * cgroup_root_mutex nests inside cgroup_mutex and should be held to modify
+ * cgroupfs_root of any cgroup hierarchy - subsys list, flags,
+ * release_agent_path and so on.  Modifying requires both cgroup_mutex and
+ * cgroup_root_mutex.  Readers can acquire either of the two.  This is to
+ * break the following locking order cycle.
+ *
+ *  A. cgroup_mutex -> cred_guard_mutex -> s_type->i_mutex_key -> namespace_sem
+ *  B. namespace_sem -> cgroup_mutex
+ *
+ * B happens only through cgroup_show_options() and using cgroup_root_mutex
+ * breaks it.
+ */
 static DEFINE_MUTEX(cgroup_mutex);
+static DEFINE_MUTEX(cgroup_root_mutex);
 
 /*
  * Generate an array of cgroup subsystem pointers. At boot time, this is
@@ -953,6 +970,7 @@ static int rebind_subsystems(struct cgroupfs_root *root,
 	int i;
 
 	BUG_ON(!mutex_is_locked(&cgroup_mutex));
+	BUG_ON(!mutex_is_locked(&cgroup_root_mutex));
 
 	removed_bits = root->actual_subsys_bits & ~final_bits;
 	added_bits = final_bits & ~root->actual_subsys_bits;
@@ -1043,7 +1061,7 @@ static int cgroup_show_options(struct seq_file *seq, struct vfsmount *vfs)
 	struct cgroupfs_root *root = vfs->mnt_sb->s_fs_info;
 	struct cgroup_subsys *ss;
 
-	mutex_lock(&cgroup_mutex);
+	mutex_lock(&cgroup_root_mutex);
 	for_each_subsys(root, ss)
 		seq_printf(seq, ",%s", ss->name);
 	if (test_bit(ROOT_NOPREFIX, &root->flags))
@@ -1054,7 +1072,7 @@ static int cgroup_show_options(struct seq_file *seq, struct vfsmount *vfs)
 		seq_puts(seq, ",clone_children");
 	if (strlen(root->name))
 		seq_printf(seq, ",name=%s", root->name);
-	mutex_unlock(&cgroup_mutex);
+	mutex_unlock(&cgroup_root_mutex);
 	return 0;
 }
 
@@ -1269,6 +1287,7 @@ static int cgroup_remount(struct super_block *sb, int *flags, char *data)
 
 	mutex_lock(&cgrp->dentry->d_inode->i_mutex);
 	mutex_lock(&cgroup_mutex);
+	mutex_lock(&cgroup_root_mutex);
 
 	/* See what subsystems are wanted */
 	ret = parse_cgroupfs_options(data, &opts);
@@ -1297,6 +1316,7 @@ static int cgroup_remount(struct super_block *sb, int *flags, char *data)
  out_unlock:
 	kfree(opts.release_agent);
 	kfree(opts.name);
+	mutex_unlock(&cgroup_root_mutex);
 	mutex_unlock(&cgroup_mutex);
 	mutex_unlock(&cgrp->dentry->d_inode->i_mutex);
 	return ret;
@@ -1481,6 +1501,7 @@ static struct dentry *cgroup_mount(struct file_system_type *fs_type,
 	int ret = 0;
 	struct super_block *sb;
 	struct cgroupfs_root *new_root;
+	struct inode *inode;
 
 	/* First find the desired set of subsystems */
 	mutex_lock(&cgroup_mutex);
@@ -1514,7 +1535,6 @@ static struct dentry *cgroup_mount(struct file_system_type *fs_type,
 		/* We used the new root structure, so this is a new hierarchy */
 		struct list_head tmp_cg_links;
 		struct cgroup *root_cgrp = &root->top_cgroup;
-		struct inode *inode;
 		struct cgroupfs_root *existing_root;
 		const struct cred *cred;
 		int i;
@@ -1528,18 +1548,14 @@ static struct dentry *cgroup_mount(struct file_system_type *fs_type,
 
 		mutex_lock(&inode->i_mutex);
 		mutex_lock(&cgroup_mutex);
+		mutex_lock(&cgroup_root_mutex);
 
-		if (strlen(root->name)) {
-			/* Check for name clashes with existing mounts */
-			for_each_active_root(existing_root) {
-				if (!strcmp(existing_root->name, root->name)) {
-					ret = -EBUSY;
-					mutex_unlock(&cgroup_mutex);
-					mutex_unlock(&inode->i_mutex);
-					goto drop_new_super;
-				}
-			}
-		}
+		/* Check for name clashes with existing mounts */
+		ret = -EBUSY;
+		if (strlen(root->name))
+			for_each_active_root(existing_root)
+				if (!strcmp(existing_root->name, root->name))
+					goto unlock_drop;
 
 		/*
 		 * We're accessing css_set_count without locking
@@ -1549,18 +1565,13 @@ static struct dentry *cgroup_mount(struct file_system_type *fs_type,
 		 * have some link structures left over
 		 */
 		ret = allocate_cg_links(css_set_count, &tmp_cg_links);
-		if (ret) {
-			mutex_unlock(&cgroup_mutex);
-			mutex_unlock(&inode->i_mutex);
-			goto drop_new_super;
-		}
+		if (ret)
+			goto unlock_drop;
 
 		ret = rebind_subsystems(root, root->subsys_bits);
 		if (ret == -EBUSY) {
-			mutex_unlock(&cgroup_mutex);
-			mutex_unlock(&inode->i_mutex);
 			free_cg_links(&tmp_cg_links);
-			goto drop_new_super;
+			goto unlock_drop;
 		}
 		/*
 		 * There must be no failure case after here, since rebinding
@@ -1599,6 +1610,7 @@ static struct dentry *cgroup_mount(struct file_system_type *fs_type,
 		cred = override_creds(&init_cred);
 		cgroup_populate_dir(root_cgrp);
 		revert_creds(cred);
+		mutex_unlock(&cgroup_root_mutex);
 		mutex_unlock(&cgroup_mutex);
 		mutex_unlock(&inode->i_mutex);
 	} else {
@@ -1615,6 +1627,10 @@ static struct dentry *cgroup_mount(struct file_system_type *fs_type,
 	kfree(opts.name);
 	return dget(sb->s_root);
 
+ unlock_drop:
+	mutex_unlock(&cgroup_root_mutex);
+	mutex_unlock(&cgroup_mutex);
+	mutex_unlock(&inode->i_mutex);
  drop_new_super:
 	deactivate_locked_super(sb);
  drop_modules:
@@ -1639,6 +1655,7 @@ static void cgroup_kill_sb(struct super_block *sb) {
 	BUG_ON(!list_empty(&cgrp->sibling));
 
 	mutex_lock(&cgroup_mutex);
+	mutex_lock(&cgroup_root_mutex);
 
 	/* Rebind all subsystems back to the default hierarchy */
 	ret = rebind_subsystems(root, 0);
@@ -1664,6 +1681,7 @@ static void cgroup_kill_sb(struct super_block *sb) {
 		root_count--;
 	}
 
+	mutex_unlock(&cgroup_root_mutex);
 	mutex_unlock(&cgroup_mutex);
 
 	kill_litter_super(sb);
@@ -2308,7 +2326,9 @@ static int cgroup_release_agent_write(struct cgroup *cgrp, struct cftype *cft,
 		return -EINVAL;
 	if (!cgroup_lock_live_group(cgrp))
 		return -ENODEV;
+	mutex_lock(&cgroup_root_mutex);
 	strcpy(cgrp->root->release_agent_path, buffer);
+	mutex_unlock(&cgroup_root_mutex);
 	cgroup_unlock();
 	return 0;
 }
Signature · 1 line
1.7.3.1

A message that is not in the archive (sent off-list or not yet received): 1320191193-8110-2-git-send-email-tj-DgEjT+Ai2ygdnm+yROfE0A@public.gmane.org

Reviewed-by
#3 lore raw

Re: [PATCH 01/10] cgroup: add cgroup_root_mutex

Quoted 8 lines · On Tue, 1 Nov 2011 16:46:24 -0700 Tejun Heo…

cgroup wants to make threadgroup stable while modifying cgroup hierarchies which will introduce locking dependency on cred_guard_mutex from cgroup_mutex. This unfortunately completes circular dependency.

A. cgroup_mutex -> cred_guard_mutex -> s_type->i_mutex_key -> namespace_sem B. namespace_sem -> cgroup_mutex

Quoted 6 lines

B is from cgroup_show_options() and this patch breaks it by introducing another mutex cgroup_root_mutex which nests inside cgroup_mutex and protects cgroupfs_root.

Cc Oleg Nesterov <[email protected]>

Thanks,

Reviewed-by on v1 1/10
#4 lore raw

Re: [PATCH 01/10] cgroup: add cgroup_root_mutex

In reply to Tejun Heo
Quoted 8 lines · On Tue, 1 Nov 2011 16:46:24 -0700 Tejun Heo <[email protected]> wrote:

cgroup wants to make threadgroup stable while modifying cgroup hierarchies which will introduce locking dependency on cred_guard_mutex from cgroup_mutex. This unfortunately completes circular dependency.

A. cgroup_mutex -> cred_guard_mutex -> s_type->i_mutex_key -> namespace_sem B. namespace_sem -> cgroup_mutex

Quoted 6 lines

B is from cgroup_show_options() and this patch breaks it by introducing another mutex cgroup_root_mutex which nests inside cgroup_mutex and protects cgroupfs_root.

Cc Oleg Nesterov <[email protected]>

Thanks,

Reviewed-by on v1 1/10
#5 lore raw

Re: [PATCH 01/10] cgroup: add cgroup_root_mutex

In reply to Tejun Heo
Quoted 8 lines · On Tue, 1 Nov 2011 16:46:24 -0700 Tejun Heo <[email protected]> wrote:

cgroup wants to make threadgroup stable while modifying cgroup hierarchies which will introduce locking dependency on cred_guard_mutex from cgroup_mutex. This unfortunately completes circular dependency.

A. cgroup_mutex -> cred_guard_mutex -> s_type->i_mutex_key -> namespace_sem B. namespace_sem -> cgroup_mutex

Quoted 6 lines

B is from cgroup_show_options() and this patch breaks it by introducing another mutex cgroup_root_mutex which nests inside cgroup_mutex and protects cgroupfs_root.

Cc Oleg Nesterov <[email protected]>

Thanks,

A message that is not in the archive (sent off-list or not yet received): 1320191193-8110-1-git-send-email-tj-DgEjT+Ai2ygdnm+yROfE0A@public.gmane.org

#7 lore raw

PATCH 1/10 cgroup: add cgroup_root_mutex

cgroup wants to make threadgroup stable while modifying cgroup hierarchies which will introduce locking dependency on cred_guard_mutex from cgroup_mutex. This unfortunately completes circular dependency.

A. cgroup_mutex -> cred_guard_mutex -> s_type->i_mutex_key -> namespace_sem B. namespace_sem -> cgroup_mutex

B is from cgroup_show_options() and this patch breaks it by introducing another mutex cgroup_root_mutex which nests inside cgroup_mutex and protects cgroupfs_root.

Cc Oleg Nesterov <[email protected]>
---
 kernel/cgroup.c |   64 ++++++++++++++++++++++++++++++++++++-------------------
 1 files changed, 42 insertions(+), 22 deletions(-)
kernel/cgroup.c +42 -22
index 453100a..efa5886 100644
@@ -63,7 +63,24 @@
 
 #include <linux/atomic.h>
 
+/*
+ * cgroup_mutex is the master lock.  Any modification to cgroup or its
+ * hierarchy must be performed while holding it.
+ *
+ * cgroup_root_mutex nests inside cgroup_mutex and should be held to modify
+ * cgroupfs_root of any cgroup hierarchy - subsys list, flags,
+ * release_agent_path and so on.  Modifying requires both cgroup_mutex and
+ * cgroup_root_mutex.  Readers can acquire either of the two.  This is to
+ * break the following locking order cycle.
+ *
+ *  A. cgroup_mutex -> cred_guard_mutex -> s_type->i_mutex_key -> namespace_sem
+ *  B. namespace_sem -> cgroup_mutex
+ *
+ * B happens only through cgroup_show_options() and using cgroup_root_mutex
+ * breaks it.
+ */
 static DEFINE_MUTEX(cgroup_mutex);
+static DEFINE_MUTEX(cgroup_root_mutex);
 
 /*
  * Generate an array of cgroup subsystem pointers. At boot time, this is
@@ -953,6 +970,7 @@ static int rebind_subsystems(struct cgroupfs_root *root,
 	int i;
 
 	BUG_ON(!mutex_is_locked(&cgroup_mutex));
+	BUG_ON(!mutex_is_locked(&cgroup_root_mutex));
 
 	removed_bits = root->actual_subsys_bits & ~final_bits;
 	added_bits = final_bits & ~root->actual_subsys_bits;
@@ -1043,7 +1061,7 @@ static int cgroup_show_options(struct seq_file *seq, struct vfsmount *vfs)
 	struct cgroupfs_root *root = vfs->mnt_sb->s_fs_info;
 	struct cgroup_subsys *ss;
 
-	mutex_lock(&cgroup_mutex);
+	mutex_lock(&cgroup_root_mutex);
 	for_each_subsys(root, ss)
 		seq_printf(seq, ",%s", ss->name);
 	if (test_bit(ROOT_NOPREFIX, &root->flags))
@@ -1054,7 +1072,7 @@ static int cgroup_show_options(struct seq_file *seq, struct vfsmount *vfs)
 		seq_puts(seq, ",clone_children");
 	if (strlen(root->name))
 		seq_printf(seq, ",name=%s", root->name);
-	mutex_unlock(&cgroup_mutex);
+	mutex_unlock(&cgroup_root_mutex);
 	return 0;
 }
 
@@ -1269,6 +1287,7 @@ static int cgroup_remount(struct super_block *sb, int *flags, char *data)
 
 	mutex_lock(&cgrp->dentry->d_inode->i_mutex);
 	mutex_lock(&cgroup_mutex);
+	mutex_lock(&cgroup_root_mutex);
 
 	/* See what subsystems are wanted */
 	ret = parse_cgroupfs_options(data, &opts);
@@ -1297,6 +1316,7 @@ static int cgroup_remount(struct super_block *sb, int *flags, char *data)
  out_unlock:
 	kfree(opts.release_agent);
 	kfree(opts.name);
+	mutex_unlock(&cgroup_root_mutex);
 	mutex_unlock(&cgroup_mutex);
 	mutex_unlock(&cgrp->dentry->d_inode->i_mutex);
 	return ret;
@@ -1481,6 +1501,7 @@ static struct dentry *cgroup_mount(struct file_system_type *fs_type,
 	int ret = 0;
 	struct super_block *sb;
 	struct cgroupfs_root *new_root;
+	struct inode *inode;
 
 	/* First find the desired set of subsystems */
 	mutex_lock(&cgroup_mutex);
@@ -1514,7 +1535,6 @@ static struct dentry *cgroup_mount(struct file_system_type *fs_type,
 		/* We used the new root structure, so this is a new hierarchy */
 		struct list_head tmp_cg_links;
 		struct cgroup *root_cgrp = &root->top_cgroup;
-		struct inode *inode;
 		struct cgroupfs_root *existing_root;
 		const struct cred *cred;
 		int i;
@@ -1528,18 +1548,14 @@ static struct dentry *cgroup_mount(struct file_system_type *fs_type,
 
 		mutex_lock(&inode->i_mutex);
 		mutex_lock(&cgroup_mutex);
+		mutex_lock(&cgroup_root_mutex);
 
-		if (strlen(root->name)) {
-			/* Check for name clashes with existing mounts */
-			for_each_active_root(existing_root) {
-				if (!strcmp(existing_root->name, root->name)) {
-					ret = -EBUSY;
-					mutex_unlock(&cgroup_mutex);
-					mutex_unlock(&inode->i_mutex);
-					goto drop_new_super;
-				}
-			}
-		}
+		/* Check for name clashes with existing mounts */
+		ret = -EBUSY;
+		if (strlen(root->name))
+			for_each_active_root(existing_root)
+				if (!strcmp(existing_root->name, root->name))
+					goto unlock_drop;
 
 		/*
 		 * We're accessing css_set_count without locking
@@ -1549,18 +1565,13 @@ static struct dentry *cgroup_mount(struct file_system_type *fs_type,
 		 * have some link structures left over
 		 */
 		ret = allocate_cg_links(css_set_count, &tmp_cg_links);
-		if (ret) {
-			mutex_unlock(&cgroup_mutex);
-			mutex_unlock(&inode->i_mutex);
-			goto drop_new_super;
-		}
+		if (ret)
+			goto unlock_drop;
 
 		ret = rebind_subsystems(root, root->subsys_bits);
 		if (ret == -EBUSY) {
-			mutex_unlock(&cgroup_mutex);
-			mutex_unlock(&inode->i_mutex);
 			free_cg_links(&tmp_cg_links);
-			goto drop_new_super;
+			goto unlock_drop;
 		}
 		/*
 		 * There must be no failure case after here, since rebinding
@@ -1599,6 +1610,7 @@ static struct dentry *cgroup_mount(struct file_system_type *fs_type,
 		cred = override_creds(&init_cred);
 		cgroup_populate_dir(root_cgrp);
 		revert_creds(cred);
+		mutex_unlock(&cgroup_root_mutex);
 		mutex_unlock(&cgroup_mutex);
 		mutex_unlock(&inode->i_mutex);
 	} else {
@@ -1615,6 +1627,10 @@ static struct dentry *cgroup_mount(struct file_system_type *fs_type,
 	kfree(opts.name);
 	return dget(sb->s_root);
 
+ unlock_drop:
+	mutex_unlock(&cgroup_root_mutex);
+	mutex_unlock(&cgroup_mutex);
+	mutex_unlock(&inode->i_mutex);
  drop_new_super:
 	deactivate_locked_super(sb);
  drop_modules:
@@ -1639,6 +1655,7 @@ static void cgroup_kill_sb(struct super_block *sb) {
 	BUG_ON(!list_empty(&cgrp->sibling));
 
 	mutex_lock(&cgroup_mutex);
+	mutex_lock(&cgroup_root_mutex);
 
 	/* Rebind all subsystems back to the default hierarchy */
 	ret = rebind_subsystems(root, 0);
@@ -1664,6 +1681,7 @@ static void cgroup_kill_sb(struct super_block *sb) {
 		root_count--;
 	}
 
+	mutex_unlock(&cgroup_root_mutex);
 	mutex_unlock(&cgroup_mutex);
 
 	kill_litter_super(sb);
@@ -2308,7 +2326,9 @@ static int cgroup_release_agent_write(struct cgroup *cgrp, struct cftype *cft,
 		return -EINVAL;
 	if (!cgroup_lock_live_group(cgrp))
 		return -ENODEV;
+	mutex_lock(&cgroup_root_mutex);
 	strcpy(cgrp->root->release_agent_path, buffer);
+	mutex_unlock(&cgroup_root_mutex);
 	cgroup_unlock();
 	return 0;
 }
Signature · 1 line
1.7.3.1
#8 lore raw

PATCH 3/10 threadgroup: extend threadgroup_lock() to cover exit and exec

In reply to a message not in the archive

threadgroup_lock() protected only protected against new addition to the threadgroup, which was inherently somewhat incomplete and problematic for its only user cgroup. On-going migration could race against exec and exit leading to interesting problems - the symmetry between various attach methods, task exiting during method execution, ->exit() racing against attach methods, migrating task switching basic properties during exec and so on.

This patch extends threadgroup_lock() such that it protects against all three threadgroup altering operations - fork, exit and exec. For exit, threadgroup_change_begin/end() calls are added to exit path. For exec, threadgroup_[un]lock() are updated to also grab and release cred_guard_mutex.

With this change, threadgroup_lock() guarantees that the target threadgroup will remain stable - no new task will be added, no new PF_EXITING will be set and exec won't happen.

The next patch will update cgroup so that it can take full advantage of this change.

Cc Oleg Nesterov <[email protected]>
Cc Andrew Morton <akpm-de/[email protected]>
Cc Paul Menage <[email protected]>
Cc Li Zefan <[email protected]>
---
 include/linux/sched.h |   36 ++++++++++++++++++++++++++++++------
 kernel/exit.c         |   17 +++++++++++++----
 2 files changed, 43 insertions(+), 10 deletions(-)
include/linux/sched.h +30 -6
index aa47d0f..6fb546e 100644
@@ -635,11 +635,12 @@ struct signal_struct {
 #endif
 #ifdef CONFIG_CGROUPS
 	/*
-	 * The group_rwsem prevents threads from forking with
-	 * CLONE_THREAD while held for writing. Use this for fork-sensitive
-	 * threadgroup-wide operations. It's taken for reading in fork.c in
-	 * copy_process().
-	 * Currently only needed write-side by cgroups.
+	 * group_rwsem prevents new tasks from entering the threadgroup and
+	 * member tasks from exiting.  fork and exit paths are protected
+	 * with this rwsem using threadgroup_change_begin/end().  Users
+	 * which require threadgroup to remain stable should use
+	 * threadgroup_[un]lock() which also takes care of exec path.
+	 * Currently, cgroup is the only user.
 	 */
 	struct rw_semaphore group_rwsem;
 #endif
@@ -2367,7 +2368,6 @@ static inline void unlock_task_sighand(struct task_struct *tsk,
 	spin_unlock_irqrestore(&tsk->sighand->siglock, *flags);
 }
 
-/* See the declaration of group_rwsem in signal_struct. */
 #ifdef CONFIG_CGROUPS
 static inline void threadgroup_change_begin(struct task_struct *tsk)
 {
@@ -2377,13 +2377,37 @@ static inline void threadgroup_change_done(struct task_struct *tsk)
 {
 	up_read(&tsk->signal->group_rwsem);
 }
+
+/**
+ * threadgroup_lock - lock threadgroup
+ * @tsk: member task of the threadgroup to lock
+ *
+ * Lock the threadgroup @tsk belongs to.  No new task is allowed to enter
+ * and member tasks aren't allowed to exit (as indicated by PF_EXITING) or
+ * perform exec.  This is useful for cases where the threadgroup needs to
+ * stay stable across blockable operations.
+ *
+ * fork and exit explicitly call threadgroup_change_{begin|end}() for
+ * synchronization.  exec is already synchronized with cread_guard_mutex
+ * which we grab here.
+ */
 static inline void threadgroup_lock(struct task_struct *tsk)
 {
+	/* exec uses exit for de-threading, grab cred_guard_mutex first */
+	mutex_lock(&tsk->signal->cred_guard_mutex);
 	down_write(&tsk->signal->group_rwsem);
 }
+
+/**
+ * threadgroup_unlock - unlock threadgroup
+ * @tsk: member task of the threadgroup to unlock
+ *
+ * Reverse threadgroup_lock().
+ */
 static inline void threadgroup_unlock(struct task_struct *tsk)
 {
 	up_write(&tsk->signal->group_rwsem);
+	mutex_unlock(&tsk->signal->cred_guard_mutex);
 }
 #else
 static inline void threadgroup_change_begin(struct task_struct *tsk) {}
kernel/exit.c +13 -4
index 2913b35..3565f00 100644
@@ -938,6 +938,12 @@ NORET_TYPE void do_exit(long code)
 		schedule();
 	}
 
+	/*
+	 * @tsk's threadgroup is going through changes - lock out users
+	 * which expect stable threadgroup.
+	 */
+	threadgroup_change_begin(tsk);
+
 	exit_irq_thread();
 
 	exit_signals(tsk);  /* sets PF_EXITING */
@@ -1020,10 +1026,6 @@ NORET_TYPE void do_exit(long code)
 		kfree(current->pi_state_cache);
 #endif
 	/*
-	 * Make sure we are holding no locks:
-	 */
-	debug_check_no_locks_held(tsk);
-	/*
 	 * We can do this unlocked here. The futex code uses this flag
 	 * just to verify whether the pi state cleanup has been done
 	 * or not. In the worst case it loops once more.
@@ -1040,6 +1042,13 @@ NORET_TYPE void do_exit(long code)
 
 	preempt_disable();
 	exit_rcu();
+
+	/*
+	 * Release threadgroup and make sure we are holding no locks.
+	 */
+	threadgroup_change_done(tsk);
+	debug_check_no_locks_held(tsk);
+
 	/* causes final put_task_struct in finish_task_switch(). */
 	tsk->state = TASK_DEAD;
 	schedule();
Signature · 1 line
1.7.3.1
#9 lore raw

PATCH 4/10 cgroup: always lock threadgroup during migration

In reply to a message not in the archive

Update cgroup to take advantage of the fack that threadgroup_lock() guarantees stable threadgroup.

* Lock threadgroup even if the target is a single task. This guarantees that when the target tasks stay stable during migration regardless of the target type.

* Remove PF_EXITING early exit optimization from attach_task_by_pid() and check it in cgroup_task_migrate() instead. The optimization was for rather cold path to begin with and PF_EXITING state can be trusted throughout migration by checking it after locking threadgroup.

* Don't add PF_EXITING tasks to target task array in cgroup_attach_proc(). This ensures that task migration is performed only for live tasks.

* Remove -ESRCH failure path from cgroup_task_migrate(). With the above changes, it's guaranteed to be called only for live tasks.

After the changes, only live tasks are migrated and they're guaranteed to stay alive until migration is complete. This removes problems caused by exec and exit racing against cgroup migration including symmetry among cgroup attach methods and different cgroup methods racing each other.

v2: Oleg pointed out that one more PF_EXITING check can be removed
from cgroup_attach_proc(). Removed.

Cc Oleg Nesterov <[email protected]>
Cc Andrew Morton <akpm-de/[email protected]>
Cc Paul Menage <[email protected]>
Cc Li Zefan <[email protected]>
---
 kernel/cgroup.c |   51 +++++++++++++++++++++++----------------------------
 1 files changed, 23 insertions(+), 28 deletions(-)
kernel/cgroup.c +23 -28
index f0e099f..83e10f9 100644
@@ -1762,7 +1762,7 @@ EXPORT_SYMBOL_GPL(cgroup_path);
  *
  * 'guarantee' is set if the caller promises that a new css_set for the task
  * will already exist. If not set, this function might sleep, and can fail with
- * -ENOMEM. Otherwise, it can only fail with -ESRCH.
+ * -ENOMEM. Must be called with cgroup_mutex and threadgroup locked.
  */
 static int cgroup_task_migrate(struct cgroup *cgrp, struct cgroup *oldcgrp,
 			       struct task_struct *tsk, bool guarantee)
@@ -1800,13 +1800,9 @@ static int cgroup_task_migrate(struct cgroup *cgrp, struct cgroup *oldcgrp,
 	}
 	put_css_set(oldcg);
 
-	/* if PF_EXITING is set, the tsk->cgroups pointer is no longer safe. */
+	/* @tsk can't exit as its threadgroup is locked */
 	task_lock(tsk);
-	if (tsk->flags & PF_EXITING) {
-		task_unlock(tsk);
-		put_css_set(newcg);
-		return -ESRCH;
-	}
+	WARN_ON_ONCE(tsk->flags & PF_EXITING);
 	rcu_assign_pointer(tsk->cgroups, newcg);
 	task_unlock(tsk);
 
@@ -1832,8 +1828,8 @@ static int cgroup_task_migrate(struct cgroup *cgrp, struct cgroup *oldcgrp,
  * @cgrp: the cgroup the task is attaching to
  * @tsk: the task to be attached
  *
- * Call holding cgroup_mutex. May take task_lock of
- * the task 'tsk' during call.
+ * Call with cgroup_mutex and threadgroup locked. May take task_lock of
+ * @tsk during call.
  */
 int cgroup_attach_task(struct cgroup *cgrp, struct task_struct *tsk)
 {
@@ -1842,6 +1838,10 @@ int cgroup_attach_task(struct cgroup *cgrp, struct task_struct *tsk)
 	struct cgroup *oldcgrp;
 	struct cgroupfs_root *root = cgrp->root;
 
+	/* @tsk either already exited or can't exit until the end */
+	if (tsk->flags & PF_EXITING)
+		return -ESRCH;
+
 	/* Nothing to do if the task is already in that cgroup */
 	oldcgrp = task_cgroup_from_root(tsk, root);
 	if (cgrp == oldcgrp)
@@ -2062,6 +2062,10 @@ int cgroup_attach_proc(struct cgroup *cgrp, struct task_struct *leader)
 	tsk = leader;
 	i = 0;
 	do {
+		/* @tsk either already exited or can't exit until the end */
+		if (tsk->flags & PF_EXITING)
+			continue;
+
 		/* as per above, nr_threads may decrease, but not increase. */
 		BUG_ON(i >= group_size);
 		get_task_struct(tsk);
@@ -2116,11 +2120,6 @@ int cgroup_attach_proc(struct cgroup *cgrp, struct task_struct *leader)
 			continue;
 		/* get old css_set pointer */
 		task_lock(tsk);
-		if (tsk->flags & PF_EXITING) {
-			/* ignore this task if it's going away */
-			task_unlock(tsk);
-			continue;
-		}
 		oldcg = tsk->cgroups;
 		get_css_set(oldcg);
 		task_unlock(tsk);
@@ -2158,9 +2157,8 @@ int cgroup_attach_proc(struct cgroup *cgrp, struct task_struct *leader)
 			if (ss->attach_task)
 				ss->attach_task(cgrp, tsk);
 		}
-		/* if the thread is PF_EXITING, it can just get skipped. */
 		retval = cgroup_task_migrate(cgrp, oldcgrp, tsk, true);
-		BUG_ON(retval != 0 && retval != -ESRCH);
+		BUG_ON(retval != 0);
 	}
 	/* nothing is sensitive to fork() after this point. */
 
@@ -2212,8 +2210,8 @@ out_free_group_list:
 
 /*
  * Find the task_struct of the task to attach by vpid and pass it along to the
- * function to attach either it or all tasks in its threadgroup. Will take
- * cgroup_mutex; may take task_lock of task.
+ * function to attach either it or all tasks in its threadgroup. Will lock
+ * cgroup_mutex and threadgroup; may take task_lock of task.
  */
 static int attach_task_by_pid(struct cgroup *cgrp, u64 pid, bool threadgroup)
 {
@@ -2240,11 +2238,6 @@ static int attach_task_by_pid(struct cgroup *cgrp, u64 pid, bool threadgroup)
 			 * detect it later.
 			 */
 			tsk = tsk->group_leader;
-		} else if (tsk->flags & PF_EXITING) {
-			/* optimization for the single-task-only case */
-			rcu_read_unlock();
-			cgroup_unlock();
-			return -ESRCH;
 		}
 		/*
 		 * even if we're attaching all tasks in the thread group, we
@@ -2268,13 +2261,15 @@ static int attach_task_by_pid(struct cgroup *cgrp, u64 pid, bool threadgroup)
 		get_task_struct(tsk);
 	}
 
-	if (threadgroup) {
-		threadgroup_lock(tsk);
+	threadgroup_lock(tsk);
+
+	if (threadgroup)
 		ret = cgroup_attach_proc(cgrp, tsk);
-		threadgroup_unlock(tsk);
-	} else {
+	else
 		ret = cgroup_attach_task(cgrp, tsk);
-	}
+
+	threadgroup_unlock(tsk);
+
 	put_task_struct(tsk);
 	cgroup_unlock();
 	return ret;
Signature · 1 line
1.7.3.1
#10 lore raw

PATCH 5/10 cgroup: subsys->attach_task() should be called after migration

In reply to a message not in the archive

cgroup_attach_task() calls subsys->attach_task() after cgroup_task_migrate(); however, cgroup_attach_proc() calls it before migration. This actually affects some of the users. Update cgroup_attach_proc() such that ->attach_task() is called after migration.

---
 kernel/cgroup.c |    6 ++++--
 1 files changed, 4 insertions(+), 2 deletions(-)
kernel/cgroup.c +4 -2
index 83e10f9..3679fb6 100644
@@ -2152,13 +2152,15 @@ int cgroup_attach_proc(struct cgroup *cgrp, struct task_struct *leader)
 		oldcgrp = task_cgroup_from_root(tsk, root);
 		if (cgrp == oldcgrp)
 			continue;
+
+		retval = cgroup_task_migrate(cgrp, oldcgrp, tsk, true);
+		BUG_ON(retval != 0);
+
 		/* attach each task to each subsystem */
 		for_each_subsys(root, ss) {
 			if (ss->attach_task)
 				ss->attach_task(cgrp, tsk);
 		}
-		retval = cgroup_task_migrate(cgrp, oldcgrp, tsk, true);
-		BUG_ON(retval != 0);
 	}
 	/* nothing is sensitive to fork() after this point. */
 
Signature · 1 line
1.7.3.1
#11 lore raw

PATCH 8/10 cgroup: don't use subsys->can_attach_task() or ->attach_task()

In reply to a message not in the archive

Now that subsys->can_attach() and attach() take @tset instead of @task, they can handle per-task operations. Convert ->can_attach_task() and ->attach_task() users to use ->can_attach() and attach() instead. Most converions are straight-forward. Noteworthy changes are,

* In cgroup_freezer, remove unnecessary NULL assignments to unused methods. It's useless and very prone to get out of sync, which already happened.

* In cpuset, PF_THREAD_BOUND test is checked for each task. This doesn't make any practical difference but is conceptually cleaner.

Cc Paul Menage <[email protected]>
Cc Li Zefan <[email protected]>
Cc Balbir Singh <[email protected]>
Cc Daisuke Nishimura <[email protected]>
Cc KAMEZAWA Hiroyuki <[email protected]>
Cc James Morris <jmorris-gx6/[email protected]>
Cc Ingo Molnar <[email protected]>
Cc Peter Zijlstra <[email protected]>
---
 block/blk-cgroup.c      |   45 +++++++++++++++++++-----------
 kernel/cgroup_freezer.c |   25 ++++++-----------
 kernel/cpuset.c         |   70 +++++++++++++++++++++-------------------------
 kernel/events/core.c    |   13 +++++---
 kernel/sched.c          |   31 +++++++++++++--------
 5 files changed, 96 insertions(+), 88 deletions(-)
block/blk-cgroup.c +28 -17
index b596e54..b5e3fef 100644
@@ -30,8 +30,10 @@ EXPORT_SYMBOL_GPL(blkio_root_cgroup);
 
 static struct cgroup_subsys_state *blkiocg_create(struct cgroup_subsys *,
 						  struct cgroup *);
-static int blkiocg_can_attach_task(struct cgroup *, struct task_struct *);
-static void blkiocg_attach_task(struct cgroup *, struct task_struct *);
+static int blkiocg_can_attach(struct cgroup_subsys *, struct cgroup *,
+			      struct cgroup_taskset *);
+static void blkiocg_attach(struct cgroup_subsys *, struct cgroup *,
+			   struct cgroup_taskset *);
 static void blkiocg_destroy(struct cgroup_subsys *, struct cgroup *);
 static int blkiocg_populate(struct cgroup_subsys *, struct cgroup *);
 
@@ -44,8 +46,8 @@ static int blkiocg_populate(struct cgroup_subsys *, struct cgroup *);
 struct cgroup_subsys blkio_subsys = {
 	.name = "blkio",
 	.create = blkiocg_create,
-	.can_attach_task = blkiocg_can_attach_task,
-	.attach_task = blkiocg_attach_task,
+	.can_attach = blkiocg_can_attach,
+	.attach = blkiocg_attach,
 	.destroy = blkiocg_destroy,
 	.populate = blkiocg_populate,
 #ifdef CONFIG_BLK_CGROUP
@@ -1609,30 +1611,39 @@ done:
  * of the main cic data structures.  For now we allow a task to change
  * its cgroup only if it's the only owner of its ioc.
  */
-static int blkiocg_can_attach_task(struct cgroup *cgrp, struct task_struct *tsk)
+static int blkiocg_can_attach(struct cgroup_subsys *ss, struct cgroup *cgrp,
+			      struct cgroup_taskset *tset)
 {
+	struct task_struct *task;
 	struct io_context *ioc;
 	int ret = 0;
 
 	/* task_lock() is needed to avoid races with exit_io_context() */
-	task_lock(tsk);
-	ioc = tsk->io_context;
-	if (ioc && atomic_read(&ioc->nr_tasks) > 1)
-		ret = -EINVAL;
-	task_unlock(tsk);
-
+	cgroup_taskset_for_each(task, cgrp, tset) {
+		task_lock(task);
+		ioc = task->io_context;
+		if (ioc && atomic_read(&ioc->nr_tasks) > 1)
+			ret = -EINVAL;
+		task_unlock(task);
+		if (ret)
+			break;
+	}
 	return ret;
 }
 
-static void blkiocg_attach_task(struct cgroup *cgrp, struct task_struct *tsk)
+static void blkiocg_attach(struct cgroup_subsys *ss, struct cgroup *cgrp,
+			   struct cgroup_taskset *tset)
 {
+	struct task_struct *task;
 	struct io_context *ioc;
 
-	task_lock(tsk);
-	ioc = tsk->io_context;
-	if (ioc)
-		ioc->cgroup_changed = 1;
-	task_unlock(tsk);
+	cgroup_taskset_for_each(task, cgrp, tset) {
+		task_lock(task);
+		ioc = task->io_context;
+		if (ioc)
+			ioc->cgroup_changed = 1;
+		task_unlock(task);
+	}
 }
 
 void blkio_policy_register(struct blkio_policy_type *blkiop)
kernel/cgroup_freezer.c +9 -16
index a72d5fa..68ef861 100644
@@ -163,10 +163,19 @@ static int freezer_can_attach(struct cgroup_subsys *ss,
 			      struct cgroup_taskset *tset)
 {
 	struct freezer *freezer;
+	struct task_struct *task;
 
 	/*
 	 * Anything frozen can't move or be moved to/from.
 	 */
+	rcu_read_lock();
+	cgroup_taskset_for_each(task, new_cgroup, tset) {
+		if (__cgroup_freezing_or_frozen(task)) {
+			rcu_read_unlock();
+			return -EBUSY;
+		}
+	}
+	rcu_read_unlock();
 
 	freezer = cgroup_freezer(new_cgroup);
 	if (freezer->state != CGROUP_THAWED)
@@ -175,17 +184,6 @@ static int freezer_can_attach(struct cgroup_subsys *ss,
 	return 0;
 }
 
-static int freezer_can_attach_task(struct cgroup *cgrp, struct task_struct *tsk)
-{
-	rcu_read_lock();
-	if (__cgroup_freezing_or_frozen(tsk)) {
-		rcu_read_unlock();
-		return -EBUSY;
-	}
-	rcu_read_unlock();
-	return 0;
-}
-
 static void freezer_fork(struct cgroup_subsys *ss, struct task_struct *task)
 {
 	struct freezer *freezer;
@@ -381,10 +379,5 @@ struct cgroup_subsys freezer_subsys = {
 	.populate	= freezer_populate,
 	.subsys_id	= freezer_subsys_id,
 	.can_attach	= freezer_can_attach,
-	.can_attach_task = freezer_can_attach_task,
-	.pre_attach	= NULL,
-	.attach_task	= NULL,
-	.attach		= NULL,
 	.fork		= freezer_fork,
-	.exit		= NULL,
 };
kernel/cpuset.c +32 -38

113 lines not on this page · raw message

kernel/events/core.c +8 -5
index d1a1bee..ed35211 100644
@@ -7136,10 +7136,13 @@ static int __perf_cgroup_move(void *info)
 	return 0;
 }
 
-static void
-perf_cgroup_attach_task(struct cgroup *cgrp, struct task_struct *task)
+static void perf_cgroup_attach(struct cgroup_subsys *ss, struct cgroup *cgrp,
+			       struct cgroup_taskset *tset)
 {
-	task_function_call(task, __perf_cgroup_move, task);
+	struct task_struct *task;
+
+	cgroup_taskset_for_each(task, cgrp, tset)
+		task_function_call(task, __perf_cgroup_move, task);
 }
 
 static void perf_cgroup_exit(struct cgroup_subsys *ss, struct cgroup *cgrp,
@@ -7153,7 +7156,7 @@ static void perf_cgroup_exit(struct cgroup_subsys *ss, struct cgroup *cgrp,
 	if (!(task->flags & PF_EXITING))
 		return;
 
-	perf_cgroup_attach_task(cgrp, task);
+	task_function_call(task, __perf_cgroup_move, task);
 }
 
 struct cgroup_subsys perf_subsys = {
@@ -7162,6 +7165,6 @@ struct cgroup_subsys perf_subsys = {
 	.create		= perf_cgroup_create,
 	.destroy	= perf_cgroup_destroy,
 	.exit		= perf_cgroup_exit,
-	.attach_task	= perf_cgroup_attach_task,
+	.attach		= perf_cgroup_attach,
 };
 #endif /* CONFIG_CGROUP_PERF */
kernel/sched.c +19 -12
index d87c6e5..49e3605 100644
@@ -9129,24 +9129,31 @@ cpu_cgroup_destroy(struct cgroup_subsys *ss, struct cgroup *cgrp)
 	sched_destroy_group(tg);
 }
 
-static int
-cpu_cgroup_can_attach_task(struct cgroup *cgrp, struct task_struct *tsk)
+static int cpu_cgroup_can_attach(struct cgroup_subsys *ss, struct cgroup *cgrp,
+				 struct cgroup_taskset *tset)
 {
+	struct task_struct *task;
+
+	cgroup_taskset_for_each(task, cgrp, tset) {
 #ifdef CONFIG_RT_GROUP_SCHED
-	if (!sched_rt_can_attach(cgroup_tg(cgrp), tsk))
-		return -EINVAL;
+		if (!sched_rt_can_attach(cgroup_tg(cgrp), task))
+			return -EINVAL;
 #else
-	/* We don't support RT-tasks being in separate groups */
-	if (tsk->sched_class != &fair_sched_class)
-		return -EINVAL;
+		/* We don't support RT-tasks being in separate groups */
+		if (task->sched_class != &fair_sched_class)
+			return -EINVAL;
 #endif
+	}
 	return 0;
 }
 
-static void
-cpu_cgroup_attach_task(struct cgroup *cgrp, struct task_struct *tsk)
+static void cpu_cgroup_attach(struct cgroup_subsys *ss, struct cgroup *cgrp,
+			      struct cgroup_taskset *tset)
 {
-	sched_move_task(tsk);
+	struct task_struct *task;
+
+	cgroup_taskset_for_each(task, cgrp, tset)
+		sched_move_task(task);
 }
 
 static void
@@ -9482,8 +9489,8 @@ struct cgroup_subsys cpu_cgroup_subsys = {
 	.name		= "cpu",
 	.create		= cpu_cgroup_create,
 	.destroy	= cpu_cgroup_destroy,
-	.can_attach_task = cpu_cgroup_can_attach_task,
-	.attach_task	= cpu_cgroup_attach_task,
+	.can_attach	= cpu_cgroup_can_attach,
+	.attach		= cpu_cgroup_attach,
 	.exit		= cpu_cgroup_exit,
 	.populate	= cpu_cgroup_populate,
 	.subsys_id	= cpu_cgroup_subsys_id,
Signature · 1 line
1.7.3.1
author maintainer
#13 lore raw

PATCH 2/10 threadgroup: rename signal->threadgroup_fork_lock to ->group_rwsem

In reply to Tejun Heo

Make the following renames to prepare for extension of threadgroup locking.

* s/signal->threadgroup_fork_lock/signal->group_rwsem/
* s/threadgroup_fork_read_lock()/threadgroup_change_begin()/
* s/threadgroup_fork_read_unlock()/threadgroup_change_done()/
* s/threadgroup_fork_write_lock()/threadgroup_lock()/
* s/threadgroup_fork_write_unlock()/threadgroup_unlock()/

This patch doesn't cause any behavior change.

Cc Oleg Nesterov <[email protected]>
Cc Andrew Morton <akpm-de/[email protected]>
Cc Paul Menage <[email protected]>
Cc Li Zefan <[email protected]>
---
 include/linux/init_task.h |    9 ++++-----
 include/linux/sched.h     |   30 +++++++++++++++---------------
 kernel/cgroup.c           |   13 ++++++-------
 kernel/fork.c             |    8 ++++----
 4 files changed, 29 insertions(+), 31 deletions(-)
include/linux/init_task.h +4 -5
index 08ffab0..ef20cbe 100644
@@ -23,11 +23,10 @@ extern struct files_struct init_files;
 extern struct fs_struct init_fs;
 
 #ifdef CONFIG_CGROUPS
-#define INIT_THREADGROUP_FORK_LOCK(sig)					\
-	.threadgroup_fork_lock =					\
-		__RWSEM_INITIALIZER(sig.threadgroup_fork_lock),
+#define INIT_GROUP_RWSEM(sig)						\
+	.group_rwsem = __RWSEM_INITIALIZER(sig.group_rwsem),
 #else
-#define INIT_THREADGROUP_FORK_LOCK(sig)
+#define INIT_GROUP_RWSEM(sig)
 #endif
 
 #define INIT_SIGNALS(sig) {						\
@@ -46,7 +45,7 @@ extern struct fs_struct init_fs;
 	},								\
 	.cred_guard_mutex =						\
 		 __MUTEX_INITIALIZER(sig.cred_guard_mutex),		\
-	INIT_THREADGROUP_FORK_LOCK(sig)					\
+	INIT_GROUP_RWSEM(sig)						\
 }
 
 extern struct nsproxy init_nsproxy;
include/linux/sched.h +15 -15
index e8acce7..aa47d0f 100644
@@ -635,13 +635,13 @@ struct signal_struct {
 #endif
 #ifdef CONFIG_CGROUPS
 	/*
-	 * The threadgroup_fork_lock prevents threads from forking with
+	 * The group_rwsem prevents threads from forking with
 	 * CLONE_THREAD while held for writing. Use this for fork-sensitive
 	 * threadgroup-wide operations. It's taken for reading in fork.c in
 	 * copy_process().
 	 * Currently only needed write-side by cgroups.
 	 */
-	struct rw_semaphore threadgroup_fork_lock;
+	struct rw_semaphore group_rwsem;
 #endif
 
 	int oom_adj;		/* OOM kill score adjustment (bit shift) */
@@ -2367,29 +2367,29 @@ static inline void unlock_task_sighand(struct task_struct *tsk,
 	spin_unlock_irqrestore(&tsk->sighand->siglock, *flags);
 }
 
-/* See the declaration of threadgroup_fork_lock in signal_struct. */
+/* See the declaration of group_rwsem in signal_struct. */
 #ifdef CONFIG_CGROUPS
-static inline void threadgroup_fork_read_lock(struct task_struct *tsk)
+static inline void threadgroup_change_begin(struct task_struct *tsk)
 {
-	down_read(&tsk->signal->threadgroup_fork_lock);
+	down_read(&tsk->signal->group_rwsem);
 }
-static inline void threadgroup_fork_read_unlock(struct task_struct *tsk)
+static inline void threadgroup_change_done(struct task_struct *tsk)
 {
-	up_read(&tsk->signal->threadgroup_fork_lock);
+	up_read(&tsk->signal->group_rwsem);
 }
-static inline void threadgroup_fork_write_lock(struct task_struct *tsk)
+static inline void threadgroup_lock(struct task_struct *tsk)
 {
-	down_write(&tsk->signal->threadgroup_fork_lock);
+	down_write(&tsk->signal->group_rwsem);
 }
-static inline void threadgroup_fork_write_unlock(struct task_struct *tsk)
+static inline void threadgroup_unlock(struct task_struct *tsk)
 {
-	up_write(&tsk->signal->threadgroup_fork_lock);
+	up_write(&tsk->signal->group_rwsem);
 }
 #else
-static inline void threadgroup_fork_read_lock(struct task_struct *tsk) {}
-static inline void threadgroup_fork_read_unlock(struct task_struct *tsk) {}
-static inline void threadgroup_fork_write_lock(struct task_struct *tsk) {}
-static inline void threadgroup_fork_write_unlock(struct task_struct *tsk) {}
+static inline void threadgroup_change_begin(struct task_struct *tsk) {}
+static inline void threadgroup_change_done(struct task_struct *tsk) {}
+static inline void threadgroup_lock(struct task_struct *tsk) {}
+static inline void threadgroup_unlock(struct task_struct *tsk) {}
 #endif
 
 #ifndef __HAVE_THREAD_FUNCTIONS
kernel/cgroup.c +6 -7
index efa5886..f0e099f 100644
@@ -2003,8 +2003,8 @@ static int css_set_prefetch(struct cgroup *cgrp, struct css_set *cg,
  * @cgrp: the cgroup to attach to
  * @leader: the threadgroup leader task_struct of the group to be attached
  *
- * Call holding cgroup_mutex and the threadgroup_fork_lock of the leader. Will
- * take task_lock of each thread in leader's threadgroup individually in turn.
+ * Call holding cgroup_mutex and the group_rwsem of the leader. Will take
+ * task_lock of each thread in leader's threadgroup individually in turn.
  */
 int cgroup_attach_proc(struct cgroup *cgrp, struct task_struct *leader)
 {
@@ -2030,8 +2030,8 @@ int cgroup_attach_proc(struct cgroup *cgrp, struct task_struct *leader)
 	 * step 0: in order to do expensive, possibly blocking operations for
 	 * every thread, we cannot iterate the thread group list, since it needs
 	 * rcu or tasklist locked. instead, build an array of all threads in the
-	 * group - threadgroup_fork_lock prevents new threads from appearing,
-	 * and if threads exit, this will just be an over-estimate.
+	 * group - group_rwsem prevents new threads from appearing, and if
+	 * threads exit, this will just be an over-estimate.
 	 */
 	group_size = get_nr_threads(leader);
 	/* flex_array supports very large thread-groups better than kmalloc. */
@@ -2246,7 +2246,6 @@ static int attach_task_by_pid(struct cgroup *cgrp, u64 pid, bool threadgroup)
 			cgroup_unlock();
 			return -ESRCH;
 		}
-
 		/*
 		 * even if we're attaching all tasks in the thread group, we
 		 * only need to check permissions on one of them.
@@ -2270,9 +2269,9 @@ static int attach_task_by_pid(struct cgroup *cgrp, u64 pid, bool threadgroup)
 	}
 
 	if (threadgroup) {
-		threadgroup_fork_write_lock(tsk);
+		threadgroup_lock(tsk);
 		ret = cgroup_attach_proc(cgrp, tsk);
-		threadgroup_fork_write_unlock(tsk);
+		threadgroup_unlock(tsk);
 	} else {
 		ret = cgroup_attach_task(cgrp, tsk);
 	}
kernel/fork.c +4 -4
index 8e6b6f4..c2af839 100644
@@ -980,7 +980,7 @@ static int copy_signal(unsigned long clone_flags, struct task_struct *tsk)
 	sched_autogroup_fork(sig);
 
 #ifdef CONFIG_CGROUPS
-	init_rwsem(&sig->threadgroup_fork_lock);
+	init_rwsem(&sig->group_rwsem);
 #endif
 
 	sig->oom_adj = current->signal->oom_adj;
@@ -1166,7 +1166,7 @@ static struct task_struct *copy_process(unsigned long clone_flags,
 	p->io_context = NULL;
 	p->audit_context = NULL;
 	if (clone_flags & CLONE_THREAD)
-		threadgroup_fork_read_lock(current);
+		threadgroup_change_begin(current);
 	cgroup_fork(p);
 #ifdef CONFIG_NUMA
 	p->mempolicy = mpol_dup(p->mempolicy);
@@ -1378,7 +1378,7 @@ static struct task_struct *copy_process(unsigned long clone_flags,
 	proc_fork_connector(p);
 	cgroup_post_fork(p);
 	if (clone_flags & CLONE_THREAD)
-		threadgroup_fork_read_unlock(current);
+		threadgroup_change_done(current);
 	perf_event_fork(p);
 	return p;
 
@@ -1418,7 +1418,7 @@ bad_fork_cleanup_policy:
 bad_fork_cleanup_cgroup:
 #endif
 	if (clone_flags & CLONE_THREAD)
-		threadgroup_fork_read_unlock(current);
+		threadgroup_change_done(current);
 	cgroup_exit(p, cgroup_callbacks_done);
 	delayacct_tsk_free(p);
 	module_put(task_thread_info(p)->exec_domain->module);
Signature · 1 line
1.7.3.1

A message that is not in the archive (sent off-list or not yet received): 1320191193-8110-3-git-send-email-tj-DgEjT+Ai2ygdnm+yROfE0A@public.gmane.org

Reviewed-by
#15 lore raw

Re: [PATCH 02/10] threadgroup: rename signal->threadgroup_fork_lock to ->group_rwsem

Quoted 6 lines · On Tue, 1 Nov 2011 16:46:25 -0700 Tejun Heo…

Make the following renames to prepare for extension of threadgroup locking.

* s/signal->threadgroup_fork_lock/signal->group_rwsem/
* s/threadgroup_fork_read_lock()/threadgroup_change_begin()/
* s/threadgroup_fork_read_unlock()/threadgroup_change_done()/

I personally like begin <-> end to see relationship.

Quoted 10 lines

* s/threadgroup_fork_write_lock()/threadgroup_lock()/
* s/threadgroup_fork_write_unlock()/threadgroup_unlock()/

This patch doesn't cause any behavior change.

Cc Oleg Nesterov <[email protected]>
Cc Andrew Morton <akpm-de/[email protected]>
Cc Paul Menage <[email protected]>
Cc Li Zefan <[email protected]>
Reviewed-by on v1 2/10
#16 lore raw

Re: [PATCH 02/10] threadgroup: rename signal->threadgroup_fork_lock to ->group_rwsem

In reply to Tejun Heo
Quoted 6 lines · On Tue, 1 Nov 2011 16:46:25 -0700 Tejun Heo <[email protected]> wrote:

Make the following renames to prepare for extension of threadgroup locking.

* s/signal->threadgroup_fork_lock/signal->group_rwsem/
* s/threadgroup_fork_read_lock()/threadgroup_change_begin()/
* s/threadgroup_fork_read_unlock()/threadgroup_change_done()/

I personally like begin <-> end to see relationship.

Quoted 10 lines

* s/threadgroup_fork_write_lock()/threadgroup_lock()/
* s/threadgroup_fork_write_unlock()/threadgroup_unlock()/

This patch doesn't cause any behavior change.

Cc Oleg Nesterov <[email protected]>
Cc Andrew Morton <[email protected]>
Cc Paul Menage <[email protected]>
Cc Li Zefan <[email protected]>
Reviewed-by on v1 2/10
#17 lore raw

Re: [PATCH 02/10] threadgroup: rename signal->threadgroup_fork_lock to ->group_rwsem

In reply to Tejun Heo
Quoted 6 lines · On Tue, 1 Nov 2011 16:46:25 -0700 Tejun Heo <[email protected]> wrote:

Make the following renames to prepare for extension of threadgroup locking.

* s/signal->threadgroup_fork_lock/signal->group_rwsem/
* s/threadgroup_fork_read_lock()/threadgroup_change_begin()/
* s/threadgroup_fork_read_unlock()/threadgroup_change_done()/

I personally like begin <-> end to see relationship.

Quoted 10 lines

* s/threadgroup_fork_write_lock()/threadgroup_lock()/
* s/threadgroup_fork_write_unlock()/threadgroup_unlock()/

This patch doesn't cause any behavior change.

Cc Oleg Nesterov <[email protected]>
Cc Andrew Morton <[email protected]>
Cc Paul Menage <[email protected]>
Cc Li Zefan <[email protected]>
author maintainer
#18 lore raw

Re: [PATCH 02/10] threadgroup: rename signal->threadgroup_fork_lock to ->group_rwsem

Quoted 11 lines · KAMEZAWA Hiroyuki
Quoted 6 lines · On Tue, 1 Nov 2011 16:46:25 -0700 Tejun Heo <[email protected]> wrote:

Make the following renames to prepare for extension of threadgroup locking.

* s/signal->threadgroup_fork_lock/signal->group_rwsem/
* s/threadgroup_fork_read_lock()/threadgroup_change_begin()/
* s/threadgroup_fork_read_unlock()/threadgroup_change_done()/

I personally like begin <-> end to see relationship.

Hmmm... yeah, I like that better too. Renaming.

Thanks.

Signature · 1 line
tejun

A message that is not in the archive (sent off-list or not yet received): 20111104174032.e0c4fc11.kamezawa.hiroyu-+CUm20s59erQFUHtdCDX3A@public.gmane.org

#20 lore raw

Re: [PATCH 02/10] threadgroup: rename signal->threadgroup_fork_lock to ->group_rwsem

Quoted 11 lines · KAMEZAWA Hiroyuki
Quoted 6 lines · On Tue, 1 Nov 2011 16:46:25 -0700 Tejun Heo…

Make the following renames to prepare for extension of threadgroup locking.

* s/signal->threadgroup_fork_lock/signal->group_rwsem/
* s/threadgroup_fork_read_lock()/threadgroup_change_begin()/
* s/threadgroup_fork_read_unlock()/threadgroup_change_done()/

I personally like begin <-> end to see relationship.

Hmmm... yeah, I like that better too. Renaming.

Thanks.

Signature · 1 line
tejun
author maintainer
#21 lore raw

PATCH 2/10 threadgroup: rename signal->threadgroup_fork_lock to ->group_rwsem

In reply to Tejun Heo

Make the following renames to prepare for extension of threadgroup locking.

* s/signal->threadgroup_fork_lock/signal->group_rwsem/
* s/threadgroup_fork_read_lock()/threadgroup_change_begin()/
* s/threadgroup_fork_read_unlock()/threadgroup_change_done()/
* s/threadgroup_fork_write_lock()/threadgroup_lock()/
* s/threadgroup_fork_write_unlock()/threadgroup_unlock()/

This patch doesn't cause any behavior change.

Cc Oleg Nesterov <[email protected]>
Cc Andrew Morton <[email protected]>
Cc Paul Menage <[email protected]>
Cc Li Zefan <[email protected]>
---
 include/linux/init_task.h |    9 ++++-----
 include/linux/sched.h     |   30 +++++++++++++++---------------
 kernel/cgroup.c           |   13 ++++++-------
 kernel/fork.c             |    8 ++++----
 4 files changed, 29 insertions(+), 31 deletions(-)
include/linux/init_task.h +4 -5
index 08ffab0..ef20cbe 100644
@@ -23,11 +23,10 @@ extern struct files_struct init_files;
 extern struct fs_struct init_fs;
 
 #ifdef CONFIG_CGROUPS
-#define INIT_THREADGROUP_FORK_LOCK(sig)					\
-	.threadgroup_fork_lock =					\
-		__RWSEM_INITIALIZER(sig.threadgroup_fork_lock),
+#define INIT_GROUP_RWSEM(sig)						\
+	.group_rwsem = __RWSEM_INITIALIZER(sig.group_rwsem),
 #else
-#define INIT_THREADGROUP_FORK_LOCK(sig)
+#define INIT_GROUP_RWSEM(sig)
 #endif
 
 #define INIT_SIGNALS(sig) {						\
@@ -46,7 +45,7 @@ extern struct fs_struct init_fs;
 	},								\
 	.cred_guard_mutex =						\
 		 __MUTEX_INITIALIZER(sig.cred_guard_mutex),		\
-	INIT_THREADGROUP_FORK_LOCK(sig)					\
+	INIT_GROUP_RWSEM(sig)						\
 }
 
 extern struct nsproxy init_nsproxy;
include/linux/sched.h +15 -15
index e8acce7..aa47d0f 100644
@@ -635,13 +635,13 @@ struct signal_struct {
 #endif
 #ifdef CONFIG_CGROUPS
 	/*
-	 * The threadgroup_fork_lock prevents threads from forking with
+	 * The group_rwsem prevents threads from forking with
 	 * CLONE_THREAD while held for writing. Use this for fork-sensitive
 	 * threadgroup-wide operations. It's taken for reading in fork.c in
 	 * copy_process().
 	 * Currently only needed write-side by cgroups.
 	 */
-	struct rw_semaphore threadgroup_fork_lock;
+	struct rw_semaphore group_rwsem;
 #endif
 
 	int oom_adj;		/* OOM kill score adjustment (bit shift) */
@@ -2367,29 +2367,29 @@ static inline void unlock_task_sighand(struct task_struct *tsk,
 	spin_unlock_irqrestore(&tsk->sighand->siglock, *flags);
 }
 
-/* See the declaration of threadgroup_fork_lock in signal_struct. */
+/* See the declaration of group_rwsem in signal_struct. */
 #ifdef CONFIG_CGROUPS
-static inline void threadgroup_fork_read_lock(struct task_struct *tsk)
+static inline void threadgroup_change_begin(struct task_struct *tsk)
 {
-	down_read(&tsk->signal->threadgroup_fork_lock);
+	down_read(&tsk->signal->group_rwsem);
 }
-static inline void threadgroup_fork_read_unlock(struct task_struct *tsk)
+static inline void threadgroup_change_done(struct task_struct *tsk)
 {
-	up_read(&tsk->signal->threadgroup_fork_lock);
+	up_read(&tsk->signal->group_rwsem);
 }
-static inline void threadgroup_fork_write_lock(struct task_struct *tsk)
+static inline void threadgroup_lock(struct task_struct *tsk)
 {
-	down_write(&tsk->signal->threadgroup_fork_lock);
+	down_write(&tsk->signal->group_rwsem);
 }
-static inline void threadgroup_fork_write_unlock(struct task_struct *tsk)
+static inline void threadgroup_unlock(struct task_struct *tsk)
 {
-	up_write(&tsk->signal->threadgroup_fork_lock);
+	up_write(&tsk->signal->group_rwsem);
 }
 #else
-static inline void threadgroup_fork_read_lock(struct task_struct *tsk) {}
-static inline void threadgroup_fork_read_unlock(struct task_struct *tsk) {}
-static inline void threadgroup_fork_write_lock(struct task_struct *tsk) {}
-static inline void threadgroup_fork_write_unlock(struct task_struct *tsk) {}
+static inline void threadgroup_change_begin(struct task_struct *tsk) {}
+static inline void threadgroup_change_done(struct task_struct *tsk) {}
+static inline void threadgroup_lock(struct task_struct *tsk) {}
+static inline void threadgroup_unlock(struct task_struct *tsk) {}
 #endif
 
 #ifndef __HAVE_THREAD_FUNCTIONS
kernel/cgroup.c +6 -7
index efa5886..f0e099f 100644
@@ -2003,8 +2003,8 @@ static int css_set_prefetch(struct cgroup *cgrp, struct css_set *cg,
  * @cgrp: the cgroup to attach to
  * @leader: the threadgroup leader task_struct of the group to be attached
  *
- * Call holding cgroup_mutex and the threadgroup_fork_lock of the leader. Will
- * take task_lock of each thread in leader's threadgroup individually in turn.
+ * Call holding cgroup_mutex and the group_rwsem of the leader. Will take
+ * task_lock of each thread in leader's threadgroup individually in turn.
  */
 int cgroup_attach_proc(struct cgroup *cgrp, struct task_struct *leader)
 {
@@ -2030,8 +2030,8 @@ int cgroup_attach_proc(struct cgroup *cgrp, struct task_struct *leader)
 	 * step 0: in order to do expensive, possibly blocking operations for
 	 * every thread, we cannot iterate the thread group list, since it needs
 	 * rcu or tasklist locked. instead, build an array of all threads in the
-	 * group - threadgroup_fork_lock prevents new threads from appearing,
-	 * and if threads exit, this will just be an over-estimate.
+	 * group - group_rwsem prevents new threads from appearing, and if
+	 * threads exit, this will just be an over-estimate.
 	 */
 	group_size = get_nr_threads(leader);
 	/* flex_array supports very large thread-groups better than kmalloc. */
@@ -2246,7 +2246,6 @@ static int attach_task_by_pid(struct cgroup *cgrp, u64 pid, bool threadgroup)
 			cgroup_unlock();
 			return -ESRCH;
 		}
-
 		/*
 		 * even if we're attaching all tasks in the thread group, we
 		 * only need to check permissions on one of them.
@@ -2270,9 +2269,9 @@ static int attach_task_by_pid(struct cgroup *cgrp, u64 pid, bool threadgroup)
 	}
 
 	if (threadgroup) {
-		threadgroup_fork_write_lock(tsk);
+		threadgroup_lock(tsk);
 		ret = cgroup_attach_proc(cgrp, tsk);
-		threadgroup_fork_write_unlock(tsk);
+		threadgroup_unlock(tsk);
 	} else {
 		ret = cgroup_attach_task(cgrp, tsk);
 	}
kernel/fork.c +4 -4
index 8e6b6f4..c2af839 100644
@@ -980,7 +980,7 @@ static int copy_signal(unsigned long clone_flags, struct task_struct *tsk)
 	sched_autogroup_fork(sig);
 
 #ifdef CONFIG_CGROUPS
-	init_rwsem(&sig->threadgroup_fork_lock);
+	init_rwsem(&sig->group_rwsem);
 #endif
 
 	sig->oom_adj = current->signal->oom_adj;
@@ -1166,7 +1166,7 @@ static struct task_struct *copy_process(unsigned long clone_flags,
 	p->io_context = NULL;
 	p->audit_context = NULL;
 	if (clone_flags & CLONE_THREAD)
-		threadgroup_fork_read_lock(current);
+		threadgroup_change_begin(current);
 	cgroup_fork(p);
 #ifdef CONFIG_NUMA
 	p->mempolicy = mpol_dup(p->mempolicy);
@@ -1378,7 +1378,7 @@ static struct task_struct *copy_process(unsigned long clone_flags,
 	proc_fork_connector(p);
 	cgroup_post_fork(p);
 	if (clone_flags & CLONE_THREAD)
-		threadgroup_fork_read_unlock(current);
+		threadgroup_change_done(current);
 	perf_event_fork(p);
 	return p;
 
@@ -1418,7 +1418,7 @@ bad_fork_cleanup_policy:
 bad_fork_cleanup_cgroup:
 #endif
 	if (clone_flags & CLONE_THREAD)
-		threadgroup_fork_read_unlock(current);
+		threadgroup_change_done(current);
 	cgroup_exit(p, cgroup_callbacks_done);
 	delayacct_tsk_free(p);
 	module_put(task_thread_info(p)->exec_domain->module);
Signature · 1 line
1.7.3.1
author maintainer
#22 lore raw

PATCH 3/10 threadgroup: extend threadgroup_lock() to cover exit and exec

In reply to Tejun Heo

threadgroup_lock() protected only protected against new addition to the threadgroup, which was inherently somewhat incomplete and problematic for its only user cgroup. On-going migration could race against exec and exit leading to interesting problems - the symmetry between various attach methods, task exiting during method execution, ->exit() racing against attach methods, migrating task switching basic properties during exec and so on.

This patch extends threadgroup_lock() such that it protects against all three threadgroup altering operations - fork, exit and exec. For exit, threadgroup_change_begin/end() calls are added to exit path. For exec, threadgroup_[un]lock() are updated to also grab and release cred_guard_mutex.

With this change, threadgroup_lock() guarantees that the target threadgroup will remain stable - no new task will be added, no new PF_EXITING will be set and exec won't happen.

The next patch will update cgroup so that it can take full advantage of this change.

Cc Oleg Nesterov <[email protected]>
Cc Andrew Morton <[email protected]>
Cc Paul Menage <[email protected]>
Cc Li Zefan <[email protected]>
---
 include/linux/sched.h |   36 ++++++++++++++++++++++++++++++------
 kernel/exit.c         |   17 +++++++++++++----
 2 files changed, 43 insertions(+), 10 deletions(-)
include/linux/sched.h +30 -6
index aa47d0f..6fb546e 100644
@@ -635,11 +635,12 @@ struct signal_struct {
 #endif
 #ifdef CONFIG_CGROUPS
 	/*
-	 * The group_rwsem prevents threads from forking with
-	 * CLONE_THREAD while held for writing. Use this for fork-sensitive
-	 * threadgroup-wide operations. It's taken for reading in fork.c in
-	 * copy_process().
-	 * Currently only needed write-side by cgroups.
+	 * group_rwsem prevents new tasks from entering the threadgroup and
+	 * member tasks from exiting.  fork and exit paths are protected
+	 * with this rwsem using threadgroup_change_begin/end().  Users
+	 * which require threadgroup to remain stable should use
+	 * threadgroup_[un]lock() which also takes care of exec path.
+	 * Currently, cgroup is the only user.
 	 */
 	struct rw_semaphore group_rwsem;
 #endif
@@ -2367,7 +2368,6 @@ static inline void unlock_task_sighand(struct task_struct *tsk,
 	spin_unlock_irqrestore(&tsk->sighand->siglock, *flags);
 }
 
-/* See the declaration of group_rwsem in signal_struct. */
 #ifdef CONFIG_CGROUPS
 static inline void threadgroup_change_begin(struct task_struct *tsk)
 {
@@ -2377,13 +2377,37 @@ static inline void threadgroup_change_done(struct task_struct *tsk)
 {
 	up_read(&tsk->signal->group_rwsem);
 }
+
+/**
+ * threadgroup_lock - lock threadgroup
+ * @tsk: member task of the threadgroup to lock
+ *
+ * Lock the threadgroup @tsk belongs to.  No new task is allowed to enter
+ * and member tasks aren't allowed to exit (as indicated by PF_EXITING) or
+ * perform exec.  This is useful for cases where the threadgroup needs to
+ * stay stable across blockable operations.
+ *
+ * fork and exit explicitly call threadgroup_change_{begin|end}() for
+ * synchronization.  exec is already synchronized with cread_guard_mutex
+ * which we grab here.
+ */
 static inline void threadgroup_lock(struct task_struct *tsk)
 {
+	/* exec uses exit for de-threading, grab cred_guard_mutex first */
+	mutex_lock(&tsk->signal->cred_guard_mutex);
 	down_write(&tsk->signal->group_rwsem);
 }
+
+/**
+ * threadgroup_unlock - unlock threadgroup
+ * @tsk: member task of the threadgroup to unlock
+ *
+ * Reverse threadgroup_lock().
+ */
 static inline void threadgroup_unlock(struct task_struct *tsk)
 {
 	up_write(&tsk->signal->group_rwsem);
+	mutex_unlock(&tsk->signal->cred_guard_mutex);
 }
 #else
 static inline void threadgroup_change_begin(struct task_struct *tsk) {}
kernel/exit.c +13 -4
index 2913b35..3565f00 100644
@@ -938,6 +938,12 @@ NORET_TYPE void do_exit(long code)
 		schedule();
 	}
 
+	/*
+	 * @tsk's threadgroup is going through changes - lock out users
+	 * which expect stable threadgroup.
+	 */
+	threadgroup_change_begin(tsk);
+
 	exit_irq_thread();
 
 	exit_signals(tsk);  /* sets PF_EXITING */
@@ -1020,10 +1026,6 @@ NORET_TYPE void do_exit(long code)
 		kfree(current->pi_state_cache);
 #endif
 	/*
-	 * Make sure we are holding no locks:
-	 */
-	debug_check_no_locks_held(tsk);
-	/*
 	 * We can do this unlocked here. The futex code uses this flag
 	 * just to verify whether the pi state cleanup has been done
 	 * or not. In the worst case it loops once more.
@@ -1040,6 +1042,13 @@ NORET_TYPE void do_exit(long code)
 
 	preempt_disable();
 	exit_rcu();
+
+	/*
+	 * Release threadgroup and make sure we are holding no locks.
+	 */
+	threadgroup_change_done(tsk);
+	debug_check_no_locks_held(tsk);
+
 	/* causes final put_task_struct in finish_task_switch(). */
 	tsk->state = TASK_DEAD;
 	schedule();
Signature · 1 line
1.7.3.1
author maintainer
#23 lore raw

PATCH 3/10 threadgroup: extend threadgroup_lock() to cover exit and exec

In reply to Tejun Heo

threadgroup_lock() protected only protected against new addition to the threadgroup, which was inherently somewhat incomplete and problematic for its only user cgroup. On-going migration could race against exec and exit leading to interesting problems - the symmetry between various attach methods, task exiting during method execution, ->exit() racing against attach methods, migrating task switching basic properties during exec and so on.

This patch extends threadgroup_lock() such that it protects against all three threadgroup altering operations - fork, exit and exec. For exit, threadgroup_change_begin/end() calls are added to exit path. For exec, threadgroup_[un]lock() are updated to also grab and release cred_guard_mutex.

With this change, threadgroup_lock() guarantees that the target threadgroup will remain stable - no new task will be added, no new PF_EXITING will be set and exec won't happen.

The next patch will update cgroup so that it can take full advantage of this change.

Cc Oleg Nesterov <[email protected]>
Cc Andrew Morton <[email protected]>
Cc Paul Menage <[email protected]>
Cc Li Zefan <[email protected]>
---
 include/linux/sched.h |   36 ++++++++++++++++++++++++++++++------
 kernel/exit.c         |   17 +++++++++++++----
 2 files changed, 43 insertions(+), 10 deletions(-)
include/linux/sched.h +30 -6
index aa47d0f..6fb546e 100644
@@ -635,11 +635,12 @@ struct signal_struct {
 #endif
 #ifdef CONFIG_CGROUPS
 	/*
-	 * The group_rwsem prevents threads from forking with
-	 * CLONE_THREAD while held for writing. Use this for fork-sensitive
-	 * threadgroup-wide operations. It's taken for reading in fork.c in
-	 * copy_process().
-	 * Currently only needed write-side by cgroups.
+	 * group_rwsem prevents new tasks from entering the threadgroup and
+	 * member tasks from exiting.  fork and exit paths are protected
+	 * with this rwsem using threadgroup_change_begin/end().  Users
+	 * which require threadgroup to remain stable should use
+	 * threadgroup_[un]lock() which also takes care of exec path.
+	 * Currently, cgroup is the only user.
 	 */
 	struct rw_semaphore group_rwsem;
 #endif
@@ -2367,7 +2368,6 @@ static inline void unlock_task_sighand(struct task_struct *tsk,
 	spin_unlock_irqrestore(&tsk->sighand->siglock, *flags);
 }
 
-/* See the declaration of group_rwsem in signal_struct. */
 #ifdef CONFIG_CGROUPS
 static inline void threadgroup_change_begin(struct task_struct *tsk)
 {
@@ -2377,13 +2377,37 @@ static inline void threadgroup_change_done(struct task_struct *tsk)
 {
 	up_read(&tsk->signal->group_rwsem);
 }
+
+/**
+ * threadgroup_lock - lock threadgroup
+ * @tsk: member task of the threadgroup to lock
+ *
+ * Lock the threadgroup @tsk belongs to.  No new task is allowed to enter
+ * and member tasks aren't allowed to exit (as indicated by PF_EXITING) or
+ * perform exec.  This is useful for cases where the threadgroup needs to
+ * stay stable across blockable operations.
+ *
+ * fork and exit explicitly call threadgroup_change_{begin|end}() for
+ * synchronization.  exec is already synchronized with cread_guard_mutex
+ * which we grab here.
+ */
 static inline void threadgroup_lock(struct task_struct *tsk)
 {
+	/* exec uses exit for de-threading, grab cred_guard_mutex first */
+	mutex_lock(&tsk->signal->cred_guard_mutex);
 	down_write(&tsk->signal->group_rwsem);
 }
+
+/**
+ * threadgroup_unlock - unlock threadgroup
+ * @tsk: member task of the threadgroup to unlock
+ *
+ * Reverse threadgroup_lock().
+ */
 static inline void threadgroup_unlock(struct task_struct *tsk)
 {
 	up_write(&tsk->signal->group_rwsem);
+	mutex_unlock(&tsk->signal->cred_guard_mutex);
 }
 #else
 static inline void threadgroup_change_begin(struct task_struct *tsk) {}
kernel/exit.c +13 -4
index 2913b35..3565f00 100644
@@ -938,6 +938,12 @@ NORET_TYPE void do_exit(long code)
 		schedule();
 	}
 
+	/*
+	 * @tsk's threadgroup is going through changes - lock out users
+	 * which expect stable threadgroup.
+	 */
+	threadgroup_change_begin(tsk);
+
 	exit_irq_thread();
 
 	exit_signals(tsk);  /* sets PF_EXITING */
@@ -1020,10 +1026,6 @@ NORET_TYPE void do_exit(long code)
 		kfree(current->pi_state_cache);
 #endif
 	/*
-	 * Make sure we are holding no locks:
-	 */
-	debug_check_no_locks_held(tsk);
-	/*
 	 * We can do this unlocked here. The futex code uses this flag
 	 * just to verify whether the pi state cleanup has been done
 	 * or not. In the worst case it loops once more.
@@ -1040,6 +1042,13 @@ NORET_TYPE void do_exit(long code)
 
 	preempt_disable();
 	exit_rcu();
+
+	/*
+	 * Release threadgroup and make sure we are holding no locks.
+	 */
+	threadgroup_change_done(tsk);
+	debug_check_no_locks_held(tsk);
+
 	/* causes final put_task_struct in finish_task_switch(). */
 	tsk->state = TASK_DEAD;
 	schedule();
Signature · 1 line
1.7.3.1

A message that is not in the archive (sent off-list or not yet received): 1320191193-8110-4-git-send-email-tj-DgEjT+Ai2ygdnm+yROfE0A@public.gmane.org

Reviewed-by
#25 lore raw

Re: [PATCH 03/10] threadgroup: extend threadgroup_lock() to cover exit and exec

Quoted 18 lines · On Tue, 1 Nov 2011 16:46:26 -0700 Tejun Heo…

threadgroup_lock() protected only protected against new addition to the threadgroup, which was inherently somewhat incomplete and problematic for its only user cgroup. On-going migration could race against exec and exit leading to interesting problems - the symmetry between various attach methods, task exiting during method execution, ->exit() racing against attach methods, migrating task switching basic properties during exec and so on.

This patch extends threadgroup_lock() such that it protects against all three threadgroup altering operations - fork, exit and exec. For exit, threadgroup_change_begin/end() calls are added to exit path. For exec, threadgroup_[un]lock() are updated to also grab and release cred_guard_mutex.

With this change, threadgroup_lock() guarantees that the target threadgroup will remain stable - no new task will be added, no new PF_EXITING will be set and exec won't happen.

Quoted 8 lines

The next patch will update cgroup so that it can take full advantage of this change.

Cc Oleg Nesterov <[email protected]>
Cc Andrew Morton <akpm-de/[email protected]>
Cc Paul Menage <[email protected]>
Cc Li Zefan <[email protected]>
#26 lore raw

Re: [PATCH 03/10] threadgroup: extend threadgroup_lock() to cover exit and exec

In reply to a message not in the archive
Quoted 16 lines · Tejun Heo
include/linux/sched.h +8 -0
index aa47d0f..6fb546e 100644
@@ -2377,13 +2377,37 @@ static inline void threadgroup_change_done(struct task_struct *tsk)
 {
 	up_read(&tsk->signal->group_rwsem);
 }
+
+/**
+ * threadgroup_lock - lock threadgroup
+ * @tsk: member task of the threadgroup to lock
+ *
+ * Lock the threadgroup @tsk belongs to.  No new task is allowed to enter
+ * and member tasks aren't allowed to exit (as indicated by PF_EXITING) or
+ * perform exec.  This is useful for cases where the threadgroup needs to

include/linux/sched.h static inline void threadgroup_change_done(struct task_struct *tsk) · line 2397

Quoted 10 lines
include/linux/sched.h +8 -0
+ * stay stable across blockable operations.
+ *
+ * fork and exit explicitly call threadgroup_change_{begin|end}() for
+ * synchronization.  exec is already synchronized with cread_guard_mutex
+ * which we grab here.
+ */
 static inline void threadgroup_lock(struct task_struct *tsk)
 {
+	/* exec uses exit for de-threading, grab cred_guard_mutex first */
+	mutex_lock(&tsk->signal->cred_guard_mutex);

May be worth explaining what in exec is synchronized through this. ie the leader.

Quoted 21 lines
include/linux/sched.h +8 -0
 	down_write(&tsk->signal->group_rwsem);
 }
+
+/**
+ * threadgroup_unlock - unlock threadgroup
+ * @tsk: member task of the threadgroup to unlock
+ *
+ * Reverse threadgroup_lock().
+ */
 static inline void threadgroup_unlock(struct task_struct *tsk)
 {
 	up_write(&tsk->signal->group_rwsem);
+	mutex_unlock(&tsk->signal->cred_guard_mutex);
 }
 #else
 static inline void threadgroup_change_begin(struct task_struct *tsk) {}
kernel/exit.c +0 -0
index 2913b35..3565f00 100644

kernel/exit.c NORET_TYPE void do_exit(long code) · line 945

Quoted 8 lines
kernel/exit.c +5 -0
@@ -938,6 +938,12 @@ NORET_TYPE void do_exit(long code)
 		schedule();
 	}
 
+	/*
+	 * @tsk's threadgroup is going through changes - lock out users
+	 * which expect stable threadgroup.
+	 */
+	threadgroup_change_begin(tsk);

Why so early? You actually want to synchronize against cgroup_exit(). Or am I missing something?

Yeah there may be a problem with reading PF_EXITING inside the lock if it is updated outside the lock, may be we need a pair of memory barriers. But other than that, this should be enough to lock around cgroup_exit().

#27 lore raw

PATCH 3/10 threadgroup: extend threadgroup_lock() to cover exit and exec

In reply to a message not in the archive

threadgroup_lock() protected only protected against new addition to the threadgroup, which was inherently somewhat incomplete and problematic for its only user cgroup. On-going migration could race against exec and exit leading to interesting problems - the symmetry between various attach methods, task exiting during method execution, ->exit() racing against attach methods, migrating task switching basic properties during exec and so on.

This patch extends threadgroup_lock() such that it protects against all three threadgroup altering operations - fork, exit and exec. For exit, threadgroup_change_begin/end() calls are added to exit path. For exec, threadgroup_[un]lock() are updated to also grab and release cred_guard_mutex.

With this change, threadgroup_lock() guarantees that the target threadgroup will remain stable - no new task will be added, no new PF_EXITING will be set and exec won't happen.

The next patch will update cgroup so that it can take full advantage of this change.

-v2: beefed up comment as suggested by Frederic.

Cc Oleg Nesterov <[email protected]>
Cc Andrew Morton <akpm-de/[email protected]>
Cc Paul Menage <[email protected]>
Cc Frederic Weisbecker <[email protected]>
Cc Linus Torvalds <torvalds-de/[email protected]>

---

Linus, this is something being scheduled for the next merge window. It extends threadgroup locking which cgroup used to do to exclude only fork path so that it includes exit and exec paths. threadgroup locking is used by cgroup to implement process-scope cgroup migration.

Migration happens in multiple steps, at each of which the matching method of each cgroup plugin is called. When a whole process is being migrated, methods being called at those different steps expect to see consistent image of the thread group.

cgroup currently only locks out addition of new tasks into the thread group and holds extra ref to member tasks. This mandates all cgroup plugins to deal with tasks being torn down and exec morphing the threadgroup. This patch extends the scope of threadgroup locking such that the thread group is guaranteed to be stable (no new task, tasks are either live or dead and no exec morphing) while locked.

This is part of changes to clean up cgroup methods and iron out corner case fuzziness and should make difficult-to-reproduce race conditions less likely and cgroup plugins easier to implement and verify.

The synchronization is strictly per-threadgroup and goes away if cgroup is not configured.

The whole series is avilable at

http://thread.gmane.org/gmane.linux.kernel.containers/21716
git://git.kernel.org/pub/scm/linux/kernel/git/tj/cgroup.git for-3.3

Thank you.

 include/linux/sched.h |   47 +++++++++++++++++++++++++++++++++++++++++------
 kernel/exit.c         |   19 +++++++++++++++----
 2 files changed, 56 insertions(+), 10 deletions(-)
include/linux/sched.h +41 -6
index c5acbce..481c5ed 100644
@@ -635,11 +635,12 @@ struct signal_struct {
 #endif
 #ifdef CONFIG_CGROUPS
 	/*
-	 * The group_rwsem prevents threads from forking with
-	 * CLONE_THREAD while held for writing. Use this for fork-sensitive
-	 * threadgroup-wide operations. It's taken for reading in fork.c in
-	 * copy_process().
-	 * Currently only needed write-side by cgroups.
+	 * group_rwsem prevents new tasks from entering the threadgroup and
+	 * member tasks from exiting.  fork and exit paths are protected
+	 * with this rwsem using threadgroup_change_begin/end().  Users
+	 * which require threadgroup to remain stable should use
+	 * threadgroup_[un]lock() which also takes care of exec path.
+	 * Currently, cgroup is the only user.
 	 */
 	struct rw_semaphore group_rwsem;
 #endif
@@ -2374,7 +2375,6 @@ static inline void unlock_task_sighand(struct task_struct *tsk,
 	spin_unlock_irqrestore(&tsk->sighand->siglock, *flags);
 }
 
-/* See the declaration of group_rwsem in signal_struct. */
 #ifdef CONFIG_CGROUPS
 static inline void threadgroup_change_begin(struct task_struct *tsk)
 {
@@ -2384,13 +2384,48 @@ static inline void threadgroup_change_end(struct task_struct *tsk)
 {
 	up_read(&tsk->signal->group_rwsem);
 }
+
+/**
+ * threadgroup_lock - lock threadgroup
+ * @tsk: member task of the threadgroup to lock
+ *
+ * Lock the threadgroup @tsk belongs to.  No new task is allowed to enter
+ * and member tasks aren't allowed to exit (as indicated by PF_EXITING) or
+ * perform exec.  This is useful for cases where the threadgroup needs to
+ * stay stable across blockable operations.
+ *
+ * fork and exit explicitly call threadgroup_change_{begin|end}() for
+ * synchronization.  This excludes most of do_exit() to ensure that, while
+ * locked, tasks belonging to a locked group are not in the process of
+ * deconstruction - they're either alive or dead.
+ *
+ * During exec, a task goes and puts its thread group through unusual
+ * changes.  After de-threading, exclusive access is assumed to resources
+ * which are usually shared by tasks in the same group - e.g. sighand may
+ * be replaced with a new one.  Also, the exec'ing task takes over group
+ * leader role including its pid.  Exclude these changes while locked by
+ * grabbing cred_guard_mutex which is used to synchronize exec path.
+ */
 static inline void threadgroup_lock(struct task_struct *tsk)
 {
+	/*
+	 * exec uses exit for de-threading nesting group_rwsem inside
+	 * cred_guard_mutex. Grab cred_guard_mutex first.
+	 */
+	mutex_lock(&tsk->signal->cred_guard_mutex);
 	down_write(&tsk->signal->group_rwsem);
 }
+
+/**
+ * threadgroup_unlock - unlock threadgroup
+ * @tsk: member task of the threadgroup to unlock
+ *
+ * Reverse threadgroup_lock().
+ */
 static inline void threadgroup_unlock(struct task_struct *tsk)
 {
 	up_write(&tsk->signal->group_rwsem);
+	mutex_unlock(&tsk->signal->cred_guard_mutex);
 }
 #else
 static inline void threadgroup_change_begin(struct task_struct *tsk) {}
kernel/exit.c +15 -4
index d0b7d98..b2cb562 100644
@@ -936,6 +936,14 @@ NORET_TYPE void do_exit(long code)
 		schedule();
 	}
 
+	/*
+	 * @tsk's threadgroup is going through changes - lock out users
+	 * which expect stable threadgroup.  Do this before actually
+	 * starting tearing down @tsk so that locked threadgroup has either
+	 * alive or dead tasks, not something inbetween.
+	 */
+	threadgroup_change_begin(tsk);
+
 	exit_irq_thread();
 
 	exit_signals(tsk);  /* sets PF_EXITING */
@@ -1018,10 +1026,6 @@ NORET_TYPE void do_exit(long code)
 		kfree(current->pi_state_cache);
 #endif
 	/*
-	 * Make sure we are holding no locks:
-	 */
-	debug_check_no_locks_held(tsk);
-	/*
 	 * We can do this unlocked here. The futex code uses this flag
 	 * just to verify whether the pi state cleanup has been done
 	 * or not. In the worst case it loops once more.
@@ -1038,6 +1042,13 @@ NORET_TYPE void do_exit(long code)
 
 	preempt_disable();
 	exit_rcu();
+
+	/*
+	 * Release threadgroup and make sure we are holding no locks.
+	 */
+	threadgroup_change_end(tsk);
+	debug_check_no_locks_held(tsk);
+
 	/* causes final put_task_struct in finish_task_switch(). */
 	tsk->state = TASK_DEAD;
 	schedule();
Signature · 1 line
1.7.3.1
Reviewed-by on v1 3/10
#28 lore raw

Re: [PATCH 03/10] threadgroup: extend threadgroup_lock() to cover exit and exec

In reply to Tejun Heo
Quoted 18 lines · On Tue, 1 Nov 2011 16:46:26 -0700 Tejun Heo <[email protected]> wrote:

threadgroup_lock() protected only protected against new addition to the threadgroup, which was inherently somewhat incomplete and problematic for its only user cgroup. On-going migration could race against exec and exit leading to interesting problems - the symmetry between various attach methods, task exiting during method execution, ->exit() racing against attach methods, migrating task switching basic properties during exec and so on.

This patch extends threadgroup_lock() such that it protects against all three threadgroup altering operations - fork, exit and exec. For exit, threadgroup_change_begin/end() calls are added to exit path. For exec, threadgroup_[un]lock() are updated to also grab and release cred_guard_mutex.

With this change, threadgroup_lock() guarantees that the target threadgroup will remain stable - no new task will be added, no new PF_EXITING will be set and exec won't happen.

Quoted 8 lines

The next patch will update cgroup so that it can take full advantage of this change.

Cc Oleg Nesterov <[email protected]>
Cc Andrew Morton <[email protected]>
Cc Paul Menage <[email protected]>
Cc Li Zefan <[email protected]>
Reviewed-by on v1 3/10
#29 lore raw

Re: [PATCH 03/10] threadgroup: extend threadgroup_lock() to cover exit and exec

In reply to Tejun Heo
Quoted 18 lines · On Tue, 1 Nov 2011 16:46:26 -0700 Tejun Heo <[email protected]> wrote:

threadgroup_lock() protected only protected against new addition to the threadgroup, which was inherently somewhat incomplete and problematic for its only user cgroup. On-going migration could race against exec and exit leading to interesting problems - the symmetry between various attach methods, task exiting during method execution, ->exit() racing against attach methods, migrating task switching basic properties during exec and so on.

This patch extends threadgroup_lock() such that it protects against all three threadgroup altering operations - fork, exit and exec. For exit, threadgroup_change_begin/end() calls are added to exit path. For exec, threadgroup_[un]lock() are updated to also grab and release cred_guard_mutex.

With this change, threadgroup_lock() guarantees that the target threadgroup will remain stable - no new task will be added, no new PF_EXITING will be set and exec won't happen.

Quoted 8 lines

The next patch will update cgroup so that it can take full advantage of this change.

Cc Oleg Nesterov <[email protected]>
Cc Andrew Morton <[email protected]>
Cc Paul Menage <[email protected]>
Cc Li Zefan <[email protected]>
#30 lore raw

Re: [PATCH 03/10] threadgroup: extend threadgroup_lock() to cover exit and exec

In reply to Tejun Heo
Quoted 14 lines · Tejun Heo

threadgroup_lock() protected only protected against new addition to the threadgroup, which was inherently somewhat incomplete and problematic for its only user cgroup. On-going migration could race against exec and exit leading to interesting problems - the symmetry between various attach methods, task exiting during method execution, ->exit() racing against attach methods, migrating task switching basic properties during exec and so on.

This patch extends threadgroup_lock() such that it protects against all three threadgroup altering operations - fork, exit and exec. For exit, threadgroup_change_begin/end() calls are added to exit path. For exec, threadgroup_[un]lock() are updated to also grab and release cred_guard_mutex.

Quoted 6 lines

With this change, threadgroup_lock() guarantees that the target threadgroup will remain stable - no new task will be added, no new PF_EXITING will be set and exec won't happen.

The next patch will update cgroup so that it can take full advantage of this change.

I don't want to nitpick really, but IMHO the races involved in exit and exec are too different, specific and complicated on their own to be solved in a single one patch. This should be split in two things.

The specific problems and their fix need to be described more in detail in the changelog because the issues are very tricky.

The exec case:

IIUC, the race in exec is about the group leader that can be changed
to become the exec'ing thread, making while_each_thread() unsafe.
We also have other things happening there like all the other threads
in the group that get killed, but that should be handled by the threadgroup_change_begin() you put in the exit path.
The old leader is also killed but release_task() -> __unhash_process() is called for it manually from the exec path. However this thread too should be covered by your synchronisation in exit().

So after your protection in the exit path, the only thing to protect against in exec is that group_leader that can change concurrently. But I may be missing something in the picture. Also note this is currently protected by the tasklist readlock. Cred guard mutex is certainly better, I just don't remember if you remove the tasklist lock in a further patch.

The exit case:

There the issue is about racing against cgroup_exit() where the task can be assigned to the root cgroup. Is this really a problem in fact? I don't know if subsystems care about that. Perhaps some of them are buggy in that regard. At least the task counter handles that and it needs a ->cancel_attach_task() for this purpose in the case the migration fails due to exit. Not sure about others. A real synchronization against exit is less error prone for sure. In the end that's probably a win.

#31 lore raw

Re: [PATCH 03/10] threadgroup: extend threadgroup_lock() to cover exit and exec

Quoted 40 lines · Frederic Weisbecker
Quoted 14 lines · Tejun Heo

threadgroup_lock() protected only protected against new addition to the threadgroup, which was inherently somewhat incomplete and problematic for its only user cgroup. On-going migration could race against exec and exit leading to interesting problems - the symmetry between various attach methods, task exiting during method execution, ->exit() racing against attach methods, migrating task switching basic properties during exec and so on.

This patch extends threadgroup_lock() such that it protects against all three threadgroup altering operations - fork, exit and exec. For exit, threadgroup_change_begin/end() calls are added to exit path. For exec, threadgroup_[un]lock() are updated to also grab and release cred_guard_mutex.

Quoted 6 lines

With this change, threadgroup_lock() guarantees that the target threadgroup will remain stable - no new task will be added, no new PF_EXITING will be set and exec won't happen.

The next patch will update cgroup so that it can take full advantage of this change.

I don't want to nitpick really, but IMHO the races involved in exit and exec are too different, specific and complicated on their own to be solved in a single one patch. This should be split in two things.

The specific problems and their fix need to be described more in detail in the changelog because the issues are very tricky.

The exec case:

IIUC, the race in exec is about the group leader that can be changed
to become the exec'ing thread, making while_each_thread() unsafe.
We also have other things happening there like all the other threads
in the group that get killed, but that should be handled by the threadgroup_change_begin() you put in the exit path.
The old leader is also killed but release_task() -> __unhash_process() is called for it manually from the exec path. However this thread too should be covered by your synchronisation in exit().

Quoted 5 lines

So after your protection in the exit path, the only thing to protect against in exec is that group_leader that can change concurrently. But I may be missing something in the picture. Also note this is currently protected by the tasklist readlock. Cred guard mutex is certainly better, I just don't remember if you remove the tasklist lock in a further patch.

Ah recalling what Ben Blum said, we also need the leader to stay stable because it is excpected to be passed in ->can_attach(), ->attach(), ->cancel_attach(), ... Although that's going to change after your patches that pass a flex array.

author maintainer
#32 lore raw

Re: [PATCH 03/10] threadgroup: extend threadgroup_lock() to cover exit and exec

Quoted 7 lines · Frederic Weisbecker
Quoted 3 lines

Also note this is currently protected by the tasklist readlock. Cred guard mutex is certainly better, I just don't remember if you remove the tasklist lock in a further patch.

Ah recalling what Ben Blum said, we also need the leader to stay stable because it is excpected to be passed in ->can_attach(), ->attach(), ->cancel_attach(), ... Although that's going to change after your patches that pass a flex array.

Not really without locking out exec. The thing is whoever is exec'ing will be the leader and we can't guarantee that the first in the flex array is always the leader. One method may see it as the leader, the next one might not.

Thanks.

Signature · 1 line
tejun
#33 lore raw

Re: [PATCH 03/10] threadgroup: extend threadgroup_lock() to cover exit and exec

Quoted 5 lines · Tejun Heo
Quoted 4 lines · Frederic Weisbecker
Quoted 3 lines

Also note this is currently protected by the tasklist readlock. Cred guard mutex is certainly better, I just don't remember if you remove the tasklist lock in a further patch.

Quoted 8 lines
Quoted 3 lines

Ah recalling what Ben Blum said, we also need the leader to stay stable because it is excpected to be passed in ->can_attach(), ->attach(), ->cancel_attach(), ... Although that's going to change after your patches that pass a flex array.

Not really without locking out exec. The thing is whoever is exec'ing will be the leader and we can't guarantee that the first in the flex array is always the leader. One method may see it as the leader, the next one might not.

Exactly!

#34 lore raw

Re: [PATCH 03/10] threadgroup: extend threadgroup_lock() to cover exit and exec

In reply to Tejun Heo
Quoted 5 lines · Tejun Heo
Quoted 4 lines · Frederic Weisbecker
Quoted 3 lines

Also note this is currently protected by the tasklist readlock. Cred guard mutex is certainly better, I just don't remember if you remove the tasklist lock in a further patch.

Quoted 8 lines
Quoted 3 lines

Ah recalling what Ben Blum said, we also need the leader to stay stable because it is excpected to be passed in ->can_attach(), ->attach(), ->cancel_attach(), ... Although that's going to change after your patches that pass a flex array.

Not really without locking out exec. The thing is whoever is exec'ing will be the leader and we can't guarantee that the first in the flex array is always the leader. One method may see it as the leader, the next one might not.

Exactly!

author maintainer
#35 lore raw

Re: [PATCH 03/10] threadgroup: extend threadgroup_lock() to cover exit and exec

In reply to Frederic Weisbecker
Quoted 7 lines · Frederic Weisbecker
Quoted 3 lines

Also note this is currently protected by the tasklist readlock. Cred guard mutex is certainly better, I just don't remember if you remove the tasklist lock in a further patch.

Ah recalling what Ben Blum said, we also need the leader to stay stable because it is excpected to be passed in ->can_attach(), ->attach(), ->cancel_attach(), ... Although that's going to change after your patches that pass a flex array.

Not really without locking out exec. The thing is whoever is exec'ing will be the leader and we can't guarantee that the first in the flex array is always the leader. One method may see it as the leader, the next one might not.

Thanks.

Signature · 1 line
tejun
#36 lore raw

Re: [PATCH 03/10] threadgroup: extend threadgroup_lock() to cover exit and exec

In reply to Frederic Weisbecker
Quoted 40 lines · Frederic Weisbecker
Quoted 14 lines · Tejun Heo

threadgroup_lock() protected only protected against new addition to the threadgroup, which was inherently somewhat incomplete and problematic for its only user cgroup. On-going migration could race against exec and exit leading to interesting problems - the symmetry between various attach methods, task exiting during method execution, ->exit() racing against attach methods, migrating task switching basic properties during exec and so on.

This patch extends threadgroup_lock() such that it protects against all three threadgroup altering operations - fork, exit and exec. For exit, threadgroup_change_begin/end() calls are added to exit path. For exec, threadgroup_[un]lock() are updated to also grab and release cred_guard_mutex.

Quoted 6 lines

With this change, threadgroup_lock() guarantees that the target threadgroup will remain stable - no new task will be added, no new PF_EXITING will be set and exec won't happen.

The next patch will update cgroup so that it can take full advantage of this change.

I don't want to nitpick really, but IMHO the races involved in exit and exec are too different, specific and complicated on their own to be solved in a single one patch. This should be split in two things.

The specific problems and their fix need to be described more in detail in the changelog because the issues are very tricky.

The exec case:

IIUC, the race in exec is about the group leader that can be changed
to become the exec'ing thread, making while_each_thread() unsafe.
We also have other things happening there like all the other threads
in the group that get killed, but that should be handled by the threadgroup_change_begin() you put in the exit path.
The old leader is also killed but release_task() -> __unhash_process() is called for it manually from the exec path. However this thread too should be covered by your synchronisation in exit().

Quoted 5 lines

So after your protection in the exit path, the only thing to protect against in exec is that group_leader that can change concurrently. But I may be missing something in the picture. Also note this is currently protected by the tasklist readlock. Cred guard mutex is certainly better, I just don't remember if you remove the tasklist lock in a further patch.

Ah recalling what Ben Blum said, we also need the leader to stay stable because it is excpected to be passed in ->can_attach(), ->attach(), ->cancel_attach(), ... Although that's going to change after your patches that pass a flex array.

author maintainer
#37 lore raw

Re: [PATCH 03/10] threadgroup: extend threadgroup_lock() to cover exit and exec

In reply to Frederic Weisbecker

Hello, Frederic.

Quoted 4 lines · Frederic Weisbecker
Quoted 2 lines

The next patch will update cgroup so that it can take full advantage of this change.

I don't want to nitpick really,

Heh, please go ahead and nitpick. That's one of the main purposes of LKML after all. :)

Quoted 11 lines

but IMHO the races involved in exit and exec
are too different, specific and complicated on their own to be solved in a single one patch. This should be split in two things.

The specific problems and their fix need to be described more in detail in the changelog because the issues are very tricky.

The exec case:

IIUC, the race in exec is about the group leader that can be changed to become the exec'ing thread, making while_each_thread() unsafe.

Not only that, pid changes, sighand struct may get swapped, and other weird things which aren't usually expected for a live task happen. It's basically semi-killed and then resurrected.

Quoted 9 lines

We also have other things happening there like all the other threads
in the group that get killed, but that should be handled by the threadgroup_change_begin() you put in the exit path.
The old leader is also killed but release_task() -> __unhash_process() is called for it manually from the exec path. However this thread too should be covered by your synchronisation in exit().

So after your protection in the exit path, the only thing to protect against in exec is that group_leader that can change concurrently. But I may be missing something in the picture.

Hmm... an exec'ing task goes through transitions which usually aren't allowed to happen. It assumes exclusive access to group-shared data structures and may ditch the current one and create a new one.

Sure, it's possible to make every cgroup method correct w.r.t. all those via explicit locking or by being more careful but I don't see much point in such excercise. If the code is sitting in some hot path and the added exclusion is gonna add significant amount of contention, sure, we should trade off easiness for better performance / scalability but this is for cgroup modifications, a fundamentally cold path, and the added locking is per-task or per-threadgroup. I don't see any reason not to be easy here.

Please read on.

Quoted 3 lines

Also note this is currently protected by the tasklist readlock. Cred guard mutex is certainly better, I just don't remember if you remove the tasklist lock in a further patch.

I don't remove tasklist_lock.

Quoted 9 lines

The exit case:

There the issue is about racing against cgroup_exit() where the task can be assigned to the root cgroup. Is this really a problem in fact? I don't know if subsystems care about that. Perhaps some of them are buggy in that regard. At least the task counter handles that and it needs a ->cancel_attach_task() for this purpose in the case the migration fails due to exit. Not sure about others. A real synchronization against exit is less error prone for sure. In the end that's probably a win.

This is the same story. Yes, we can narrow the locking and try to make sure everyone handles partially destroyed tasks properly in all methods, but for what? If we can give stable threadgroups to all cgroup methods without introducing performance or scalability bottleneck, that's the right thing to do. Please also note that bugs stemming from failure to handle those corner cases properly will be subtle, difficult to reproduce and track down.

In general, for any subsystem with pluggable interface, I think it's best to put as much complexity as possible into the core layer to make things eaiser for its customers. It becomes excruciatingly painful if the API invites subtle corner case bugs and the subsystem grows several dozen clients down the road.

So, to me, what seems more important is how to make it easier for each cgroup client instead of what's the minimal that's necessary right now.

Heh, did I make any sense? :)

Thanks.

Signature · 1 line
tejun
#38 lore raw

Re: [PATCH 03/10] threadgroup: extend threadgroup_lock() to cover exit and exec

Quoted 7 lines · Tejun Heo
Quoted 7 lines

but IMHO the races involved in exit and exec
are too different, specific and complicated on their own to be solved in a single one patch. This should be split in two things.

The specific problems and their fix need to be described more in detail in the changelog because the issues are very tricky.

Quoted 8 lines
Quoted 4 lines

The exec case:

IIUC, the race in exec is about the group leader that can be changed to become the exec'ing thread, making while_each_thread() unsafe.

Not only that, pid changes, sighand struct may get swapped, and other weird things which aren't usually expected for a live task happen. It's basically semi-killed and then resurrected.

Right. I've no problem with the fact you want to make the threadgroup more stable against subsystem attachment. Just please put more detailed comments about what you are protecting. It took me quite some time to precisely identify the race involved in that game in order to consciously review your patches. And I'm thinking about the next people who will work on this piece of code.

This:

static inline void threadgroup_lock(struct task_struct *tsk) {
+ /* exec uses exit for de-threading, grab cred_guard_mutex first */

+       mutex_lock(&tsk->signal->cred_guard_mutex);                                                                          
        down_write(&tsk->signal->group_rwsem);

is really not obvious.

Just point out we want to synchronize against the leader, pid and the sighand that may change concurrently.

Quoted 14 lines
Quoted 9 lines

We also have other things happening there like all the other threads
in the group that get killed, but that should be handled by the threadgroup_change_begin() you put in the exit path.
The old leader is also killed but release_task() -> __unhash_process() is called for it manually from the exec path. However this thread too should be covered by your synchronisation in exit().

So after your protection in the exit path, the only thing to protect against in exec is that group_leader that can change concurrently. But I may be missing something in the picture.

Hmm... an exec'ing task goes through transitions which usually aren't allowed to happen. It assumes exclusive access to group-shared data structures and may ditch the current one and create a new one.

Quoted 8 lines

Sure, it's possible to make every cgroup method correct w.r.t. all those via explicit locking or by being more careful but I don't see much point in such excercise. If the code is sitting in some hot path and the added exclusion is gonna add significant amount of contention, sure, we should trade off easiness for better performance / scalability but this is for cgroup modifications, a fundamentally cold path, and the added locking is per-task or per-threadgroup. I don't see any reason not to be easy here.

Sure. I have no problem with that. I just wish we can precisely identify what we are protecting against, and not have a foggy locking. Especially when it's about such a very core issue.

Quoted 7 lines

Please read on.

Quoted 3 lines

Also note this is currently protected by the tasklist readlock. Cred guard mutex is certainly better, I just don't remember if you remove the tasklist lock in a further patch.

I don't remove tasklist_lock.

I believe you can do it after using the cred guard mutex. That needs to be double checked though. I think it was mostly there to keep while_each_thread() stable non-racy against leader change. cred_guard_mutex should handle that now.

Quoted 11 lines
Quoted 10 lines

The exit case:

There the issue is about racing against cgroup_exit() where the task can be assigned to the root cgroup. Is this really a problem in fact? I don't know if subsystems care about that. Perhaps some of them are buggy in that regard. At least the task counter handles that and it needs a ->cancel_attach_task() for this purpose in the case the migration fails due to exit. Not sure about others. A real synchronization against exit is less error prone for sure. In the end that's probably a win.

Quoted 7 lines

This is the same story. Yes, we can narrow the locking and try to make sure everyone handles partially destroyed tasks properly in all methods, but for what? If we can give stable threadgroups to all cgroup methods without introducing performance or scalability bottleneck, that's the right thing to do. Please also note that bugs stemming from failure to handle those corner cases properly will be subtle, difficult to reproduce and track down.

Agreed.

Quoted 5 lines

In general, for any subsystem with pluggable interface, I think it's best to put as much complexity as possible into the core layer to make things eaiser for its customers. It becomes excruciatingly painful if the API invites subtle corner case bugs and the subsystem grows several dozen clients down the road.

Sure.

Quoted 6 lines

So, to me, what seems more important is how to make it easier for each cgroup client instead of what's the minimal that's necessary right now.

Heh, did I make any sense? :)

Yep, fine for me :)
Just wanted to ensure I (and others) understood and identified well the issues and the fixes.

author maintainer
#39 lore raw

Re: [PATCH 03/10] threadgroup: extend threadgroup_lock() to cover exit and exec

Hello, Frederic.

Quoted 8 lines · Frederic Weisbecker

This:

static inline void threadgroup_lock(struct task_struct *tsk) {

include/linux/sched.h +2 -0
+       /* exec uses exit for de-threading, grab cred_guard_mutex first */                                                   
+       mutex_lock(&tsk->signal->cred_guard_mutex);                                                                          
        down_write(&tsk->signal->group_rwsem);
Quoted 4 lines

is really not obvious.

Just point out we want to synchronize against the leader, pid and the sighand that may change concurrently.

Sure thing. Will beef up the comments.

Quoted 10 lines
Quoted 5 lines
Quoted 3 lines

Also note this is currently protected by the tasklist readlock. Cred guard mutex is certainly better, I just don't remember if you remove the tasklist lock in a further patch.

I don't remove tasklist_lock.

I believe you can do it after using the cred guard mutex. That needs to be double checked though. I think it was mostly there to keep while_each_thread() stable non-racy against leader change. cred_guard_mutex should handle that now.

Yes, probably, but I still don't think removing that is a good idea. We're walking tasklist in a rather cold path, IMHO it's better to keep the locking obvious.

Quoted 9 lines
Quoted 5 lines

So, to me, what seems more important is how to make it easier for each cgroup client instead of what's the minimal that's necessary right now.

Heh, did I make any sense? :)

Yep, fine for me :)
Just wanted to ensure I (and others) understood and identified well the issues and the fixes.

Yeap, fair enough. Will update the patches, repost and push them through cgroup/for-3.3, and move onto other pending patchsets.

Thank you.

Signature · 1 line
tejun

A message that is not in the archive (sent off-list or not yet received): 20111123140139.GB10669-oHC15RC7JGTpAmv0O++HtFaTQe2KTcn/@public.gmane.org

#41 lore raw

Re: [PATCH 03/10] threadgroup: extend threadgroup_lock() to cover exit and exec

Hello, Frederic.

Quoted 8 lines · Frederic Weisbecker

This:

static inline void threadgroup_lock(struct task_struct *tsk) {

include/linux/sched.h +2 -0
+       /* exec uses exit for de-threading, grab cred_guard_mutex first */                                                   
+       mutex_lock(&tsk->signal->cred_guard_mutex);                                                                          
        down_write(&tsk->signal->group_rwsem);
Quoted 4 lines

is really not obvious.

Just point out we want to synchronize against the leader, pid and the sighand that may change concurrently.

Sure thing. Will beef up the comments.

Quoted 10 lines
Quoted 5 lines
Quoted 3 lines

Also note this is currently protected by the tasklist readlock. Cred guard mutex is certainly better, I just don't remember if you remove the tasklist lock in a further patch.

I don't remove tasklist_lock.

I believe you can do it after using the cred guard mutex. That needs to be double checked though. I think it was mostly there to keep while_each_thread() stable non-racy against leader change. cred_guard_mutex should handle that now.

Yes, probably, but I still don't think removing that is a good idea. We're walking tasklist in a rather cold path, IMHO it's better to keep the locking obvious.

Quoted 9 lines
Quoted 5 lines

So, to me, what seems more important is how to make it easier for each cgroup client instead of what's the minimal that's necessary right now.

Heh, did I make any sense? :)

Yep, fine for me :)
Just wanted to ensure I (and others) understood and identified well the issues and the fixes.

Yeap, fair enough. Will update the patches, repost and push them through cgroup/for-3.3, and move onto other pending patchsets.

Thank you.

Signature · 1 line
tejun
#42 lore raw

Re: [PATCH 03/10] threadgroup: extend threadgroup_lock() to cover exit and exec

In reply to Tejun Heo
Quoted 7 lines · Tejun Heo
Quoted 7 lines

but IMHO the races involved in exit and exec
are too different, specific and complicated on their own to be solved in a single one patch. This should be split in two things.

The specific problems and their fix need to be described more in detail in the changelog because the issues are very tricky.

Quoted 8 lines
Quoted 4 lines

The exec case:

IIUC, the race in exec is about the group leader that can be changed to become the exec'ing thread, making while_each_thread() unsafe.

Not only that, pid changes, sighand struct may get swapped, and other weird things which aren't usually expected for a live task happen. It's basically semi-killed and then resurrected.

Right. I've no problem with the fact you want to make the threadgroup more stable against subsystem attachment. Just please put more detailed comments about what you are protecting. It took me quite some time to precisely identify the race involved in that game in order to consciously review your patches. And I'm thinking about the next people who will work on this piece of code.

This:

static inline void threadgroup_lock(struct task_struct *tsk) {
+ /* exec uses exit for de-threading, grab cred_guard_mutex first */

+       mutex_lock(&tsk->signal->cred_guard_mutex);                                                                          
        down_write(&tsk->signal->group_rwsem);

is really not obvious.

Just point out we want to synchronize against the leader, pid and the sighand that may change concurrently.

Quoted 14 lines
Quoted 9 lines

We also have other things happening there like all the other threads
in the group that get killed, but that should be handled by the threadgroup_change_begin() you put in the exit path.
The old leader is also killed but release_task() -> __unhash_process() is called for it manually from the exec path. However this thread too should be covered by your synchronisation in exit().

So after your protection in the exit path, the only thing to protect against in exec is that group_leader that can change concurrently. But I may be missing something in the picture.

Hmm... an exec'ing task goes through transitions which usually aren't allowed to happen. It assumes exclusive access to group-shared data structures and may ditch the current one and create a new one.

Quoted 8 lines

Sure, it's possible to make every cgroup method correct w.r.t. all those via explicit locking or by being more careful but I don't see much point in such excercise. If the code is sitting in some hot path and the added exclusion is gonna add significant amount of contention, sure, we should trade off easiness for better performance / scalability but this is for cgroup modifications, a fundamentally cold path, and the added locking is per-task or per-threadgroup. I don't see any reason not to be easy here.

Sure. I have no problem with that. I just wish we can precisely identify what we are protecting against, and not have a foggy locking. Especially when it's about such a very core issue.

Quoted 7 lines

Please read on.

Quoted 3 lines

Also note this is currently protected by the tasklist readlock. Cred guard mutex is certainly better, I just don't remember if you remove the tasklist lock in a further patch.

I don't remove tasklist_lock.

I believe you can do it after using the cred guard mutex. That needs to be double checked though. I think it was mostly there to keep while_each_thread() stable non-racy against leader change. cred_guard_mutex should handle that now.

Quoted 11 lines
Quoted 10 lines

The exit case:

There the issue is about racing against cgroup_exit() where the task can be assigned to the root cgroup. Is this really a problem in fact? I don't know if subsystems care about that. Perhaps some of them are buggy in that regard. At least the task counter handles that and it needs a ->cancel_attach_task() for this purpose in the case the migration fails due to exit. Not sure about others. A real synchronization against exit is less error prone for sure. In the end that's probably a win.

Quoted 7 lines

This is the same story. Yes, we can narrow the locking and try to make sure everyone handles partially destroyed tasks properly in all methods, but for what? If we can give stable threadgroups to all cgroup methods without introducing performance or scalability bottleneck, that's the right thing to do. Please also note that bugs stemming from failure to handle those corner cases properly will be subtle, difficult to reproduce and track down.

Agreed.

Quoted 5 lines

In general, for any subsystem with pluggable interface, I think it's best to put as much complexity as possible into the core layer to make things eaiser for its customers. It becomes excruciatingly painful if the API invites subtle corner case bugs and the subsystem grows several dozen clients down the road.

Sure.

Quoted 6 lines

So, to me, what seems more important is how to make it easier for each cgroup client instead of what's the minimal that's necessary right now.

Heh, did I make any sense? :)

Yep, fine for me :)
Just wanted to ensure I (and others) understood and identified well the issues and the fixes.

author maintainer
#43 lore raw

Re: [PATCH 03/10] threadgroup: extend threadgroup_lock() to cover exit and exec

In reply to Frederic Weisbecker

Hello, Frederic.

Quoted 4 lines · Frederic Weisbecker
Quoted 2 lines

The next patch will update cgroup so that it can take full advantage of this change.

I don't want to nitpick really,

Heh, please go ahead and nitpick. That's one of the main purposes of LKML after all. :)

Quoted 11 lines

but IMHO the races involved in exit and exec
are too different, specific and complicated on their own to be solved in a single one patch. This should be split in two things.

The specific problems and their fix need to be described more in detail in the changelog because the issues are very tricky.

The exec case:

IIUC, the race in exec is about the group leader that can be changed to become the exec'ing thread, making while_each_thread() unsafe.

Not only that, pid changes, sighand struct may get swapped, and other weird things which aren't usually expected for a live task happen. It's basically semi-killed and then resurrected.

Quoted 9 lines

We also have other things happening there like all the other threads
in the group that get killed, but that should be handled by the threadgroup_change_begin() you put in the exit path.
The old leader is also killed but release_task() -> __unhash_process() is called for it manually from the exec path. However this thread too should be covered by your synchronisation in exit().

So after your protection in the exit path, the only thing to protect against in exec is that group_leader that can change concurrently. But I may be missing something in the picture.

Hmm... an exec'ing task goes through transitions which usually aren't allowed to happen. It assumes exclusive access to group-shared data structures and may ditch the current one and create a new one.

Sure, it's possible to make every cgroup method correct w.r.t. all those via explicit locking or by being more careful but I don't see much point in such excercise. If the code is sitting in some hot path and the added exclusion is gonna add significant amount of contention, sure, we should trade off easiness for better performance / scalability but this is for cgroup modifications, a fundamentally cold path, and the added locking is per-task or per-threadgroup. I don't see any reason not to be easy here.

Please read on.

Quoted 3 lines

Also note this is currently protected by the tasklist readlock. Cred guard mutex is certainly better, I just don't remember if you remove the tasklist lock in a further patch.

I don't remove tasklist_lock.

Quoted 9 lines

The exit case:

There the issue is about racing against cgroup_exit() where the task can be assigned to the root cgroup. Is this really a problem in fact? I don't know if subsystems care about that. Perhaps some of them are buggy in that regard. At least the task counter handles that and it needs a ->cancel_attach_task() for this purpose in the case the migration fails due to exit. Not sure about others. A real synchronization against exit is less error prone for sure. In the end that's probably a win.

This is the same story. Yes, we can narrow the locking and try to make sure everyone handles partially destroyed tasks properly in all methods, but for what? If we can give stable threadgroups to all cgroup methods without introducing performance or scalability bottleneck, that's the right thing to do. Please also note that bugs stemming from failure to handle those corner cases properly will be subtle, difficult to reproduce and track down.

In general, for any subsystem with pluggable interface, I think it's best to put as much complexity as possible into the core layer to make things eaiser for its customers. It becomes excruciatingly painful if the API invites subtle corner case bugs and the subsystem grows several dozen clients down the road.

So, to me, what seems more important is how to make it easier for each cgroup client instead of what's the minimal that's necessary right now.

Heh, did I make any sense? :)

Thanks.

Signature · 1 line
tejun
#44 lore raw

Re: [PATCH 03/10] threadgroup: extend threadgroup_lock() to cover exit and exec

In reply to Tejun Heo
Quoted 14 lines · Tejun Heo

threadgroup_lock() protected only protected against new addition to the threadgroup, which was inherently somewhat incomplete and problematic for its only user cgroup. On-going migration could race against exec and exit leading to interesting problems - the symmetry between various attach methods, task exiting during method execution, ->exit() racing against attach methods, migrating task switching basic properties during exec and so on.

This patch extends threadgroup_lock() such that it protects against all three threadgroup altering operations - fork, exit and exec. For exit, threadgroup_change_begin/end() calls are added to exit path. For exec, threadgroup_[un]lock() are updated to also grab and release cred_guard_mutex.

Quoted 6 lines

With this change, threadgroup_lock() guarantees that the target threadgroup will remain stable - no new task will be added, no new PF_EXITING will be set and exec won't happen.

The next patch will update cgroup so that it can take full advantage of this change.

I don't want to nitpick really, but IMHO the races involved in exit and exec are too different, specific and complicated on their own to be solved in a single one patch. This should be split in two things.

The specific problems and their fix need to be described more in detail in the changelog because the issues are very tricky.

The exec case:

IIUC, the race in exec is about the group leader that can be changed
to become the exec'ing thread, making while_each_thread() unsafe.
We also have other things happening there like all the other threads
in the group that get killed, but that should be handled by the threadgroup_change_begin() you put in the exit path.
The old leader is also killed but release_task() -> __unhash_process() is called for it manually from the exec path. However this thread too should be covered by your synchronisation in exit().

So after your protection in the exit path, the only thing to protect against in exec is that group_leader that can change concurrently. But I may be missing something in the picture. Also note this is currently protected by the tasklist readlock. Cred guard mutex is certainly better, I just don't remember if you remove the tasklist lock in a further patch.

The exit case:

There the issue is about racing against cgroup_exit() where the task can be assigned to the root cgroup. Is this really a problem in fact? I don't know if subsystems care about that. Perhaps some of them are buggy in that regard. At least the task counter handles that and it needs a ->cancel_attach_task() for this purpose in the case the migration fails due to exit. Not sure about others. A real synchronization against exit is less error prone for sure. In the end that's probably a win.

#45 lore raw

Re: [PATCH 03/10] threadgroup: extend threadgroup_lock() to cover exit and exec

In reply to Tejun Heo
Quoted 16 lines · Tejun Heo
include/linux/sched.h +8 -0
index aa47d0f..6fb546e 100644
@@ -2377,13 +2377,37 @@ static inline void threadgroup_change_done(struct task_struct *tsk)
 {
 	up_read(&tsk->signal->group_rwsem);
 }
+
+/**
+ * threadgroup_lock - lock threadgroup
+ * @tsk: member task of the threadgroup to lock
+ *
+ * Lock the threadgroup @tsk belongs to.  No new task is allowed to enter
+ * and member tasks aren't allowed to exit (as indicated by PF_EXITING) or
+ * perform exec.  This is useful for cases where the threadgroup needs to

include/linux/sched.h static inline void threadgroup_change_done(struct task_struct *tsk) · line 2397

Quoted 10 lines
include/linux/sched.h +8 -0
+ * stay stable across blockable operations.
+ *
+ * fork and exit explicitly call threadgroup_change_{begin|end}() for
+ * synchronization.  exec is already synchronized with cread_guard_mutex
+ * which we grab here.
+ */
 static inline void threadgroup_lock(struct task_struct *tsk)
 {
+	/* exec uses exit for de-threading, grab cred_guard_mutex first */
+	mutex_lock(&tsk->signal->cred_guard_mutex);

May be worth explaining what in exec is synchronized through this. ie the leader.

Quoted 21 lines
include/linux/sched.h +8 -0
 	down_write(&tsk->signal->group_rwsem);
 }
+
+/**
+ * threadgroup_unlock - unlock threadgroup
+ * @tsk: member task of the threadgroup to unlock
+ *
+ * Reverse threadgroup_lock().
+ */
 static inline void threadgroup_unlock(struct task_struct *tsk)
 {
 	up_write(&tsk->signal->group_rwsem);
+	mutex_unlock(&tsk->signal->cred_guard_mutex);
 }
 #else
 static inline void threadgroup_change_begin(struct task_struct *tsk) {}
kernel/exit.c +0 -0
index 2913b35..3565f00 100644

kernel/exit.c NORET_TYPE void do_exit(long code) · line 945

Quoted 8 lines
kernel/exit.c +5 -0
@@ -938,6 +938,12 @@ NORET_TYPE void do_exit(long code)
 		schedule();
 	}
 
+	/*
+	 * @tsk's threadgroup is going through changes - lock out users
+	 * which expect stable threadgroup.
+	 */
+	threadgroup_change_begin(tsk);

Why so early? You actually want to synchronize against cgroup_exit(). Or am I missing something?

Yeah there may be a problem with reading PF_EXITING inside the lock if it is updated outside the lock, may be we need a pair of memory barriers. But other than that, this should be enough to lock around cgroup_exit().

author maintainer
#46 lore raw

PATCH 3/10 threadgroup: extend threadgroup_lock() to cover exit and exec

In reply to Tejun Heo

threadgroup_lock() protected only protected against new addition to the threadgroup, which was inherently somewhat incomplete and problematic for its only user cgroup. On-going migration could race against exec and exit leading to interesting problems - the symmetry between various attach methods, task exiting during method execution, ->exit() racing against attach methods, migrating task switching basic properties during exec and so on.

This patch extends threadgroup_lock() such that it protects against all three threadgroup altering operations - fork, exit and exec. For exit, threadgroup_change_begin/end() calls are added to exit path. For exec, threadgroup_[un]lock() are updated to also grab and release cred_guard_mutex.

With this change, threadgroup_lock() guarantees that the target threadgroup will remain stable - no new task will be added, no new PF_EXITING will be set and exec won't happen.

The next patch will update cgroup so that it can take full advantage of this change.

-v2: beefed up comment as suggested by Frederic.

Cc Oleg Nesterov <[email protected]>
Cc Andrew Morton <[email protected]>
Cc Paul Menage <[email protected]>
Cc Frederic Weisbecker <[email protected]>
Cc Linus Torvalds <[email protected]>

---

Linus, this is something being scheduled for the next merge window. It extends threadgroup locking which cgroup used to do to exclude only fork path so that it includes exit and exec paths. threadgroup locking is used by cgroup to implement process-scope cgroup migration.

Migration happens in multiple steps, at each of which the matching method of each cgroup plugin is called. When a whole process is being migrated, methods being called at those different steps expect to see consistent image of the thread group.

cgroup currently only locks out addition of new tasks into the thread group and holds extra ref to member tasks. This mandates all cgroup plugins to deal with tasks being torn down and exec morphing the threadgroup. This patch extends the scope of threadgroup locking such that the thread group is guaranteed to be stable (no new task, tasks are either live or dead and no exec morphing) while locked.

This is part of changes to clean up cgroup methods and iron out corner case fuzziness and should make difficult-to-reproduce race conditions less likely and cgroup plugins easier to implement and verify.

The synchronization is strictly per-threadgroup and goes away if cgroup is not configured.

The whole series is avilable at

http://thread.gmane.org/gmane.linux.kernel.containers/21716
git://git.kernel.org/pub/scm/linux/kernel/git/tj/cgroup.git for-3.3

Thank you.

 include/linux/sched.h |   47 +++++++++++++++++++++++++++++++++++++++++------
 kernel/exit.c         |   19 +++++++++++++++----
 2 files changed, 56 insertions(+), 10 deletions(-)
include/linux/sched.h +41 -6
index c5acbce..481c5ed 100644
@@ -635,11 +635,12 @@ struct signal_struct {
 #endif
 #ifdef CONFIG_CGROUPS
 	/*
-	 * The group_rwsem prevents threads from forking with
-	 * CLONE_THREAD while held for writing. Use this for fork-sensitive
-	 * threadgroup-wide operations. It's taken for reading in fork.c in
-	 * copy_process().
-	 * Currently only needed write-side by cgroups.
+	 * group_rwsem prevents new tasks from entering the threadgroup and
+	 * member tasks from exiting.  fork and exit paths are protected
+	 * with this rwsem using threadgroup_change_begin/end().  Users
+	 * which require threadgroup to remain stable should use
+	 * threadgroup_[un]lock() which also takes care of exec path.
+	 * Currently, cgroup is the only user.
 	 */
 	struct rw_semaphore group_rwsem;
 #endif
@@ -2374,7 +2375,6 @@ static inline void unlock_task_sighand(struct task_struct *tsk,
 	spin_unlock_irqrestore(&tsk->sighand->siglock, *flags);
 }
 
-/* See the declaration of group_rwsem in signal_struct. */
 #ifdef CONFIG_CGROUPS
 static inline void threadgroup_change_begin(struct task_struct *tsk)
 {
@@ -2384,13 +2384,48 @@ static inline void threadgroup_change_end(struct task_struct *tsk)
 {
 	up_read(&tsk->signal->group_rwsem);
 }
+
+/**
+ * threadgroup_lock - lock threadgroup
+ * @tsk: member task of the threadgroup to lock
+ *
+ * Lock the threadgroup @tsk belongs to.  No new task is allowed to enter
+ * and member tasks aren't allowed to exit (as indicated by PF_EXITING) or
+ * perform exec.  This is useful for cases where the threadgroup needs to
+ * stay stable across blockable operations.
+ *
+ * fork and exit explicitly call threadgroup_change_{begin|end}() for
+ * synchronization.  This excludes most of do_exit() to ensure that, while
+ * locked, tasks belonging to a locked group are not in the process of
+ * deconstruction - they're either alive or dead.
+ *
+ * During exec, a task goes and puts its thread group through unusual
+ * changes.  After de-threading, exclusive access is assumed to resources
+ * which are usually shared by tasks in the same group - e.g. sighand may
+ * be replaced with a new one.  Also, the exec'ing task takes over group
+ * leader role including its pid.  Exclude these changes while locked by
+ * grabbing cred_guard_mutex which is used to synchronize exec path.
+ */
 static inline void threadgroup_lock(struct task_struct *tsk)
 {
+	/*
+	 * exec uses exit for de-threading nesting group_rwsem inside
+	 * cred_guard_mutex. Grab cred_guard_mutex first.
+	 */
+	mutex_lock(&tsk->signal->cred_guard_mutex);
 	down_write(&tsk->signal->group_rwsem);
 }
+
+/**
+ * threadgroup_unlock - unlock threadgroup
+ * @tsk: member task of the threadgroup to unlock
+ *
+ * Reverse threadgroup_lock().
+ */
 static inline void threadgroup_unlock(struct task_struct *tsk)
 {
 	up_write(&tsk->signal->group_rwsem);
+	mutex_unlock(&tsk->signal->cred_guard_mutex);
 }
 #else
 static inline void threadgroup_change_begin(struct task_struct *tsk) {}
kernel/exit.c +15 -4
index d0b7d98..b2cb562 100644
@@ -936,6 +936,14 @@ NORET_TYPE void do_exit(long code)
 		schedule();
 	}
 
+	/*
+	 * @tsk's threadgroup is going through changes - lock out users
+	 * which expect stable threadgroup.  Do this before actually
+	 * starting tearing down @tsk so that locked threadgroup has either
+	 * alive or dead tasks, not something inbetween.
+	 */
+	threadgroup_change_begin(tsk);
+
 	exit_irq_thread();
 
 	exit_signals(tsk);  /* sets PF_EXITING */
@@ -1018,10 +1026,6 @@ NORET_TYPE void do_exit(long code)
 		kfree(current->pi_state_cache);
 #endif
 	/*
-	 * Make sure we are holding no locks:
-	 */
-	debug_check_no_locks_held(tsk);
-	/*
 	 * We can do this unlocked here. The futex code uses this flag
 	 * just to verify whether the pi state cleanup has been done
 	 * or not. In the worst case it loops once more.
@@ -1038,6 +1042,13 @@ NORET_TYPE void do_exit(long code)
 
 	preempt_disable();
 	exit_rcu();
+
+	/*
+	 * Release threadgroup and make sure we are holding no locks.
+	 */
+	threadgroup_change_end(tsk);
+	debug_check_no_locks_held(tsk);
+
 	/* causes final put_task_struct in finish_task_switch(). */
 	tsk->state = TASK_DEAD;
 	schedule();
Signature · 1 line
1.7.3.1
#47 lore raw

Re: [PATCH UPDATED 03/10] threadgroup: extend threadgroup_lock() to cover exit and exec

Quoted 10 lines · Tejun Heo

Linus, this is something being scheduled for the next merge window. It extends threadgroup locking which cgroup used to do to exclude only fork path so that it includes exit and exec paths.  threadgroup locking is used by cgroup to implement process-scope cgroup migration.

Migration happens in multiple steps, at each of which the matching method of each cgroup plugin is called.  When a whole process is being migrated, methods being called at those different steps expect to see consistent image of the thread group.

Ugh.

I see why you want to do this, but I really don't much like some of it.

I've got a few questions:

- do you even *need* two separate locks at all? If you extend the
threadgroup_fork_lock to cover exec/exit too (and rename it), then why does that separate cred_guard_mutex make sense at all?

Taking two locks for the common exit case seems disgusting. What do
the separate locks buy us?

- could we possible avoid the lock(s) entirely for the
single-threaded case? The fact that ptrace wants to serialize makes me say "maybe we can't avoid it", but I thought I'd ask. Even if we do need/want two separate locks, do we really want to take them both for the case that shouldn't really need even a single one?

Hmm? Simplifying the locking rules is always a good idea, and I think this seems to make some of it more straightforward, but at the same time I shudder when I look at some of the patches in the series that nest locking three locks deep. Ugh.

Linus

author maintainer
#48 lore raw

Re: [PATCH UPDATED 03/10] threadgroup: extend threadgroup_lock() to cover exit and exec

Hello, Linus.

Quoted 3 lines · Linus Torvalds

- do you even *need* two separate locks at all? If you extend the
threadgroup_fork_lock to cover exec/exit too (and rename it), then why does that separate cred_guard_mutex make sense at all?

That was mostly choosing the path of least resistance as both locks already existed and exec exclusion was added later on, but yeah there isn't much to lose by merging those two locks (do_exit nesting inside exec will need some care tho). I'll try to unify the two locks.

Quoted 2 lines

Taking two locks for the common exit case seems disgusting. What do
the separate locks buy us?

A bit confused. threadgroup_change_begin() only locks signal->group_rwsem. ie. fork/exit is protected by signal->group_rwsem. exec is protected by signal->cred_guard_mutex. Grabbing both locks gives stable thread group.

Quoted 5 lines

- could we possible avoid the lock(s) entirely for the
single-threaded case? The fact that ptrace wants to serialize makes me say "maybe we can't avoid it", but I thought I'd ask. Even if we do need/want two separate locks, do we really want to take them both for the case that shouldn't really need even a single one?

Maybe we can avoid grabbing a lock on exec and exit if the task belongs to single task process but optimizations like that can be very fragile. I think it would be better to merge the two locks and keep the locking unconditional.

Quoted 4 lines

Hmm? Simplifying the locking rules is always a good idea, and I think this seems to make some of it more straightforward, but at the same time I shudder when I look at some of the patches in the series that nest locking three locks deep. Ugh.

cgroup_root_mutex separation is to avoid lock recursion via cgroup_show_options and can probably be done in lighter handed way. As I'm still getting used to cgroup code, I wanted to resolve the locking order without changing much else.

Anyways, I'll send separated patch series dealing with fork/exec/exit synchronization.

Thank you.

Signature · 1 line
tejun
author maintainer
#49 lore raw

Re: [PATCH UPDATED 03/10] threadgroup: extend threadgroup_lock() to cover exit and exec

Hello, Linus.

Quoted 9 lines · Tejun Heo
Quoted 3 lines · Linus Torvalds

- do you even *need* two separate locks at all? If you extend the
threadgroup_fork_lock to cover exec/exit too (and rename it), then why does that separate cred_guard_mutex make sense at all?

That was mostly choosing the path of least resistance as both locks already existed and exec exclusion was added later on, but yeah there isn't much to lose by merging those two locks (do_exit nesting inside exec will need some care tho). I'll try to unify the two locks.

Hmmm... I tried this but it seems more difficult than originally expected. The biggest obstacle is that we don't have _interruptible and _killable rwsem ops.

As proposed, what we have is,

* fork and exit paths are read-protected by group_rwsem. ie. forks and exits don't block each other.

* exec path is protected by cred_guard_mutex. cred_guard_mutex also synchronizes parallel exec attempts. (It's probably better to rename it to exec_mutex)

* thread_group_lock() makes the thread group stable by write-locking group_rwsem and grabbing cred_guard_mutex. The former excludes forks and exits and the latter exec.

group_rwsem and cred_guard_mutex can be merged by,

* fork and exit paths read-lock group_rwsem as it currently does.

* exec path write-locks group_rwsem. Note that it'll need to unlock in de_thread() while waiting for other threads to exit as exit path can't proceed while group_rwsem is write-locked. prepare_bprm_creds() should be updated to check signal_pending() after acquiring group_rwsem.

This introduces synchronization between exec and fork/exit paths, but I don't think this is something we need to worry about as both fork-exec and exit-exec scalabilities don't matter.

* thread_group_lock() write-locks group_rwsem.

The problem is that cred_guard_mutex uses _interruptible/_killable operations and rwsem doesn't have them, so cred_guard_mutex can't be easily replaced with write-locking group_rwsem.

If the two locks can't be merged, under the proposed scheme, while not exactly pretty, both fork/exit and exec paths go through single locking and only the ones which want stable threadgroup need to grab both locks, so IMHO it is at least reasonable.

Any better ideas?

Thanks.

Signature · 1 line
tejun
author maintainer
#50 lore raw

Re: [PATCH UPDATED 03/10] threadgroup: extend threadgroup_lock() to cover exit and exec

Hello again, Linus.

Quoted 10 lines · Tejun Heo

The problem is that cred_guard_mutex uses _interruptible/_killable operations and rwsem doesn't have them, so cred_guard_mutex can't be easily replaced with write-locking group_rwsem.

If the two locks can't be merged, under the proposed scheme, while not exactly pretty, both fork/exit and exec paths go through single locking and only the ones which want stable threadgroup need to grab both locks, so IMHO it is at least reasonable.

Any better ideas?

I agree that the proposed solution is rather ugly but stable thread-group is a valid mechanism to have and cgroup can benefit a lot from it. I'd be happy to revamp the implementation if anyone can come up with a better way and can add big fat comment stating that. Until something better comes up, would it be okay to stick with this implementation?

Thanks.

Signature · 1 line
tejun

A message that is not in the archive (sent off-list or not yet received): [email protected]

#52 lore raw

Re: [PATCH UPDATED 03/10] threadgroup: extend threadgroup_lock() to cover exit and exec

Hello again, Linus.

Quoted 10 lines · Tejun Heo

The problem is that cred_guard_mutex uses _interruptible/_killable operations and rwsem doesn't have them, so cred_guard_mutex can't be easily replaced with write-locking group_rwsem.

If the two locks can't be merged, under the proposed scheme, while not exactly pretty, both fork/exit and exec paths go through single locking and only the ones which want stable threadgroup need to grab both locks, so IMHO it is at least reasonable.

Any better ideas?

I agree that the proposed solution is rather ugly but stable thread-group is a valid mechanism to have and cgroup can benefit a lot from it. I'd be happy to revamp the implementation if anyone can come up with a better way and can add big fat comment stating that. Until something better comes up, would it be okay to stick with this implementation?

Thanks.

Signature · 1 line
tejun
author maintainer
#53 lore raw

Re: [PATCH UPDATED 03/10] threadgroup: extend threadgroup_lock() to cover exit and exec

In reply to Tejun Heo

Hello, Linus.

Quoted 9 lines · Tejun Heo
Quoted 3 lines · Linus Torvalds

- do you even *need* two separate locks at all? If you extend the
threadgroup_fork_lock to cover exec/exit too (and rename it), then why does that separate cred_guard_mutex make sense at all?

That was mostly choosing the path of least resistance as both locks already existed and exec exclusion was added later on, but yeah there isn't much to lose by merging those two locks (do_exit nesting inside exec will need some care tho). I'll try to unify the two locks.

Hmmm... I tried this but it seems more difficult than originally expected. The biggest obstacle is that we don't have _interruptible and _killable rwsem ops.

As proposed, what we have is,

* fork and exit paths are read-protected by group_rwsem. ie. forks and exits don't block each other.

* exec path is protected by cred_guard_mutex. cred_guard_mutex also synchronizes parallel exec attempts. (It's probably better to rename it to exec_mutex)

* thread_group_lock() makes the thread group stable by write-locking group_rwsem and grabbing cred_guard_mutex. The former excludes forks and exits and the latter exec.

group_rwsem and cred_guard_mutex can be merged by,

* fork and exit paths read-lock group_rwsem as it currently does.

* exec path write-locks group_rwsem. Note that it'll need to unlock in de_thread() while waiting for other threads to exit as exit path can't proceed while group_rwsem is write-locked. prepare_bprm_creds() should be updated to check signal_pending() after acquiring group_rwsem.

This introduces synchronization between exec and fork/exit paths, but I don't think this is something we need to worry about as both fork-exec and exit-exec scalabilities don't matter.

* thread_group_lock() write-locks group_rwsem.

The problem is that cred_guard_mutex uses _interruptible/_killable operations and rwsem doesn't have them, so cred_guard_mutex can't be easily replaced with write-locking group_rwsem.

If the two locks can't be merged, under the proposed scheme, while not exactly pretty, both fork/exit and exec paths go through single locking and only the ones which want stable threadgroup need to grab both locks, so IMHO it is at least reasonable.

Any better ideas?

Thanks.

Signature · 1 line
tejun
author maintainer
#54 lore raw

Re: [PATCH UPDATED 03/10] threadgroup: extend threadgroup_lock() to cover exit and exec

In reply to Linus Torvalds

Hello, Linus.

Quoted 3 lines · Linus Torvalds

- do you even *need* two separate locks at all? If you extend the
threadgroup_fork_lock to cover exec/exit too (and rename it), then why does that separate cred_guard_mutex make sense at all?

That was mostly choosing the path of least resistance as both locks already existed and exec exclusion was added later on, but yeah there isn't much to lose by merging those two locks (do_exit nesting inside exec will need some care tho). I'll try to unify the two locks.

Quoted 2 lines

Taking two locks for the common exit case seems disgusting. What do
the separate locks buy us?

A bit confused. threadgroup_change_begin() only locks signal->group_rwsem. ie. fork/exit is protected by signal->group_rwsem. exec is protected by signal->cred_guard_mutex. Grabbing both locks gives stable thread group.

Quoted 5 lines

- could we possible avoid the lock(s) entirely for the
single-threaded case? The fact that ptrace wants to serialize makes me say "maybe we can't avoid it", but I thought I'd ask. Even if we do need/want two separate locks, do we really want to take them both for the case that shouldn't really need even a single one?

Maybe we can avoid grabbing a lock on exec and exit if the task belongs to single task process but optimizations like that can be very fragile. I think it would be better to merge the two locks and keep the locking unconditional.

Quoted 4 lines

Hmm? Simplifying the locking rules is always a good idea, and I think this seems to make some of it more straightforward, but at the same time I shudder when I look at some of the patches in the series that nest locking three locks deep. Ugh.

cgroup_root_mutex separation is to avoid lock recursion via cgroup_show_options and can probably be done in lighter handed way. As I'm still getting used to cgroup code, I wanted to resolve the locking order without changing much else.

Anyways, I'll send separated patch series dealing with fork/exec/exit synchronization.

Thank you.

Signature · 1 line
tejun

A message that is not in the archive (sent off-list or not yet received): [email protected]

#56 lore raw

Re: [PATCH UPDATED 03/10] threadgroup: extend threadgroup_lock() to cover exit and exec

Quoted 10 lines · Tejun Heo

Linus, this is something being scheduled for the next merge window. It extends threadgroup locking which cgroup used to do to exclude only fork path so that it includes exit and exec paths.  threadgroup locking is used by cgroup to implement process-scope cgroup migration.

Migration happens in multiple steps, at each of which the matching method of each cgroup plugin is called.  When a whole process is being migrated, methods being called at those different steps expect to see consistent image of the thread group.

Ugh.

I see why you want to do this, but I really don't much like some of it.

I've got a few questions:

- do you even *need* two separate locks at all? If you extend the
threadgroup_fork_lock to cover exec/exit too (and rename it), then why does that separate cred_guard_mutex make sense at all?

Taking two locks for the common exit case seems disgusting. What do
the separate locks buy us?

- could we possible avoid the lock(s) entirely for the
single-threaded case? The fact that ptrace wants to serialize makes me say "maybe we can't avoid it", but I thought I'd ask. Even if we do need/want two separate locks, do we really want to take them both for the case that shouldn't really need even a single one?

Hmm? Simplifying the locking rules is always a good idea, and I think this seems to make some of it more straightforward, but at the same time I shudder when I look at some of the patches in the series that nest locking three locks deep. Ugh.

Linus

#57 lore raw

Re: [PATCH UPDATED 03/10] threadgroup: extend threadgroup_lock() to cover exit and exec

In reply to a message not in the archive
Quoted 153 lines · Tejun Heo

threadgroup_lock() protected only protected against new addition to the threadgroup, which was inherently somewhat incomplete and problematic for its only user cgroup. On-going migration could race against exec and exit leading to interesting problems - the symmetry between various attach methods, task exiting during method execution, ->exit() racing against attach methods, migrating task switching basic properties during exec and so on.

This patch extends threadgroup_lock() such that it protects against all three threadgroup altering operations - fork, exit and exec. For exit, threadgroup_change_begin/end() calls are added to exit path. For exec, threadgroup_[un]lock() are updated to also grab and release cred_guard_mutex.

With this change, threadgroup_lock() guarantees that the target threadgroup will remain stable - no new task will be added, no new PF_EXITING will be set and exec won't happen.

The next patch will update cgroup so that it can take full advantage of this change.

-v2: beefed up comment as suggested by Frederic.

Cc Oleg Nesterov <[email protected]>
Cc Andrew Morton <akpm-de/[email protected]>
Cc Paul Menage <[email protected]>
Cc Frederic Weisbecker <[email protected]>
Cc Linus Torvalds <torvalds-de/[email protected]>

---

Linus, this is something being scheduled for the next merge window. It extends threadgroup locking which cgroup used to do to exclude only fork path so that it includes exit and exec paths. threadgroup locking is used by cgroup to implement process-scope cgroup migration.

Migration happens in multiple steps, at each of which the matching method of each cgroup plugin is called. When a whole process is being migrated, methods being called at those different steps expect to see consistent image of the thread group.

cgroup currently only locks out addition of new tasks into the thread group and holds extra ref to member tasks. This mandates all cgroup plugins to deal with tasks being torn down and exec morphing the threadgroup. This patch extends the scope of threadgroup locking such that the thread group is guaranteed to be stable (no new task, tasks are either live or dead and no exec morphing) while locked.

This is part of changes to clean up cgroup methods and iron out corner case fuzziness and should make difficult-to-reproduce race conditions less likely and cgroup plugins easier to implement and verify.

The synchronization is strictly per-threadgroup and goes away if cgroup is not configured.

The whole series is avilable at

http://thread.gmane.org/gmane.linux.kernel.containers/21716
git://git.kernel.org/pub/scm/linux/kernel/git/tj/cgroup.git for-3.3

Thank you.

 include/linux/sched.h |   47 +++++++++++++++++++++++++++++++++++++++++------
 kernel/exit.c         |   19 +++++++++++++++----
 2 files changed, 56 insertions(+), 10 deletions(-)
include/linux/sched.h +41 -6
index c5acbce..481c5ed 100644
@@ -635,11 +635,12 @@ struct signal_struct {
 #endif
 #ifdef CONFIG_CGROUPS
 	/*
-	 * The group_rwsem prevents threads from forking with
-	 * CLONE_THREAD while held for writing. Use this for fork-sensitive
-	 * threadgroup-wide operations. It's taken for reading in fork.c in
-	 * copy_process().
-	 * Currently only needed write-side by cgroups.
+	 * group_rwsem prevents new tasks from entering the threadgroup and
+	 * member tasks from exiting.  fork and exit paths are protected
+	 * with this rwsem using threadgroup_change_begin/end().  Users
+	 * which require threadgroup to remain stable should use
+	 * threadgroup_[un]lock() which also takes care of exec path.
+	 * Currently, cgroup is the only user.
 	 */
 	struct rw_semaphore group_rwsem;
 #endif
@@ -2374,7 +2375,6 @@ static inline void unlock_task_sighand(struct task_struct *tsk,
 	spin_unlock_irqrestore(&tsk->sighand->siglock, *flags);
 }
 
-/* See the declaration of group_rwsem in signal_struct. */
 #ifdef CONFIG_CGROUPS
 static inline void threadgroup_change_begin(struct task_struct *tsk)
 {
@@ -2384,13 +2384,48 @@ static inline void threadgroup_change_end(struct task_struct *tsk)
 {
 	up_read(&tsk->signal->group_rwsem);
 }
+
+/**
+ * threadgroup_lock - lock threadgroup
+ * @tsk: member task of the threadgroup to lock
+ *
+ * Lock the threadgroup @tsk belongs to.  No new task is allowed to enter
+ * and member tasks aren't allowed to exit (as indicated by PF_EXITING) or
+ * perform exec.  This is useful for cases where the threadgroup needs to
+ * stay stable across blockable operations.
+ *
+ * fork and exit explicitly call threadgroup_change_{begin|end}() for
+ * synchronization.  This excludes most of do_exit() to ensure that, while
+ * locked, tasks belonging to a locked group are not in the process of
+ * deconstruction - they're either alive or dead.
+ *
+ * During exec, a task goes and puts its thread group through unusual
+ * changes.  After de-threading, exclusive access is assumed to resources
+ * which are usually shared by tasks in the same group - e.g. sighand may
+ * be replaced with a new one.  Also, the exec'ing task takes over group
+ * leader role including its pid.  Exclude these changes while locked by
+ * grabbing cred_guard_mutex which is used to synchronize exec path.
+ */
 static inline void threadgroup_lock(struct task_struct *tsk)
 {
+	/*
+	 * exec uses exit for de-threading nesting group_rwsem inside
+	 * cred_guard_mutex. Grab cred_guard_mutex first.
+	 */
+	mutex_lock(&tsk->signal->cred_guard_mutex);
 	down_write(&tsk->signal->group_rwsem);
 }
+
+/**
+ * threadgroup_unlock - unlock threadgroup
+ * @tsk: member task of the threadgroup to unlock
+ *
+ * Reverse threadgroup_lock().
+ */
 static inline void threadgroup_unlock(struct task_struct *tsk)
 {
 	up_write(&tsk->signal->group_rwsem);
+	mutex_unlock(&tsk->signal->cred_guard_mutex);
 }
 #else
 static inline void threadgroup_change_begin(struct task_struct *tsk) {}
kernel/exit.c +0 -0
index d0b7d98..b2cb562 100644
@@ -936,6 +936,14 @@ NORET_TYPE void do_exit(long code)
 		schedule();

kernel/exit.c NORET_TYPE void do_exit(long code) · line 946

Quoted 10 lines
kernel/exit.c +8 -0
 	}
 
+	/*
+	 * @tsk's threadgroup is going through changes - lock out users
+	 * which expect stable threadgroup.  Do this before actually
+	 * starting tearing down @tsk so that locked threadgroup has either
+	 * alive or dead tasks, not something inbetween.
+	 */
+	threadgroup_change_begin(tsk);
+

I still wonder why there is a so big coverage of this lock. I mean why is it called right before exit_irq_thread() and released so late. All we want is to lock cgroup_exit() I think, after which tasks can't be migrated.

Thanks.

#58 lore raw

Re: [PATCH UPDATED 03/10] threadgroup: extend threadgroup_lock() to cover exit and exec

In reply to Tejun Heo
Quoted 153 lines · Tejun Heo

threadgroup_lock() protected only protected against new addition to the threadgroup, which was inherently somewhat incomplete and problematic for its only user cgroup. On-going migration could race against exec and exit leading to interesting problems - the symmetry between various attach methods, task exiting during method execution, ->exit() racing against attach methods, migrating task switching basic properties during exec and so on.

This patch extends threadgroup_lock() such that it protects against all three threadgroup altering operations - fork, exit and exec. For exit, threadgroup_change_begin/end() calls are added to exit path. For exec, threadgroup_[un]lock() are updated to also grab and release cred_guard_mutex.

With this change, threadgroup_lock() guarantees that the target threadgroup will remain stable - no new task will be added, no new PF_EXITING will be set and exec won't happen.

The next patch will update cgroup so that it can take full advantage of this change.

-v2: beefed up comment as suggested by Frederic.

Cc Oleg Nesterov <[email protected]>
Cc Andrew Morton <[email protected]>
Cc Paul Menage <[email protected]>
Cc Frederic Weisbecker <[email protected]>
Cc Linus Torvalds <[email protected]>

---

Linus, this is something being scheduled for the next merge window. It extends threadgroup locking which cgroup used to do to exclude only fork path so that it includes exit and exec paths. threadgroup locking is used by cgroup to implement process-scope cgroup migration.

Migration happens in multiple steps, at each of which the matching method of each cgroup plugin is called. When a whole process is being migrated, methods being called at those different steps expect to see consistent image of the thread group.

cgroup currently only locks out addition of new tasks into the thread group and holds extra ref to member tasks. This mandates all cgroup plugins to deal with tasks being torn down and exec morphing the threadgroup. This patch extends the scope of threadgroup locking such that the thread group is guaranteed to be stable (no new task, tasks are either live or dead and no exec morphing) while locked.

This is part of changes to clean up cgroup methods and iron out corner case fuzziness and should make difficult-to-reproduce race conditions less likely and cgroup plugins easier to implement and verify.

The synchronization is strictly per-threadgroup and goes away if cgroup is not configured.

The whole series is avilable at

http://thread.gmane.org/gmane.linux.kernel.containers/21716
git://git.kernel.org/pub/scm/linux/kernel/git/tj/cgroup.git for-3.3

Thank you.

 include/linux/sched.h |   47 +++++++++++++++++++++++++++++++++++++++++------
 kernel/exit.c         |   19 +++++++++++++++----
 2 files changed, 56 insertions(+), 10 deletions(-)
include/linux/sched.h +41 -6
index c5acbce..481c5ed 100644
@@ -635,11 +635,12 @@ struct signal_struct {
 #endif
 #ifdef CONFIG_CGROUPS
 	/*
-	 * The group_rwsem prevents threads from forking with
-	 * CLONE_THREAD while held for writing. Use this for fork-sensitive
-	 * threadgroup-wide operations. It's taken for reading in fork.c in
-	 * copy_process().
-	 * Currently only needed write-side by cgroups.
+	 * group_rwsem prevents new tasks from entering the threadgroup and
+	 * member tasks from exiting.  fork and exit paths are protected
+	 * with this rwsem using threadgroup_change_begin/end().  Users
+	 * which require threadgroup to remain stable should use
+	 * threadgroup_[un]lock() which also takes care of exec path.
+	 * Currently, cgroup is the only user.
 	 */
 	struct rw_semaphore group_rwsem;
 #endif
@@ -2374,7 +2375,6 @@ static inline void unlock_task_sighand(struct task_struct *tsk,
 	spin_unlock_irqrestore(&tsk->sighand->siglock, *flags);
 }
 
-/* See the declaration of group_rwsem in signal_struct. */
 #ifdef CONFIG_CGROUPS
 static inline void threadgroup_change_begin(struct task_struct *tsk)
 {
@@ -2384,13 +2384,48 @@ static inline void threadgroup_change_end(struct task_struct *tsk)
 {
 	up_read(&tsk->signal->group_rwsem);
 }
+
+/**
+ * threadgroup_lock - lock threadgroup
+ * @tsk: member task of the threadgroup to lock
+ *
+ * Lock the threadgroup @tsk belongs to.  No new task is allowed to enter
+ * and member tasks aren't allowed to exit (as indicated by PF_EXITING) or
+ * perform exec.  This is useful for cases where the threadgroup needs to
+ * stay stable across blockable operations.
+ *
+ * fork and exit explicitly call threadgroup_change_{begin|end}() for
+ * synchronization.  This excludes most of do_exit() to ensure that, while
+ * locked, tasks belonging to a locked group are not in the process of
+ * deconstruction - they're either alive or dead.
+ *
+ * During exec, a task goes and puts its thread group through unusual
+ * changes.  After de-threading, exclusive access is assumed to resources
+ * which are usually shared by tasks in the same group - e.g. sighand may
+ * be replaced with a new one.  Also, the exec'ing task takes over group
+ * leader role including its pid.  Exclude these changes while locked by
+ * grabbing cred_guard_mutex which is used to synchronize exec path.
+ */
 static inline void threadgroup_lock(struct task_struct *tsk)
 {
+	/*
+	 * exec uses exit for de-threading nesting group_rwsem inside
+	 * cred_guard_mutex. Grab cred_guard_mutex first.
+	 */
+	mutex_lock(&tsk->signal->cred_guard_mutex);
 	down_write(&tsk->signal->group_rwsem);
 }
+
+/**
+ * threadgroup_unlock - unlock threadgroup
+ * @tsk: member task of the threadgroup to unlock
+ *
+ * Reverse threadgroup_lock().
+ */
 static inline void threadgroup_unlock(struct task_struct *tsk)
 {
 	up_write(&tsk->signal->group_rwsem);
+	mutex_unlock(&tsk->signal->cred_guard_mutex);
 }
 #else
 static inline void threadgroup_change_begin(struct task_struct *tsk) {}
kernel/exit.c +0 -0
index d0b7d98..b2cb562 100644
@@ -936,6 +936,14 @@ NORET_TYPE void do_exit(long code)
 		schedule();

kernel/exit.c NORET_TYPE void do_exit(long code) · line 946

Quoted 10 lines
kernel/exit.c +8 -0
 	}
 
+	/*
+	 * @tsk's threadgroup is going through changes - lock out users
+	 * which expect stable threadgroup.  Do this before actually
+	 * starting tearing down @tsk so that locked threadgroup has either
+	 * alive or dead tasks, not something inbetween.
+	 */
+	threadgroup_change_begin(tsk);
+

I still wonder why there is a so big coverage of this lock. I mean why is it called right before exit_irq_thread() and released so late. All we want is to lock cgroup_exit() I think, after which tasks can't be migrated.

Thanks.

author maintainer
#59 lore raw

Re: [PATCH UPDATED 03/10] threadgroup: extend threadgroup_lock() to cover exit and exec

Hello, Frederic.

Quoted 5 lines · Frederic Weisbecker
Quoted 5 lines
kernel/exit.c +5 -0
+	/*
+	 * @tsk's threadgroup is going through changes - lock out users
+	 * which expect stable threadgroup.  Do this before actually
+	 * starting tearing down @tsk so that locked threadgroup has either
+	 * alive or dead tasks, not something inbetween.
Quoted 8 lines

kernel/exit.c NORET_TYPE void do_exit(long code) · line 946

Quoted 3 lines
kernel/exit.c +3 -0
+	 */
+	threadgroup_change_begin(tsk);
+

I still wonder why there is a so big coverage of this lock. I mean why is it called right before exit_irq_thread() and released so late. All we want is to lock cgroup_exit() I think, after which tasks can't be migrated.

That way, cgroup plugins never see tasks in the process of deconstruction. Its ->mm, ->sighand, ->signal and so on either don't exist or continue to exist across all cgroup callbacks. It's simpler and safer (especially as bugs in this area would only be visible when migration and exit race), and if we're gonna synchronize exit path at all, there isn't anything to lose by excluding the whole thing.

Thanks.

Signature · 1 line
tejun
#60 lore raw

Re: [PATCH UPDATED 03/10] threadgroup: extend threadgroup_lock() to cover exit and exec

Quoted 17 lines · Tejun Heo

Hello, Frederic.

Quoted 5 lines · Frederic Weisbecker
Quoted 5 lines
kernel/exit.c +5 -0
+	/*
+	 * @tsk's threadgroup is going through changes - lock out users
+	 * which expect stable threadgroup.  Do this before actually
+	 * starting tearing down @tsk so that locked threadgroup has either
+	 * alive or dead tasks, not something inbetween.
Quoted 8 lines

kernel/exit.c NORET_TYPE void do_exit(long code) · line 946

Quoted 3 lines
kernel/exit.c +3 -0
+	 */
+	threadgroup_change_begin(tsk);
+

I still wonder why there is a so big coverage of this lock. I mean why is it called right before exit_irq_thread() and released so late. All we want is to lock cgroup_exit() I think, after which tasks can't be migrated.

Quoted 6 lines

That way, cgroup plugins never see tasks in the process of deconstruction. Its ->mm, ->sighand, ->signal and so on either don't exist or continue to exist across all cgroup callbacks. It's simpler and safer (especially as bugs in this area would only be visible when migration and exit race), and if we're gonna synchronize exit path at all, there isn't anything to lose by excluding the whole thing.

Fine.

But I don't think it's very useful to protect against irq_exit_thread(), what happens there is purely of internal irq interest.

Then right after, PF_EXITING is set before any interesting change. Isn't it possible to simply lock this flag setting? IIRC, as soon as the PF_EXITING flag is set, you ignore the task for attachment.

author maintainer
#61 lore raw

Re: [PATCH UPDATED 03/10] threadgroup: extend threadgroup_lock() to cover exit and exec

Hello, Frederic.

Quoted 6 lines · Frederic Weisbecker

But I don't think it's very useful to protect against irq_exit_thread(), what happens there is purely of internal irq interest.

Then right after, PF_EXITING is set before any interesting change. Isn't it possible to simply lock this flag setting? IIRC, as soon as the PF_EXITING flag is set, you ignore the task for attachment.

I think that's technically possible but it does introduce another class of tasks - the dying ones. e.g. If a task has PF_EXITING set and the containing process is migrating, we'll have to migrate all tasks but the dying one and cgroup ->exit callbacks can be called on the lonely task after the migration is complete. It's kinda messy and if someone makes a wrong assumption there, the bug is gonna be even more difficult to reproduce / track down than now. Yes, smaller scope locking is nicer but I would like to avoid api weirdities like that.

Thanks.

Signature · 1 line
tejun
#62 lore raw

Re: [PATCH UPDATED 03/10] threadgroup: extend threadgroup_lock() to cover exit and exec

Quoted 10 lines · Tejun Heo

Hello, Frederic.

Quoted 6 lines · Frederic Weisbecker

But I don't think it's very useful to protect against irq_exit_thread(), what happens there is purely of internal irq interest.

Then right after, PF_EXITING is set before any interesting change. Isn't it possible to simply lock this flag setting? IIRC, as soon as the PF_EXITING flag is set, you ignore the task for attachment.

Quoted 8 lines

I think that's technically possible but it does introduce another class of tasks - the dying ones. e.g. If a task has PF_EXITING set and the containing process is migrating, we'll have to migrate all tasks but the dying one and cgroup ->exit callbacks can be called on the lonely task after the migration is complete. It's kinda messy and if someone makes a wrong assumption there, the bug is gonna be even more difficult to reproduce / track down than now. Yes, smaller scope locking is nicer but I would like to avoid api weirdities like that.

I don't understand what you mean. On your patches, you only process tasks that don't have PF_EXITING, ie: you don't include these in the flex array on cgroup_attach_proc(). So that still applies in my proposal.

Quoted 1 line

From the exit path we would have:

	exit_signal() {
		lock_threadgroup_change(task);
		task->flags |= PF_EXITING;
		lock_threadgroup(task);
	}

	exit all the rest: mm, etc...

Then from cgroup_attach_proc():

	lock_threadgroup(task);
	for_each_thread(task) {
		if (!(task->flags & PF_EXITING))
			include in flex array
	}

Am I forgetting something?

author maintainer
#63 lore raw

Re: [PATCH UPDATED 03/10] threadgroup: extend threadgroup_lock() to cover exit and exec

Hello, Frederic.

Quoted 16 lines · Frederic Weisbecker

I don't understand what you mean. On your patches, you only process tasks that don't have PF_EXITING, ie: you don't include these in the flex array on cgroup_attach_proc(). So that still applies in my proposal.

From the exit path we would have:

	exit_signal() {
		lock_threadgroup_change(task);
		task->flags |= PF_EXITING;
		lock_threadgroup(task);
	}

	exit all the rest: mm, etc...

Then from cgroup_attach_proc():

Quoted 7 lines
	lock_threadgroup(task);
	for_each_thread(task) {
		if (!(task->flags & PF_EXITING))
			include in flex array
	}

Am I forgetting something?

The point I was trying to make was that doing the above would make ->exit() called on dangling task of a threadgroup in rare cases. ie. With the proposed change, after a threadgroup migration, all tasks in the threadgroup is in the new cgroup. No method will be called on the old cgroup for any of the member task. With the above change, process migration would leave out dying tasks and ->exit() can be called with the old cgroup.

Hmm... that said, it probably doesn't matter all that much either way. I'll update the patchset and repost.

Thanks.

Signature · 1 line
tejun
author maintainer
#64 lore raw

PATCH 3/10 threadgroup: extend threadgroup_lock() to cover exit and exec

(Note for Linus at the bottom)

threadgroup_lock() protected only protected against new addition to the threadgroup, which was inherently somewhat incomplete and problematic for its only user cgroup. On-going migration could race against exec and exit leading to interesting problems - the symmetry between various attach methods, task exiting during method execution, ->exit() racing against attach methods, migrating task switching basic properties during exec and so on.

This patch extends threadgroup_lock() such that it protects against all three threadgroup altering operations - fork, exit and exec. For exit, threadgroup_change_begin/end() calls are added to exit_signals around assertion of PF_EXITING. For exec, threadgroup_[un]lock() are updated to also grab and release cred_guard_mutex.

With this change, threadgroup_lock() guarantees that the target threadgroup will remain stable - no new task will be added, no new PF_EXITING will be set and exec won't happen.

The next patch will update cgroup so that it can take full advantage of this change.

-v2: beefed up comment as suggested by Frederic.

-v3: narrowed scope of protection in exit path as suggested by
Frederic.

Cc Oleg Nesterov <[email protected]>
Cc Andrew Morton <[email protected]>
Cc Paul Menage <[email protected]>
Cc Frederic Weisbecker <[email protected]>
Cc Linus Torvalds <[email protected]>

---
Okay, narrowed exit path protection down to setting of PF_EXITING itself. ->exit() on dangling tasks is a bit weird but I don't think it's too bad. Frederic, are you okay with this version?

Linus, if Frederic is okay with it, I'm gonna rebase the series on top of freezer changes in pm tree to avoid conflicts in cgroup_freezer, which sits between cgroup and freezer, both of which are going through non-trivial changes, push the branch to linux-next and put pending cgroup patches on top. Please scream if you're mighty unhappy with it or have a better idea.

Thank you.

 include/linux/sched.h |   47 +++++++++++++++++++++++++++++++++++++++++------
 kernel/signal.c       |   10 ++++++++++
 2 files changed, 51 insertions(+), 6 deletions(-)
include/linux/sched.h +41 -6
Index: work/include/linux/sched.h
===================================================================
@@ -635,11 +635,13 @@ struct signal_struct {
 #endif
 #ifdef CONFIG_CGROUPS
 	/*
-	 * The group_rwsem prevents threads from forking with
-	 * CLONE_THREAD while held for writing. Use this for fork-sensitive
-	 * threadgroup-wide operations. It's taken for reading in fork.c in
-	 * copy_process().
-	 * Currently only needed write-side by cgroups.
+	 * group_rwsem prevents new tasks from entering the threadgroup and
+	 * member tasks from exiting,a more specifically, setting of
+	 * PF_EXITING.  fork and exit paths are protected with this rwsem
+	 * using threadgroup_change_begin/end().  Users which require
+	 * threadgroup to remain stable should use threadgroup_[un]lock()
+	 * which also takes care of exec path.  Currently, cgroup is the
+	 * only user.
 	 */
 	struct rw_semaphore group_rwsem;
 #endif
@@ -2374,7 +2376,6 @@ static inline void unlock_task_sighand(s
 	spin_unlock_irqrestore(&tsk->sighand->siglock, *flags);
 }
 
-/* See the declaration of group_rwsem in signal_struct. */
 #ifdef CONFIG_CGROUPS
 static inline void threadgroup_change_begin(struct task_struct *tsk)
 {
@@ -2384,13 +2385,47 @@ static inline void threadgroup_change_en
 {
 	up_read(&tsk->signal->group_rwsem);
 }
+
+/**
+ * threadgroup_lock - lock threadgroup
+ * @tsk: member task of the threadgroup to lock
+ *
+ * Lock the threadgroup @tsk belongs to.  No new task is allowed to enter
+ * and member tasks aren't allowed to exit (as indicated by PF_EXITING) or
+ * perform exec.  This is useful for cases where the threadgroup needs to
+ * stay stable across blockable operations.
+ *
+ * fork and exit paths explicitly call threadgroup_change_{begin|end}() for
+ * synchronization.  While held, no new task will be added to threadgroup
+ * and no existing live task will have its PF_EXITING set.
+ *
+ * During exec, a task goes and puts its thread group through unusual
+ * changes.  After de-threading, exclusive access is assumed to resources
+ * which are usually shared by tasks in the same group - e.g. sighand may
+ * be replaced with a new one.  Also, the exec'ing task takes over group
+ * leader role including its pid.  Exclude these changes while locked by
+ * grabbing cred_guard_mutex which is used to synchronize exec path.
+ */
 static inline void threadgroup_lock(struct task_struct *tsk)
 {
+	/*
+	 * exec uses exit for de-threading nesting group_rwsem inside
+	 * cred_guard_mutex. Grab cred_guard_mutex first.
+	 */
+	mutex_lock(&tsk->signal->cred_guard_mutex);
 	down_write(&tsk->signal->group_rwsem);
 }
+
+/**
+ * threadgroup_unlock - unlock threadgroup
+ * @tsk: member task of the threadgroup to unlock
+ *
+ * Reverse threadgroup_lock().
+ */
 static inline void threadgroup_unlock(struct task_struct *tsk)
 {
 	up_write(&tsk->signal->group_rwsem);
+	mutex_unlock(&tsk->signal->cred_guard_mutex);
 }
 #else
 static inline void threadgroup_change_begin(struct task_struct *tsk) {}
kernel/signal.c +10 -0
Index: work/kernel/signal.c
===================================================================
@@ -2359,8 +2359,15 @@ void exit_signals(struct task_struct *ts
 	int group_stop = 0;
 	sigset_t unblocked;
 
+	/*
+	 * @tsk is about to have PF_EXITING set - lock out users which
+	 * expect stable threadgroup.
+	 */
+	threadgroup_change_begin(tsk);
+
 	if (thread_group_empty(tsk) || signal_group_exit(tsk->signal)) {
 		tsk->flags |= PF_EXITING;
+		threadgroup_change_end(tsk);
 		return;
 	}
 
@@ -2370,6 +2377,9 @@ void exit_signals(struct task_struct *ts
 	 * see wants_signal(), do_signal_stop().
 	 */
 	tsk->flags |= PF_EXITING;
+
+	threadgroup_change_end(tsk);
+
 	if (!signal_pending(tsk))
 		goto out;
 
Acked-by on v1 3/10
#65 lore raw

Re: [PATCH UPDATED AGAIN 03/10] threadgroup: extend threadgroup_lock() to cover exit and exec

Quoted 36 lines · Tejun Heo

(Note for Linus at the bottom)

threadgroup_lock() protected only protected against new addition to the threadgroup, which was inherently somewhat incomplete and problematic for its only user cgroup. On-going migration could race against exec and exit leading to interesting problems - the symmetry between various attach methods, task exiting during method execution, ->exit() racing against attach methods, migrating task switching basic properties during exec and so on.

This patch extends threadgroup_lock() such that it protects against all three threadgroup altering operations - fork, exit and exec. For exit, threadgroup_change_begin/end() calls are added to exit_signals around assertion of PF_EXITING. For exec, threadgroup_[un]lock() are updated to also grab and release cred_guard_mutex.

With this change, threadgroup_lock() guarantees that the target threadgroup will remain stable - no new task will be added, no new PF_EXITING will be set and exec won't happen.

The next patch will update cgroup so that it can take full advantage of this change.

(quoted diff) +0 -2
-v2: beefed up comment as suggested by Frederic.
 
-v3: narrowed scope of protection in exit path as suggested by
     Frederic.
Cc Oleg Nesterov <[email protected]>
Cc Andrew Morton <akpm-de/[email protected]>
Cc Paul Menage <[email protected]>
Cc Frederic Weisbecker <[email protected]>
Cc Linus Torvalds <torvalds-de/[email protected]>
Quoted 4 lines

---
Okay, narrowed exit path protection down to setting of PF_EXITING itself. ->exit() on dangling tasks is a bit weird but I don't think it's too bad. Frederic, are you okay with this version?

Yeah that new scheme that only protects PF_EXITING may look a bit strange. But I think we are fine. With rcu list traversal, it should be safe even if a group member is concurrently dropped from the list (in that case all we check if its PF_EXITING then we give up). And we may have a concurrent ->exit() but that should be fine too.

Thanks!

Quoted 9 lines

Linus, if Frederic is okay with it, I'm gonna rebase the series on top of freezer changes in pm tree to avoid conflicts in cgroup_freezer, which sits between cgroup and freezer, both of which are going through non-trivial changes, push the branch to linux-next and put pending cgroup patches on top. Please scream if you're mighty unhappy with it or have a better idea.

Thank you.

#66 lore raw

Re: [PATCH UPDATED AGAIN 03/10] threadgroup: extend threadgroup_lock() to cover exit and exec

Hello,

Quoted 10 lines · Frederic Weisbecker
Quoted 3 lines

Okay, narrowed exit path protection down to setting of PF_EXITING itself. ->exit() on dangling tasks is a bit weird but I don't think it's too bad. Frederic, are you okay with this version?

Yeah that new scheme that only protects PF_EXITING may look a bit strange. But I think we are fine. With rcu list traversal, it should be safe even if a group member is concurrently dropped from the list (in that case all we check if its PF_EXITING then we give up). And we may have a concurrent ->exit() but that should be fine too.

Quoted 3 lines

Thanks!

Awesome, adding Acked-by, putting it on top of pm-freezer and pushing it out to linux-next.

Thank you.

Signature · 1 line
tejun
author maintainer
#67 lore raw

Re: [PATCH UPDATED AGAIN 03/10] threadgroup: extend threadgroup_lock() to cover exit and exec

In reply to Frederic Weisbecker

Hello,

Quoted 10 lines · Frederic Weisbecker
Quoted 3 lines

Okay, narrowed exit path protection down to setting of PF_EXITING itself. ->exit() on dangling tasks is a bit weird but I don't think it's too bad. Frederic, are you okay with this version?

Yeah that new scheme that only protects PF_EXITING may look a bit strange. But I think we are fine. With rcu list traversal, it should be safe even if a group member is concurrently dropped from the list (in that case all we check if its PF_EXITING then we give up). And we may have a concurrent ->exit() but that should be fine too.

Quoted 3 lines

Thanks!

Awesome, adding Acked-by, putting it on top of pm-freezer and pushing it out to linux-next.

Thank you.

Signature · 1 line
tejun
author maintainer
#68 lore raw

Re: [PATCH UPDATED AGAIN 03/10] threadgroup: extend threadgroup_lock() to cover exit and exec

In reply to Frederic Weisbecker

Hello,

Quoted 10 lines · Frederic Weisbecker
Quoted 3 lines

Okay, narrowed exit path protection down to setting of PF_EXITING itself. ->exit() on dangling tasks is a bit weird but I don't think it's too bad. Frederic, are you okay with this version?

Yeah that new scheme that only protects PF_EXITING may look a bit strange. But I think we are fine. With rcu list traversal, it should be safe even if a group member is concurrently dropped from the list (in that case all we check if its PF_EXITING then we give up). And we may have a concurrent ->exit() but that should be fine too.

Quoted 3 lines

Thanks!

Awesome, adding Acked-by, putting it on top of pm-freezer and pushing it out to linux-next.

Thank you.

Signature · 1 line
tejun
author maintainer
#69 lore raw

Re: [PATCH UPDATED AGAIN 03/10] threadgroup: extend threadgroup_lock() to cover exit and exec

Updated patchset published at

git://git.kernel.org/pub/scm/linux/kernel/git/tj/cgroup.git for-3.3

and merged branch also pushed out to for-next.

Thanks.

Signature · 1 line
tejun
author maintainer
#70 lore raw

Re: [PATCH UPDATED AGAIN 03/10] threadgroup: extend threadgroup_lock() to cover exit and exec

In reply to Tejun Heo

Updated patchset published at

git://git.kernel.org/pub/scm/linux/kernel/git/tj/cgroup.git for-3.3

and merged branch also pushed out to for-next.

Thanks.

Signature · 1 line
tejun
Acked-by on v1 3/10
#71 lore raw

Re: [PATCH UPDATED AGAIN 03/10] threadgroup: extend threadgroup_lock() to cover exit and exec

In reply to Tejun Heo
Quoted 36 lines · Tejun Heo

(Note for Linus at the bottom)

threadgroup_lock() protected only protected against new addition to the threadgroup, which was inherently somewhat incomplete and problematic for its only user cgroup. On-going migration could race against exec and exit leading to interesting problems - the symmetry between various attach methods, task exiting during method execution, ->exit() racing against attach methods, migrating task switching basic properties during exec and so on.

This patch extends threadgroup_lock() such that it protects against all three threadgroup altering operations - fork, exit and exec. For exit, threadgroup_change_begin/end() calls are added to exit_signals around assertion of PF_EXITING. For exec, threadgroup_[un]lock() are updated to also grab and release cred_guard_mutex.

With this change, threadgroup_lock() guarantees that the target threadgroup will remain stable - no new task will be added, no new PF_EXITING will be set and exec won't happen.

The next patch will update cgroup so that it can take full advantage of this change.

(quoted diff) +0 -2
-v2: beefed up comment as suggested by Frederic.
 
-v3: narrowed scope of protection in exit path as suggested by
     Frederic.
Cc Oleg Nesterov <[email protected]>
Cc Andrew Morton <[email protected]>
Cc Paul Menage <[email protected]>
Cc Frederic Weisbecker <[email protected]>
Cc Linus Torvalds <[email protected]>
Quoted 4 lines

---
Okay, narrowed exit path protection down to setting of PF_EXITING itself. ->exit() on dangling tasks is a bit weird but I don't think it's too bad. Frederic, are you okay with this version?

Yeah that new scheme that only protects PF_EXITING may look a bit strange. But I think we are fine. With rcu list traversal, it should be safe even if a group member is concurrently dropped from the list (in that case all we check if its PF_EXITING then we give up). And we may have a concurrent ->exit() but that should be fine too.

Thanks!

Quoted 9 lines

Linus, if Frederic is okay with it, I'm gonna rebase the series on top of freezer changes in pm tree to avoid conflicts in cgroup_freezer, which sits between cgroup and freezer, both of which are going through non-trivial changes, push the branch to linux-next and put pending cgroup patches on top. Please scream if you're mighty unhappy with it or have a better idea.

Thank you.

A message that is not in the archive (sent off-list or not yet received): [email protected]

#73 lore raw

PATCH 3/10 threadgroup: extend threadgroup_lock() to cover exit and exec

(Note for Linus at the bottom)

threadgroup_lock() protected only protected against new addition to the threadgroup, which was inherently somewhat incomplete and problematic for its only user cgroup. On-going migration could race against exec and exit leading to interesting problems - the symmetry between various attach methods, task exiting during method execution, ->exit() racing against attach methods, migrating task switching basic properties during exec and so on.

This patch extends threadgroup_lock() such that it protects against all three threadgroup altering operations - fork, exit and exec. For exit, threadgroup_change_begin/end() calls are added to exit_signals around assertion of PF_EXITING. For exec, threadgroup_[un]lock() are updated to also grab and release cred_guard_mutex.

With this change, threadgroup_lock() guarantees that the target threadgroup will remain stable - no new task will be added, no new PF_EXITING will be set and exec won't happen.

The next patch will update cgroup so that it can take full advantage of this change.

-v2: beefed up comment as suggested by Frederic.

-v3: narrowed scope of protection in exit path as suggested by
Frederic.

Cc Oleg Nesterov <[email protected]>
Cc Andrew Morton <akpm-de/[email protected]>
Cc Paul Menage <[email protected]>
Cc Frederic Weisbecker <[email protected]>
Cc Linus Torvalds <torvalds-de/[email protected]>

---
Okay, narrowed exit path protection down to setting of PF_EXITING itself. ->exit() on dangling tasks is a bit weird but I don't think it's too bad. Frederic, are you okay with this version?

Linus, if Frederic is okay with it, I'm gonna rebase the series on top of freezer changes in pm tree to avoid conflicts in cgroup_freezer, which sits between cgroup and freezer, both of which are going through non-trivial changes, push the branch to linux-next and put pending cgroup patches on top. Please scream if you're mighty unhappy with it or have a better idea.

Thank you.

 include/linux/sched.h |   47 +++++++++++++++++++++++++++++++++++++++++------
 kernel/signal.c       |   10 ++++++++++
 2 files changed, 51 insertions(+), 6 deletions(-)
include/linux/sched.h +41 -6
Index: work/include/linux/sched.h
===================================================================
@@ -635,11 +635,13 @@ struct signal_struct {
 #endif
 #ifdef CONFIG_CGROUPS
 	/*
-	 * The group_rwsem prevents threads from forking with
-	 * CLONE_THREAD while held for writing. Use this for fork-sensitive
-	 * threadgroup-wide operations. It's taken for reading in fork.c in
-	 * copy_process().
-	 * Currently only needed write-side by cgroups.
+	 * group_rwsem prevents new tasks from entering the threadgroup and
+	 * member tasks from exiting,a more specifically, setting of
+	 * PF_EXITING.  fork and exit paths are protected with this rwsem
+	 * using threadgroup_change_begin/end().  Users which require
+	 * threadgroup to remain stable should use threadgroup_[un]lock()
+	 * which also takes care of exec path.  Currently, cgroup is the
+	 * only user.
 	 */
 	struct rw_semaphore group_rwsem;
 #endif
@@ -2374,7 +2376,6 @@ static inline void unlock_task_sighand(s
 	spin_unlock_irqrestore(&tsk->sighand->siglock, *flags);
 }
 
-/* See the declaration of group_rwsem in signal_struct. */
 #ifdef CONFIG_CGROUPS
 static inline void threadgroup_change_begin(struct task_struct *tsk)
 {
@@ -2384,13 +2385,47 @@ static inline void threadgroup_change_en
 {
 	up_read(&tsk->signal->group_rwsem);
 }
+
+/**
+ * threadgroup_lock - lock threadgroup
+ * @tsk: member task of the threadgroup to lock
+ *
+ * Lock the threadgroup @tsk belongs to.  No new task is allowed to enter
+ * and member tasks aren't allowed to exit (as indicated by PF_EXITING) or
+ * perform exec.  This is useful for cases where the threadgroup needs to
+ * stay stable across blockable operations.
+ *
+ * fork and exit paths explicitly call threadgroup_change_{begin|end}() for
+ * synchronization.  While held, no new task will be added to threadgroup
+ * and no existing live task will have its PF_EXITING set.
+ *
+ * During exec, a task goes and puts its thread group through unusual
+ * changes.  After de-threading, exclusive access is assumed to resources
+ * which are usually shared by tasks in the same group - e.g. sighand may
+ * be replaced with a new one.  Also, the exec'ing task takes over group
+ * leader role including its pid.  Exclude these changes while locked by
+ * grabbing cred_guard_mutex which is used to synchronize exec path.
+ */
 static inline void threadgroup_lock(struct task_struct *tsk)
 {
+	/*
+	 * exec uses exit for de-threading nesting group_rwsem inside
+	 * cred_guard_mutex. Grab cred_guard_mutex first.
+	 */
+	mutex_lock(&tsk->signal->cred_guard_mutex);
 	down_write(&tsk->signal->group_rwsem);
 }
+
+/**
+ * threadgroup_unlock - unlock threadgroup
+ * @tsk: member task of the threadgroup to unlock
+ *
+ * Reverse threadgroup_lock().
+ */
 static inline void threadgroup_unlock(struct task_struct *tsk)
 {
 	up_write(&tsk->signal->group_rwsem);
+	mutex_unlock(&tsk->signal->cred_guard_mutex);
 }
 #else
 static inline void threadgroup_change_begin(struct task_struct *tsk) {}
kernel/signal.c +10 -0
Index: work/kernel/signal.c
===================================================================
@@ -2359,8 +2359,15 @@ void exit_signals(struct task_struct *ts
 	int group_stop = 0;
 	sigset_t unblocked;
 
+	/*
+	 * @tsk is about to have PF_EXITING set - lock out users which
+	 * expect stable threadgroup.
+	 */
+	threadgroup_change_begin(tsk);
+
 	if (thread_group_empty(tsk) || signal_group_exit(tsk->signal)) {
 		tsk->flags |= PF_EXITING;
+		threadgroup_change_end(tsk);
 		return;
 	}
 
@@ -2370,6 +2377,9 @@ void exit_signals(struct task_struct *ts
 	 * see wants_signal(), do_signal_stop().
 	 */
 	tsk->flags |= PF_EXITING;
+
+	threadgroup_change_end(tsk);
+
 	if (!signal_pending(tsk))
 		goto out;

A message that is not in the archive (sent off-list or not yet received): 20111207153046.GC13252-oHC15RC7JGTpAmv0O++HtFaTQe2KTcn/@public.gmane.org

#75 lore raw

Re: [PATCH UPDATED 03/10] threadgroup: extend threadgroup_lock() to cover exit and exec

Hello, Frederic.

Quoted 16 lines · Frederic Weisbecker

I don't understand what you mean. On your patches, you only process tasks that don't have PF_EXITING, ie: you don't include these in the flex array on cgroup_attach_proc(). So that still applies in my proposal.

From the exit path we would have:

	exit_signal() {
		lock_threadgroup_change(task);
		task->flags |= PF_EXITING;
		lock_threadgroup(task);
	}

	exit all the rest: mm, etc...

Then from cgroup_attach_proc():

Quoted 7 lines
	lock_threadgroup(task);
	for_each_thread(task) {
		if (!(task->flags & PF_EXITING))
			include in flex array
	}

Am I forgetting something?

The point I was trying to make was that doing the above would make ->exit() called on dangling task of a threadgroup in rare cases. ie. With the proposed change, after a threadgroup migration, all tasks in the threadgroup is in the new cgroup. No method will be called on the old cgroup for any of the member task. With the above change, process migration would leave out dying tasks and ->exit() can be called with the old cgroup.

Hmm... that said, it probably doesn't matter all that much either way. I'll update the patchset and repost.

Thanks.

Signature · 1 line
tejun

A message that is not in the archive (sent off-list or not yet received): [email protected]

#77 lore raw

Re: [PATCH UPDATED 03/10] threadgroup: extend threadgroup_lock() to cover exit and exec

Quoted 10 lines · Tejun Heo

Hello, Frederic.

Quoted 6 lines · Frederic Weisbecker

But I don't think it's very useful to protect against irq_exit_thread(), what happens there is purely of internal irq interest.

Then right after, PF_EXITING is set before any interesting change. Isn't it possible to simply lock this flag setting? IIRC, as soon as the PF_EXITING flag is set, you ignore the task for attachment.

Quoted 8 lines

I think that's technically possible but it does introduce another class of tasks - the dying ones. e.g. If a task has PF_EXITING set and the containing process is migrating, we'll have to migrate all tasks but the dying one and cgroup ->exit callbacks can be called on the lonely task after the migration is complete. It's kinda messy and if someone makes a wrong assumption there, the bug is gonna be even more difficult to reproduce / track down than now. Yes, smaller scope locking is nicer but I would like to avoid api weirdities like that.

I don't understand what you mean. On your patches, you only process tasks that don't have PF_EXITING, ie: you don't include these in the flex array on cgroup_attach_proc(). So that still applies in my proposal.

From the exit path we would have:

	exit_signal() {
		lock_threadgroup_change(task);
		task->flags |= PF_EXITING;
		lock_threadgroup(task);
	}

	exit all the rest: mm, etc...

Then from cgroup_attach_proc():

	lock_threadgroup(task);
	for_each_thread(task) {
		if (!(task->flags & PF_EXITING))
			include in flex array
	}

Am I forgetting something?

author maintainer
#78 lore raw

Re: [PATCH UPDATED 03/10] threadgroup: extend threadgroup_lock() to cover exit and exec

In reply to Frederic Weisbecker

Hello, Frederic.

Quoted 6 lines · Frederic Weisbecker

But I don't think it's very useful to protect against irq_exit_thread(), what happens there is purely of internal irq interest.

Then right after, PF_EXITING is set before any interesting change. Isn't it possible to simply lock this flag setting? IIRC, as soon as the PF_EXITING flag is set, you ignore the task for attachment.

I think that's technically possible but it does introduce another class of tasks - the dying ones. e.g. If a task has PF_EXITING set and the containing process is migrating, we'll have to migrate all tasks but the dying one and cgroup ->exit callbacks can be called on the lonely task after the migration is complete. It's kinda messy and if someone makes a wrong assumption there, the bug is gonna be even more difficult to reproduce / track down than now. Yes, smaller scope locking is nicer but I would like to avoid api weirdities like that.

Thanks.

Signature · 1 line
tejun

A message that is not in the archive (sent off-list or not yet received): [email protected]

#80 lore raw

Re: [PATCH UPDATED 03/10] threadgroup: extend threadgroup_lock() to cover exit and exec

Quoted 17 lines · Tejun Heo

Hello, Frederic.

Quoted 5 lines · Frederic Weisbecker
Quoted 5 lines
kernel/exit.c +5 -0
+	/*
+	 * @tsk's threadgroup is going through changes - lock out users
+	 * which expect stable threadgroup.  Do this before actually
+	 * starting tearing down @tsk so that locked threadgroup has either
+	 * alive or dead tasks, not something inbetween.
Quoted 8 lines

kernel/exit.c NORET_TYPE void do_exit(long code) · line 946

Quoted 3 lines
kernel/exit.c +3 -0
+	 */
+	threadgroup_change_begin(tsk);
+

I still wonder why there is a so big coverage of this lock. I mean why is it called right before exit_irq_thread() and released so late. All we want is to lock cgroup_exit() I think, after which tasks can't be migrated.

Quoted 6 lines

That way, cgroup plugins never see tasks in the process of deconstruction. Its ->mm, ->sighand, ->signal and so on either don't exist or continue to exist across all cgroup callbacks. It's simpler and safer (especially as bugs in this area would only be visible when migration and exit race), and if we're gonna synchronize exit path at all, there isn't anything to lose by excluding the whole thing.

Fine.

But I don't think it's very useful to protect against irq_exit_thread(), what happens there is purely of internal irq interest.

Then right after, PF_EXITING is set before any interesting change. Isn't it possible to simply lock this flag setting? IIRC, as soon as the PF_EXITING flag is set, you ignore the task for attachment.

author maintainer
#81 lore raw

Re: [PATCH UPDATED 03/10] threadgroup: extend threadgroup_lock() to cover exit and exec

In reply to Frederic Weisbecker

Hello, Frederic.

Quoted 5 lines · Frederic Weisbecker
Quoted 5 lines
kernel/exit.c +5 -0
+	/*
+	 * @tsk's threadgroup is going through changes - lock out users
+	 * which expect stable threadgroup.  Do this before actually
+	 * starting tearing down @tsk so that locked threadgroup has either
+	 * alive or dead tasks, not something inbetween.
Quoted 8 lines

kernel/exit.c NORET_TYPE void do_exit(long code) · line 946

Quoted 3 lines
kernel/exit.c +3 -0
+	 */
+	threadgroup_change_begin(tsk);
+

I still wonder why there is a so big coverage of this lock. I mean why is it called right before exit_irq_thread() and released so late. All we want is to lock cgroup_exit() I think, after which tasks can't be migrated.

That way, cgroup plugins never see tasks in the process of deconstruction. Its ->mm, ->sighand, ->signal and so on either don't exist or continue to exist across all cgroup callbacks. It's simpler and safer (especially as bugs in this area would only be visible when migration and exit race), and if we're gonna synchronize exit path at all, there isn't anything to lose by excluding the whole thing.

Thanks.

Signature · 1 line
tejun
author maintainer
#82 lore raw

PATCH 4/10 cgroup: always lock threadgroup during migration

In reply to Tejun Heo

Update cgroup to take advantage of the fack that threadgroup_lock() guarantees stable threadgroup.

* Lock threadgroup even if the target is a single task. This guarantees that when the target tasks stay stable during migration regardless of the target type.

* Remove PF_EXITING early exit optimization from attach_task_by_pid() and check it in cgroup_task_migrate() instead. The optimization was for rather cold path to begin with and PF_EXITING state can be trusted throughout migration by checking it after locking threadgroup.

* Don't add PF_EXITING tasks to target task array in cgroup_attach_proc(). This ensures that task migration is performed only for live tasks.

* Remove -ESRCH failure path from cgroup_task_migrate(). With the above changes, it's guaranteed to be called only for live tasks.

After the changes, only live tasks are migrated and they're guaranteed to stay alive until migration is complete. This removes problems caused by exec and exit racing against cgroup migration including symmetry among cgroup attach methods and different cgroup methods racing each other.

v2: Oleg pointed out that one more PF_EXITING check can be removed
from cgroup_attach_proc(). Removed.

Cc Oleg Nesterov <[email protected]>
Cc Andrew Morton <[email protected]>
Cc Paul Menage <[email protected]>
Cc Li Zefan <[email protected]>
---
 kernel/cgroup.c |   51 +++++++++++++++++++++++----------------------------
 1 files changed, 23 insertions(+), 28 deletions(-)
kernel/cgroup.c +23 -28
index f0e099f..83e10f9 100644
@@ -1762,7 +1762,7 @@ EXPORT_SYMBOL_GPL(cgroup_path);
  *
  * 'guarantee' is set if the caller promises that a new css_set for the task
  * will already exist. If not set, this function might sleep, and can fail with
- * -ENOMEM. Otherwise, it can only fail with -ESRCH.
+ * -ENOMEM. Must be called with cgroup_mutex and threadgroup locked.
  */
 static int cgroup_task_migrate(struct cgroup *cgrp, struct cgroup *oldcgrp,
 			       struct task_struct *tsk, bool guarantee)
@@ -1800,13 +1800,9 @@ static int cgroup_task_migrate(struct cgroup *cgrp, struct cgroup *oldcgrp,
 	}
 	put_css_set(oldcg);
 
-	/* if PF_EXITING is set, the tsk->cgroups pointer is no longer safe. */
+	/* @tsk can't exit as its threadgroup is locked */
 	task_lock(tsk);
-	if (tsk->flags & PF_EXITING) {
-		task_unlock(tsk);
-		put_css_set(newcg);
-		return -ESRCH;
-	}
+	WARN_ON_ONCE(tsk->flags & PF_EXITING);
 	rcu_assign_pointer(tsk->cgroups, newcg);
 	task_unlock(tsk);
 
@@ -1832,8 +1828,8 @@ static int cgroup_task_migrate(struct cgroup *cgrp, struct cgroup *oldcgrp,
  * @cgrp: the cgroup the task is attaching to
  * @tsk: the task to be attached
  *
- * Call holding cgroup_mutex. May take task_lock of
- * the task 'tsk' during call.
+ * Call with cgroup_mutex and threadgroup locked. May take task_lock of
+ * @tsk during call.
  */
 int cgroup_attach_task(struct cgroup *cgrp, struct task_struct *tsk)
 {
@@ -1842,6 +1838,10 @@ int cgroup_attach_task(struct cgroup *cgrp, struct task_struct *tsk)
 	struct cgroup *oldcgrp;
 	struct cgroupfs_root *root = cgrp->root;
 
+	/* @tsk either already exited or can't exit until the end */
+	if (tsk->flags & PF_EXITING)
+		return -ESRCH;
+
 	/* Nothing to do if the task is already in that cgroup */
 	oldcgrp = task_cgroup_from_root(tsk, root);
 	if (cgrp == oldcgrp)
@@ -2062,6 +2062,10 @@ int cgroup_attach_proc(struct cgroup *cgrp, struct task_struct *leader)
 	tsk = leader;
 	i = 0;
 	do {
+		/* @tsk either already exited or can't exit until the end */
+		if (tsk->flags & PF_EXITING)
+			continue;
+
 		/* as per above, nr_threads may decrease, but not increase. */
 		BUG_ON(i >= group_size);
 		get_task_struct(tsk);
@@ -2116,11 +2120,6 @@ int cgroup_attach_proc(struct cgroup *cgrp, struct task_struct *leader)
 			continue;
 		/* get old css_set pointer */
 		task_lock(tsk);
-		if (tsk->flags & PF_EXITING) {
-			/* ignore this task if it's going away */
-			task_unlock(tsk);
-			continue;
-		}
 		oldcg = tsk->cgroups;
 		get_css_set(oldcg);
 		task_unlock(tsk);
@@ -2158,9 +2157,8 @@ int cgroup_attach_proc(struct cgroup *cgrp, struct task_struct *leader)
 			if (ss->attach_task)
 				ss->attach_task(cgrp, tsk);
 		}
-		/* if the thread is PF_EXITING, it can just get skipped. */
 		retval = cgroup_task_migrate(cgrp, oldcgrp, tsk, true);
-		BUG_ON(retval != 0 && retval != -ESRCH);
+		BUG_ON(retval != 0);
 	}
 	/* nothing is sensitive to fork() after this point. */
 
@@ -2212,8 +2210,8 @@ out_free_group_list:
 
 /*
  * Find the task_struct of the task to attach by vpid and pass it along to the
- * function to attach either it or all tasks in its threadgroup. Will take
- * cgroup_mutex; may take task_lock of task.
+ * function to attach either it or all tasks in its threadgroup. Will lock
+ * cgroup_mutex and threadgroup; may take task_lock of task.
  */
 static int attach_task_by_pid(struct cgroup *cgrp, u64 pid, bool threadgroup)
 {
@@ -2240,11 +2238,6 @@ static int attach_task_by_pid(struct cgroup *cgrp, u64 pid, bool threadgroup)
 			 * detect it later.
 			 */
 			tsk = tsk->group_leader;
-		} else if (tsk->flags & PF_EXITING) {
-			/* optimization for the single-task-only case */
-			rcu_read_unlock();
-			cgroup_unlock();
-			return -ESRCH;
 		}
 		/*
 		 * even if we're attaching all tasks in the thread group, we
@@ -2268,13 +2261,15 @@ static int attach_task_by_pid(struct cgroup *cgrp, u64 pid, bool threadgroup)
 		get_task_struct(tsk);
 	}
 
-	if (threadgroup) {
-		threadgroup_lock(tsk);
+	threadgroup_lock(tsk);
+
+	if (threadgroup)
 		ret = cgroup_attach_proc(cgrp, tsk);
-		threadgroup_unlock(tsk);
-	} else {
+	else
 		ret = cgroup_attach_task(cgrp, tsk);
-	}
+
+	threadgroup_unlock(tsk);
+
 	put_task_struct(tsk);
 	cgroup_unlock();
 	return ret;
Signature · 1 line
1.7.3.1

A message that is not in the archive (sent off-list or not yet received): 1320191193-8110-5-git-send-email-tj-DgEjT+Ai2ygdnm+yROfE0A@public.gmane.org

Reviewed-by
#84 lore raw

Re: [PATCH 04/10] cgroup: always lock threadgroup during migration

Quoted 26 lines · On Tue, 1 Nov 2011 16:46:27 -0700 Tejun Heo…

Update cgroup to take advantage of the fack that threadgroup_lock() guarantees stable threadgroup.

* Lock threadgroup even if the target is a single task. This guarantees that when the target tasks stay stable during migration regardless of the target type.

* Remove PF_EXITING early exit optimization from attach_task_by_pid() and check it in cgroup_task_migrate() instead. The optimization was for rather cold path to begin with and PF_EXITING state can be trusted throughout migration by checking it after locking threadgroup.

* Don't add PF_EXITING tasks to target task array in cgroup_attach_proc(). This ensures that task migration is performed only for live tasks.

* Remove -ESRCH failure path from cgroup_task_migrate(). With the above changes, it's guaranteed to be called only for live tasks.

After the changes, only live tasks are migrated and they're guaranteed to stay alive until migration is complete. This removes problems caused by exec and exit racing against cgroup migration including symmetry among cgroup attach methods and different cgroup methods racing each other.

Quoted 8 lines

v2: Oleg pointed out that one more PF_EXITING check can be removed
from cgroup_attach_proc(). Removed.

Cc Oleg Nesterov <[email protected]>
Cc Andrew Morton <akpm-de/[email protected]>
Cc Paul Menage <[email protected]>
Cc Li Zefan <[email protected]>

A few questions below.

Quoted 22 lines
---
 kernel/cgroup.c |   51 +++++++++++++++++++++++----------------------------
 1 files changed, 23 insertions(+), 28 deletions(-)
kernel/cgroup.c +1 -2
index f0e099f..83e10f9 100644
@@ -1762,7 +1762,7 @@ EXPORT_SYMBOL_GPL(cgroup_path);
  *
  * 'guarantee' is set if the caller promises that a new css_set for the task
  * will already exist. If not set, this function might sleep, and can fail with
- * -ENOMEM. Otherwise, it can only fail with -ESRCH.
+ * -ENOMEM. Must be called with cgroup_mutex and threadgroup locked.
  */
 static int cgroup_task_migrate(struct cgroup *cgrp, struct cgroup *oldcgrp,
 			       struct task_struct *tsk, bool guarantee)
@@ -1800,13 +1800,9 @@ static int cgroup_task_migrate(struct cgroup *cgrp, struct cgroup *oldcgrp,
 	}
 	put_css_set(oldcg);
 
-	/* if PF_EXITING is set, the tsk->cgroups pointer is no longer safe. */

kernel/cgroup.c static int cgroup_task_migrate(struct cgroup *cgrp, struct cgroup *oldcgrp, · line 1807

Quoted 10 lines
kernel/cgroup.c +2 -5
+	/* @tsk can't exit as its threadgroup is locked */
 	task_lock(tsk);
-	if (tsk->flags & PF_EXITING) {
-		task_unlock(tsk);
-		put_css_set(newcg);
-		return -ESRCH;
-	}
+	WARN_ON_ONCE(tsk->flags & PF_EXITING);
 	rcu_assign_pointer(tsk->cgroups, newcg);
 	task_unlock(tsk);

Is this task_lock/unlock is required ?

kernel/cgroup.c int cgroup_attach_proc(struct cgroup *cgrp, struct task_struct *leader) · line 2125

Quoted 13 lines
kernel/cgroup.c +0 -5
@@ -2116,11 +2120,6 @@ int cgroup_attach_proc(struct cgroup *cgrp, struct task_struct *leader)
 			continue;
 		/* get old css_set pointer */
 		task_lock(tsk);
-		if (tsk->flags & PF_EXITING) {
-			/* ignore this task if it's going away */
-			task_unlock(tsk);
-			continue;
-		}
 		oldcg = tsk->cgroups;
 		get_css_set(oldcg);
 		task_unlock(tsk);

ditto.

Thanks,
-Kame

Reviewed-by on v1 4/10
#85 lore raw

Re: [PATCH 04/10] cgroup: always lock threadgroup during migration

In reply to Tejun Heo
Quoted 26 lines · On Tue, 1 Nov 2011 16:46:27 -0700 Tejun Heo <[email protected]> wrote:

Update cgroup to take advantage of the fack that threadgroup_lock() guarantees stable threadgroup.

* Lock threadgroup even if the target is a single task. This guarantees that when the target tasks stay stable during migration regardless of the target type.

* Remove PF_EXITING early exit optimization from attach_task_by_pid() and check it in cgroup_task_migrate() instead. The optimization was for rather cold path to begin with and PF_EXITING state can be trusted throughout migration by checking it after locking threadgroup.

* Don't add PF_EXITING tasks to target task array in cgroup_attach_proc(). This ensures that task migration is performed only for live tasks.

* Remove -ESRCH failure path from cgroup_task_migrate(). With the above changes, it's guaranteed to be called only for live tasks.

After the changes, only live tasks are migrated and they're guaranteed to stay alive until migration is complete. This removes problems caused by exec and exit racing against cgroup migration including symmetry among cgroup attach methods and different cgroup methods racing each other.

Quoted 8 lines

v2: Oleg pointed out that one more PF_EXITING check can be removed
from cgroup_attach_proc(). Removed.

Cc Oleg Nesterov <[email protected]>
Cc Andrew Morton <[email protected]>
Cc Paul Menage <[email protected]>
Cc Li Zefan <[email protected]>

A few questions below.

Quoted 22 lines
---
 kernel/cgroup.c |   51 +++++++++++++++++++++++----------------------------
 1 files changed, 23 insertions(+), 28 deletions(-)
kernel/cgroup.c +1 -2
index f0e099f..83e10f9 100644
@@ -1762,7 +1762,7 @@ EXPORT_SYMBOL_GPL(cgroup_path);
  *
  * 'guarantee' is set if the caller promises that a new css_set for the task
  * will already exist. If not set, this function might sleep, and can fail with
- * -ENOMEM. Otherwise, it can only fail with -ESRCH.
+ * -ENOMEM. Must be called with cgroup_mutex and threadgroup locked.
  */
 static int cgroup_task_migrate(struct cgroup *cgrp, struct cgroup *oldcgrp,
 			       struct task_struct *tsk, bool guarantee)
@@ -1800,13 +1800,9 @@ static int cgroup_task_migrate(struct cgroup *cgrp, struct cgroup *oldcgrp,
 	}
 	put_css_set(oldcg);
 
-	/* if PF_EXITING is set, the tsk->cgroups pointer is no longer safe. */

kernel/cgroup.c static int cgroup_task_migrate(struct cgroup *cgrp, struct cgroup *oldcgrp, · line 1807

Quoted 10 lines
kernel/cgroup.c +2 -5
+	/* @tsk can't exit as its threadgroup is locked */
 	task_lock(tsk);
-	if (tsk->flags & PF_EXITING) {
-		task_unlock(tsk);
-		put_css_set(newcg);
-		return -ESRCH;
-	}
+	WARN_ON_ONCE(tsk->flags & PF_EXITING);
 	rcu_assign_pointer(tsk->cgroups, newcg);
 	task_unlock(tsk);

Is this task_lock/unlock is required ?

kernel/cgroup.c int cgroup_attach_proc(struct cgroup *cgrp, struct task_struct *leader) · line 2125

Quoted 13 lines
kernel/cgroup.c +0 -5
@@ -2116,11 +2120,6 @@ int cgroup_attach_proc(struct cgroup *cgrp, struct task_struct *leader)
 			continue;
 		/* get old css_set pointer */
 		task_lock(tsk);
-		if (tsk->flags & PF_EXITING) {
-			/* ignore this task if it's going away */
-			task_unlock(tsk);
-			continue;
-		}
 		oldcg = tsk->cgroups;
 		get_css_set(oldcg);
 		task_unlock(tsk);

ditto.

Thanks,
-Kame

Reviewed-by on v1 4/10
#86 lore raw

Re: [PATCH 04/10] cgroup: always lock threadgroup during migration

In reply to Tejun Heo
Quoted 26 lines · On Tue, 1 Nov 2011 16:46:27 -0700 Tejun Heo <[email protected]> wrote:

Update cgroup to take advantage of the fack that threadgroup_lock() guarantees stable threadgroup.

* Lock threadgroup even if the target is a single task. This guarantees that when the target tasks stay stable during migration regardless of the target type.

* Remove PF_EXITING early exit optimization from attach_task_by_pid() and check it in cgroup_task_migrate() instead. The optimization was for rather cold path to begin with and PF_EXITING state can be trusted throughout migration by checking it after locking threadgroup.

* Don't add PF_EXITING tasks to target task array in cgroup_attach_proc(). This ensures that task migration is performed only for live tasks.

* Remove -ESRCH failure path from cgroup_task_migrate(). With the above changes, it's guaranteed to be called only for live tasks.

After the changes, only live tasks are migrated and they're guaranteed to stay alive until migration is complete. This removes problems caused by exec and exit racing against cgroup migration including symmetry among cgroup attach methods and different cgroup methods racing each other.

Quoted 8 lines

v2: Oleg pointed out that one more PF_EXITING check can be removed
from cgroup_attach_proc(). Removed.

Cc Oleg Nesterov <[email protected]>
Cc Andrew Morton <[email protected]>
Cc Paul Menage <[email protected]>
Cc Li Zefan <[email protected]>

A few questions below.

Quoted 22 lines
---
 kernel/cgroup.c |   51 +++++++++++++++++++++++----------------------------
 1 files changed, 23 insertions(+), 28 deletions(-)
kernel/cgroup.c +1 -2
index f0e099f..83e10f9 100644
@@ -1762,7 +1762,7 @@ EXPORT_SYMBOL_GPL(cgroup_path);
  *
  * 'guarantee' is set if the caller promises that a new css_set for the task
  * will already exist. If not set, this function might sleep, and can fail with
- * -ENOMEM. Otherwise, it can only fail with -ESRCH.
+ * -ENOMEM. Must be called with cgroup_mutex and threadgroup locked.
  */
 static int cgroup_task_migrate(struct cgroup *cgrp, struct cgroup *oldcgrp,
 			       struct task_struct *tsk, bool guarantee)
@@ -1800,13 +1800,9 @@ static int cgroup_task_migrate(struct cgroup *cgrp, struct cgroup *oldcgrp,
 	}
 	put_css_set(oldcg);
 
-	/* if PF_EXITING is set, the tsk->cgroups pointer is no longer safe. */

kernel/cgroup.c static int cgroup_task_migrate(struct cgroup *cgrp, struct cgroup *oldcgrp, · line 1807

Quoted 10 lines
kernel/cgroup.c +2 -5
+	/* @tsk can't exit as its threadgroup is locked */
 	task_lock(tsk);
-	if (tsk->flags & PF_EXITING) {
-		task_unlock(tsk);
-		put_css_set(newcg);
-		return -ESRCH;
-	}
+	WARN_ON_ONCE(tsk->flags & PF_EXITING);
 	rcu_assign_pointer(tsk->cgroups, newcg);
 	task_unlock(tsk);

Is this task_lock/unlock is required ?

kernel/cgroup.c int cgroup_attach_proc(struct cgroup *cgrp, struct task_struct *leader) · line 2125

Quoted 13 lines
kernel/cgroup.c +0 -5
@@ -2116,11 +2120,6 @@ int cgroup_attach_proc(struct cgroup *cgrp, struct task_struct *leader)
 			continue;
 		/* get old css_set pointer */
 		task_lock(tsk);
-		if (tsk->flags & PF_EXITING) {
-			/* ignore this task if it's going away */
-			task_unlock(tsk);
-			continue;
-		}
 		oldcg = tsk->cgroups;
 		get_css_set(oldcg);
 		task_unlock(tsk);

ditto.

Thanks,
-Kame

author maintainer
#87 lore raw

Re: [PATCH 04/10] cgroup: always lock threadgroup during migration

Hello,

Quoted 5 lines · KAMEZAWA Hiroyuki
Quoted 5 lines
kernel/cgroup.c +1 -3
-	/* if PF_EXITING is set, the tsk->cgroups pointer is no longer safe. */
+	/* @tsk can't exit as its threadgroup is locked */
 	task_lock(tsk);
-	if (tsk->flags & PF_EXITING) {
-		task_unlock(tsk);
Quoted 8 lines

kernel/cgroup.c static int cgroup_task_migrate(struct cgroup *cgrp, struct cgroup *oldcgrp, · line 1807

Quoted 6 lines
kernel/cgroup.c +1 -3
-		put_css_set(newcg);
-		return -ESRCH;
-	}
+	WARN_ON_ONCE(tsk->flags & PF_EXITING);
 	rcu_assign_pointer(tsk->cgroups, newcg);
 	task_unlock(tsk);

Is this task_lock/unlock is required ?

For put, I don't think it's necessary.

Quoted 4 lines
Quoted 4 lines
kernel/cgroup.c +0 -0
@@ -2116,11 +2120,6 @@ int cgroup_attach_proc(struct cgroup *cgrp, struct task_struct *leader)
 			continue;
 		/* get old css_set pointer */
 		task_lock(tsk);
Quoted 8 lines

kernel/cgroup.c int cgroup_attach_proc(struct cgroup *cgrp, struct task_struct *leader) · line 2125

Quoted 8 lines
kernel/cgroup.c +0 -5
-		if (tsk->flags & PF_EXITING) {
-			/* ignore this task if it's going away */
-			task_unlock(tsk);
-			continue;
-		}
 		oldcg = tsk->cgroups;
 		get_css_set(oldcg);
 		task_unlock(tsk);

For get, I think it is; otherwise, nothing prevents someone else from changing tsk->cgroups between load and get, and destroying it.

Thanks.

Signature · 1 line
tejun
author maintainer
#88 lore raw

Re: [PATCH 04/10] cgroup: always lock threadgroup during migration

In reply to KAMEZAWA Hiroyuki

Hello,

Quoted 5 lines · KAMEZAWA Hiroyuki
Quoted 5 lines
kernel/cgroup.c +1 -3
-	/* if PF_EXITING is set, the tsk->cgroups pointer is no longer safe. */
+	/* @tsk can't exit as its threadgroup is locked */
 	task_lock(tsk);
-	if (tsk->flags & PF_EXITING) {
-		task_unlock(tsk);
Quoted 8 lines

kernel/cgroup.c static int cgroup_task_migrate(struct cgroup *cgrp, struct cgroup *oldcgrp, · line 1807

Quoted 6 lines
kernel/cgroup.c +1 -3
-		put_css_set(newcg);
-		return -ESRCH;
-	}
+	WARN_ON_ONCE(tsk->flags & PF_EXITING);
 	rcu_assign_pointer(tsk->cgroups, newcg);
 	task_unlock(tsk);

Is this task_lock/unlock is required ?

For put, I don't think it's necessary.

Quoted 4 lines
Quoted 4 lines
kernel/cgroup.c +0 -0
@@ -2116,11 +2120,6 @@ int cgroup_attach_proc(struct cgroup *cgrp, struct task_struct *leader)
 			continue;
 		/* get old css_set pointer */
 		task_lock(tsk);
Quoted 8 lines

kernel/cgroup.c int cgroup_attach_proc(struct cgroup *cgrp, struct task_struct *leader) · line 2125

Quoted 8 lines
kernel/cgroup.c +0 -5
-		if (tsk->flags & PF_EXITING) {
-			/* ignore this task if it's going away */
-			task_unlock(tsk);
-			continue;
-		}
 		oldcg = tsk->cgroups;
 		get_css_set(oldcg);
 		task_unlock(tsk);

For get, I think it is; otherwise, nothing prevents someone else from changing tsk->cgroups between load and get, and destroying it.

Thanks.

Signature · 1 line
tejun
Reviewed-by on v1 4/10
#89 lore raw

Re: [PATCH 04/10] cgroup: always lock threadgroup during migration

In reply to Tejun Heo
Quoted 26 lines · Tejun Heo

Update cgroup to take advantage of the fack that threadgroup_lock() guarantees stable threadgroup.

* Lock threadgroup even if the target is a single task. This guarantees that when the target tasks stay stable during migration regardless of the target type.

* Remove PF_EXITING early exit optimization from attach_task_by_pid() and check it in cgroup_task_migrate() instead. The optimization was for rather cold path to begin with and PF_EXITING state can be trusted throughout migration by checking it after locking threadgroup.

* Don't add PF_EXITING tasks to target task array in cgroup_attach_proc(). This ensures that task migration is performed only for live tasks.

* Remove -ESRCH failure path from cgroup_task_migrate(). With the above changes, it's guaranteed to be called only for live tasks.

After the changes, only live tasks are migrated and they're guaranteed to stay alive until migration is complete. This removes problems caused by exec and exit racing against cgroup migration including symmetry among cgroup attach methods and different cgroup methods racing each other.

Quoted 8 lines

v2: Oleg pointed out that one more PF_EXITING check can be removed
from cgroup_attach_proc(). Removed.

Cc Oleg Nesterov <[email protected]>
Cc Andrew Morton <akpm-de/[email protected]>
Cc Paul Menage <[email protected]>
Cc Li Zefan <[email protected]>

Just a little thing:

Quoted 20 lines
---
 kernel/cgroup.c |   51 +++++++++++++++++++++++----------------------------
 1 files changed, 23 insertions(+), 28 deletions(-)
kernel/cgroup.c +1 -1
index f0e099f..83e10f9 100644
@@ -1762,7 +1762,7 @@ EXPORT_SYMBOL_GPL(cgroup_path);
  *
  * 'guarantee' is set if the caller promises that a new css_set for the task
  * will already exist. If not set, this function might sleep, and can fail with
- * -ENOMEM. Otherwise, it can only fail with -ESRCH.
+ * -ENOMEM. Must be called with cgroup_mutex and threadgroup locked.
  */
 static int cgroup_task_migrate(struct cgroup *cgrp, struct cgroup *oldcgrp,
 			       struct task_struct *tsk, bool guarantee)
@@ -1800,13 +1800,9 @@ static int cgroup_task_migrate(struct cgroup *cgrp, struct cgroup *oldcgrp,
 	}
 	put_css_set(oldcg);

kernel/cgroup.c static int cgroup_task_migrate(struct cgroup *cgrp, struct cgroup *oldcgrp, · line 1805

Quoted 10 lines
kernel/cgroup.c +2 -6
 
-	/* if PF_EXITING is set, the tsk->cgroups pointer is no longer safe. */
+	/* @tsk can't exit as its threadgroup is locked */
 	task_lock(tsk);
-	if (tsk->flags & PF_EXITING) {
-		task_unlock(tsk);
-		put_css_set(newcg);
-		return -ESRCH;
-	}
+	WARN_ON_ONCE(tsk->flags & PF_EXITING);

I have the feeling the task_lock is now useless given that we are synchronized against cgroup_exit() with the threadgroup_lock.

It's probably also useless in cgroup_exit(). But that's something for a further patch...

Thanks.

#90 lore raw

Re: [PATCH 04/10] cgroup: always lock threadgroup during migration

Quoted 7 lines · Frederic Weisbecker

I have the feeling the task_lock is now useless given that we are synchronized against cgroup_exit() with the threadgroup_lock.

It's probably also useless in cgroup_exit(). But that's something for a further patch...

Thanks.

Oops, I missed what Kamezawa said and your answer, sorry.

author maintainer
#91 lore raw

Re: [PATCH 04/10] cgroup: always lock threadgroup during migration

Quoted 10 lines · Frederic Weisbecker
Quoted 7 lines · Frederic Weisbecker

I have the feeling the task_lock is now useless given that we are synchronized against cgroup_exit() with the threadgroup_lock.

It's probably also useless in cgroup_exit(). But that's something for a further patch...

Thanks.

Oops, I missed what Kamezawa said and your answer, sorry.

Yeap, I'll see how removing it for put looks.

Thanks.

Signature · 1 line
tejun
#92 lore raw

Re: [PATCH 04/10] cgroup: always lock threadgroup during migration

In reply to Frederic Weisbecker
Quoted 10 lines · Frederic Weisbecker
Quoted 7 lines · Frederic Weisbecker

I have the feeling the task_lock is now useless given that we are synchronized against cgroup_exit() with the threadgroup_lock.

It's probably also useless in cgroup_exit(). But that's something for a further patch...

Thanks.

Oops, I missed what Kamezawa said and your answer, sorry.

Yeap, I'll see how removing it for put looks.

Thanks.

Signature · 1 line
tejun
#93 lore raw

Re: [PATCH 04/10] cgroup: always lock threadgroup during migration

In reply to Frederic Weisbecker
Quoted 7 lines · Frederic Weisbecker

I have the feeling the task_lock is now useless given that we are synchronized against cgroup_exit() with the threadgroup_lock.

It's probably also useless in cgroup_exit(). But that's something for a further patch...

Thanks.

Oops, I missed what Kamezawa said and your answer, sorry.

Reviewed-by on v1 4/10
#94 lore raw

Re: [PATCH 04/10] cgroup: always lock threadgroup during migration

In reply to Tejun Heo
Quoted 26 lines · Tejun Heo

Update cgroup to take advantage of the fack that threadgroup_lock() guarantees stable threadgroup.

* Lock threadgroup even if the target is a single task. This guarantees that when the target tasks stay stable during migration regardless of the target type.

* Remove PF_EXITING early exit optimization from attach_task_by_pid() and check it in cgroup_task_migrate() instead. The optimization was for rather cold path to begin with and PF_EXITING state can be trusted throughout migration by checking it after locking threadgroup.

* Don't add PF_EXITING tasks to target task array in cgroup_attach_proc(). This ensures that task migration is performed only for live tasks.

* Remove -ESRCH failure path from cgroup_task_migrate(). With the above changes, it's guaranteed to be called only for live tasks.

After the changes, only live tasks are migrated and they're guaranteed to stay alive until migration is complete. This removes problems caused by exec and exit racing against cgroup migration including symmetry among cgroup attach methods and different cgroup methods racing each other.

Quoted 8 lines

v2: Oleg pointed out that one more PF_EXITING check can be removed
from cgroup_attach_proc(). Removed.

Cc Oleg Nesterov <[email protected]>
Cc Andrew Morton <[email protected]>
Cc Paul Menage <[email protected]>
Cc Li Zefan <[email protected]>

Just a little thing:

Quoted 20 lines
---
 kernel/cgroup.c |   51 +++++++++++++++++++++++----------------------------
 1 files changed, 23 insertions(+), 28 deletions(-)
kernel/cgroup.c +1 -1
index f0e099f..83e10f9 100644
@@ -1762,7 +1762,7 @@ EXPORT_SYMBOL_GPL(cgroup_path);
  *
  * 'guarantee' is set if the caller promises that a new css_set for the task
  * will already exist. If not set, this function might sleep, and can fail with
- * -ENOMEM. Otherwise, it can only fail with -ESRCH.
+ * -ENOMEM. Must be called with cgroup_mutex and threadgroup locked.
  */
 static int cgroup_task_migrate(struct cgroup *cgrp, struct cgroup *oldcgrp,
 			       struct task_struct *tsk, bool guarantee)
@@ -1800,13 +1800,9 @@ static int cgroup_task_migrate(struct cgroup *cgrp, struct cgroup *oldcgrp,
 	}
 	put_css_set(oldcg);

kernel/cgroup.c static int cgroup_task_migrate(struct cgroup *cgrp, struct cgroup *oldcgrp, · line 1805

Quoted 10 lines
kernel/cgroup.c +2 -6
 
-	/* if PF_EXITING is set, the tsk->cgroups pointer is no longer safe. */
+	/* @tsk can't exit as its threadgroup is locked */
 	task_lock(tsk);
-	if (tsk->flags & PF_EXITING) {
-		task_unlock(tsk);
-		put_css_set(newcg);
-		return -ESRCH;
-	}
+	WARN_ON_ONCE(tsk->flags & PF_EXITING);

I have the feeling the task_lock is now useless given that we are synchronized against cgroup_exit() with the threadgroup_lock.

It's probably also useless in cgroup_exit(). But that's something for a further patch...

Thanks.

author maintainer
#95 lore raw

PATCH 5/10 cgroup: subsys->attach_task() should be called after migration

In reply to Tejun Heo

cgroup_attach_task() calls subsys->attach_task() after cgroup_task_migrate(); however, cgroup_attach_proc() calls it before migration. This actually affects some of the users. Update cgroup_attach_proc() such that ->attach_task() is called after migration.

---
 kernel/cgroup.c |    6 ++++--
 1 files changed, 4 insertions(+), 2 deletions(-)
kernel/cgroup.c +4 -2
index 83e10f9..3679fb6 100644
@@ -2152,13 +2152,15 @@ int cgroup_attach_proc(struct cgroup *cgrp, struct task_struct *leader)
 		oldcgrp = task_cgroup_from_root(tsk, root);
 		if (cgrp == oldcgrp)
 			continue;
+
+		retval = cgroup_task_migrate(cgrp, oldcgrp, tsk, true);
+		BUG_ON(retval != 0);
+
 		/* attach each task to each subsystem */
 		for_each_subsys(root, ss) {
 			if (ss->attach_task)
 				ss->attach_task(cgrp, tsk);
 		}
-		retval = cgroup_task_migrate(cgrp, oldcgrp, tsk, true);
-		BUG_ON(retval != 0);
 	}
 	/* nothing is sensitive to fork() after this point. */
 
Signature · 1 line
1.7.3.1
author maintainer
#96 lore raw

PATCH 5/10 cgroup: subsys->attach_task() should be called after migration

In reply to Tejun Heo

cgroup_attach_task() calls subsys->attach_task() after cgroup_task_migrate(); however, cgroup_attach_proc() calls it before migration. This actually affects some of the users. Update cgroup_attach_proc() such that ->attach_task() is called after migration.

---
 kernel/cgroup.c |    6 ++++--
 1 files changed, 4 insertions(+), 2 deletions(-)
kernel/cgroup.c +4 -2
index 83e10f9..3679fb6 100644
@@ -2152,13 +2152,15 @@ int cgroup_attach_proc(struct cgroup *cgrp, struct task_struct *leader)
 		oldcgrp = task_cgroup_from_root(tsk, root);
 		if (cgrp == oldcgrp)
 			continue;
+
+		retval = cgroup_task_migrate(cgrp, oldcgrp, tsk, true);
+		BUG_ON(retval != 0);
+
 		/* attach each task to each subsystem */
 		for_each_subsys(root, ss) {
 			if (ss->attach_task)
 				ss->attach_task(cgrp, tsk);
 		}
-		retval = cgroup_task_migrate(cgrp, oldcgrp, tsk, true);
-		BUG_ON(retval != 0);
 	}
 	/* nothing is sensitive to fork() after this point. */
 
Signature · 1 line
1.7.3.1
#97 lore raw

Re: [PATCH 05/10] cgroup: subsys->attach_task() should be called after migration

Quoted 5 lines · Tejun Heo

cgroup_attach_task() calls subsys->attach_task() after cgroup_task_migrate(); however, cgroup_attach_proc() calls it before migration. This actually affects some of the users. Update cgroup_attach_proc() such that ->attach_task() is called after migration.

That's already upstream.

author maintainer
#98 lore raw

Re: [PATCH 05/10] cgroup: subsys->attach_task() should be called after migration

Quoted 8 lines · Frederic Weisbecker
Quoted 5 lines · Tejun Heo

cgroup_attach_task() calls subsys->attach_task() after cgroup_task_migrate(); however, cgroup_attach_proc() calls it before migration. This actually affects some of the users. Update cgroup_attach_proc() such that ->attach_task() is called after migration.

That's already upstream.

Yeap, just dropped this one while rebasing.

Thanks.

Signature · 1 line
tejun
#99 lore raw

Re: [PATCH 05/10] cgroup: subsys->attach_task() should be called after migration

In reply to Frederic Weisbecker
Quoted 8 lines · Frederic Weisbecker
Quoted 5 lines · Tejun Heo

cgroup_attach_task() calls subsys->attach_task() after cgroup_task_migrate(); however, cgroup_attach_proc() calls it before migration. This actually affects some of the users. Update cgroup_attach_proc() such that ->attach_task() is called after migration.

That's already upstream.

Yeap, just dropped this one while rebasing.

Thanks.

Signature · 1 line
tejun

A message that is not in the archive (sent off-list or not yet received): 1320191193-8110-6-git-send-email-tj-DgEjT+Ai2ygdnm+yROfE0A@public.gmane.org

#101 lore raw

Re: [PATCH 05/10] cgroup: subsys->attach_task() should be called after migration

Quoted 5 lines · Tejun Heo

cgroup_attach_task() calls subsys->attach_task() after cgroup_task_migrate(); however, cgroup_attach_proc() calls it before migration. This actually affects some of the users. Update cgroup_attach_proc() such that ->attach_task() is called after migration.

That's already upstream.

author maintainer
#102 lore raw

PATCH 6/10 cgroup: improve old cgroup handling in cgroup_attach_proc()

In reply to Tejun Heo

cgroup_attach_proc() behaves differently from cgroup_attach_task() in the following aspects.

* All hooks are invoked even if no task is actually being moved.

* ->can_attach_task() is called for all tasks in the group whether the new cgrp is different from the current cgrp or not; however, ->attach_task() is skipped if new equals new. This makes the calls asymmetric.

This patch improves old cgroup handling in cgroup_attach_proc() by looking up the current cgroup at the head, recording it in the flex array along with the task itself, and using it to remove the above two differences. This will also ease further changes.

-v2: nr_todo renamed to nr_migrating_tasks as per Paul Menage's
suggestion.

---
 kernel/cgroup.c |   66 +++++++++++++++++++++++++++++++++++--------------------
 1 files changed, 42 insertions(+), 24 deletions(-)
kernel/cgroup.c +42 -24
index 3679fb6..19396d6 100644
@@ -1757,6 +1757,11 @@ int cgroup_path(const struct cgroup *cgrp, char *buf, int buflen)
 }
 EXPORT_SYMBOL_GPL(cgroup_path);
 
+struct task_and_cgroup {
+	struct task_struct	*task;
+	struct cgroup		*cgrp;
+};
+
 /*
  * cgroup_task_migrate - move a task from one cgroup to another.
  *
@@ -2008,15 +2013,15 @@ static int css_set_prefetch(struct cgroup *cgrp, struct css_set *cg,
  */
 int cgroup_attach_proc(struct cgroup *cgrp, struct task_struct *leader)
 {
-	int retval, i, group_size;
+	int retval, i, group_size, nr_migrating_tasks;
 	struct cgroup_subsys *ss, *failed_ss = NULL;
 	bool cancel_failed_ss = false;
 	/* guaranteed to be initialized later, but the compiler needs this */
-	struct cgroup *oldcgrp = NULL;
 	struct css_set *oldcg;
 	struct cgroupfs_root *root = cgrp->root;
 	/* threadgroup list cursor and array */
 	struct task_struct *tsk;
+	struct task_and_cgroup *tc;
 	struct flex_array *group;
 	/*
 	 * we need to make sure we have css_sets for all the tasks we're
@@ -2035,8 +2040,7 @@ int cgroup_attach_proc(struct cgroup *cgrp, struct task_struct *leader)
 	 */
 	group_size = get_nr_threads(leader);
 	/* flex_array supports very large thread-groups better than kmalloc. */
-	group = flex_array_alloc(sizeof(struct task_struct *), group_size,
-				 GFP_KERNEL);
+	group = flex_array_alloc(sizeof(*tc), group_size, GFP_KERNEL);
 	if (!group)
 		return -ENOMEM;
 	/* pre-allocate to guarantee space while iterating in rcu read-side. */
@@ -2060,8 +2064,10 @@ int cgroup_attach_proc(struct cgroup *cgrp, struct task_struct *leader)
 	}
 	/* take a reference on each task in the group to go in the array. */
 	tsk = leader;
-	i = 0;
+	i = nr_migrating_tasks = 0;
 	do {
+		struct task_and_cgroup ent;
+
 		/* @tsk either already exited or can't exit until the end */
 		if (tsk->flags & PF_EXITING)
 			continue;
@@ -2073,14 +2079,23 @@ int cgroup_attach_proc(struct cgroup *cgrp, struct task_struct *leader)
 		 * saying GFP_ATOMIC has no effect here because we did prealloc
 		 * earlier, but it's good form to communicate our expectations.
 		 */
-		retval = flex_array_put_ptr(group, i, tsk, GFP_ATOMIC);
+		ent.task = tsk;
+		ent.cgrp = task_cgroup_from_root(tsk, root);
+		retval = flex_array_put(group, i, &ent, GFP_ATOMIC);
 		BUG_ON(retval != 0);
 		i++;
+		if (ent.cgrp != cgrp)
+			nr_migrating_tasks++;
 	} while_each_thread(leader, tsk);
 	/* remember the number of threads in the array for later. */
 	group_size = i;
 	rcu_read_unlock();
 
+	/* methods shouldn't be called if no task is actually migrating */
+	retval = 0;
+	if (!nr_migrating_tasks)
+		goto out_put_tasks;
+
 	/*
 	 * step 1: check that we can legitimately attach to the cgroup.
 	 */
@@ -2096,8 +2111,10 @@ int cgroup_attach_proc(struct cgroup *cgrp, struct task_struct *leader)
 		if (ss->can_attach_task) {
 			/* run on each task in the threadgroup. */
 			for (i = 0; i < group_size; i++) {
-				tsk = flex_array_get_ptr(group, i);
-				retval = ss->can_attach_task(cgrp, tsk);
+				tc = flex_array_get(group, i);
+				if (tc->cgrp == cgrp)
+					continue;
+				retval = ss->can_attach_task(cgrp, tc->task);
 				if (retval) {
 					failed_ss = ss;
 					cancel_failed_ss = true;
@@ -2113,18 +2130,17 @@ int cgroup_attach_proc(struct cgroup *cgrp, struct task_struct *leader)
 	 */
 	INIT_LIST_HEAD(&newcg_list);
 	for (i = 0; i < group_size; i++) {
-		tsk = flex_array_get_ptr(group, i);
+		tc = flex_array_get(group, i);
 		/* nothing to do if this task is already in the cgroup */
-		oldcgrp = task_cgroup_from_root(tsk, root);
-		if (cgrp == oldcgrp)
+		if (tc->cgrp == cgrp)
 			continue;
 		/* get old css_set pointer */
-		task_lock(tsk);
-		oldcg = tsk->cgroups;
+		task_lock(tc->task);
+		oldcg = tc->task->cgroups;
 		get_css_set(oldcg);
-		task_unlock(tsk);
+		task_unlock(tc->task);
 		/* see if the new one for us is already in the list? */
-		if (css_set_check_fetched(cgrp, tsk, oldcg, &newcg_list)) {
+		if (css_set_check_fetched(cgrp, tc->task, oldcg, &newcg_list)) {
 			/* was already there, nothing to do. */
 			put_css_set(oldcg);
 		} else {
@@ -2147,19 +2163,18 @@ int cgroup_attach_proc(struct cgroup *cgrp, struct task_struct *leader)
 			ss->pre_attach(cgrp);
 	}
 	for (i = 0; i < group_size; i++) {
-		tsk = flex_array_get_ptr(group, i);
+		tc = flex_array_get(group, i);
 		/* leave current thread as it is if it's already there */
-		oldcgrp = task_cgroup_from_root(tsk, root);
-		if (cgrp == oldcgrp)
+		if (tc->cgrp == cgrp)
 			continue;
 
-		retval = cgroup_task_migrate(cgrp, oldcgrp, tsk, true);
+		retval = cgroup_task_migrate(cgrp, tc->cgrp, tc->task, true);
 		BUG_ON(retval != 0);
 
 		/* attach each task to each subsystem */
 		for_each_subsys(root, ss) {
 			if (ss->attach_task)
-				ss->attach_task(cgrp, tsk);
+				ss->attach_task(cgrp, tc->task);
 		}
 	}
 	/* nothing is sensitive to fork() after this point. */
@@ -2170,8 +2185,10 @@ int cgroup_attach_proc(struct cgroup *cgrp, struct task_struct *leader)
 	 * being moved, this call will need to be reworked to communicate that.
 	 */
 	for_each_subsys(root, ss) {
-		if (ss->attach)
-			ss->attach(ss, cgrp, oldcgrp, leader);
+		if (ss->attach) {
+			tc = flex_array_get(group, 0);
+			ss->attach(ss, cgrp, tc->cgrp, tc->task);
+		}
 	}
 
 	/*
@@ -2200,10 +2217,11 @@ out_cancel_attach:
 				ss->cancel_attach(ss, cgrp, leader);
 		}
 	}
+out_put_tasks:
 	/* clean up the array of referenced threads in the group. */
 	for (i = 0; i < group_size; i++) {
-		tsk = flex_array_get_ptr(group, i);
-		put_task_struct(tsk);
+		tc = flex_array_get(group, i);
+		put_task_struct(tc->task);
 	}
 out_free_group_list:
 	flex_array_free(group);
Signature · 1 line
1.7.3.1
Reviewed-by on v1 6/10
#103 lore raw

Re: [PATCH 06/10] cgroup: improve old cgroup handling in cgroup_attach_proc()

Quoted 15 lines · Tejun Heo

cgroup_attach_proc() behaves differently from cgroup_attach_task() in the following aspects.

* All hooks are invoked even if no task is actually being moved.

* ->can_attach_task() is called for all tasks in the group whether the new cgrp is different from the current cgrp or not; however, ->attach_task() is skipped if new equals new. This makes the calls asymmetric.

This patch improves old cgroup handling in cgroup_attach_proc() by looking up the current cgroup at the head, recording it in the flex array along with the task itself, and using it to remove the above two differences. This will also ease further changes.

Quoted 7 lines

-v2: nr_todo renamed to nr_migrating_tasks as per Paul Menage's
suggestion.

Reviewed-by on v1 6/10
#104 lore raw

Re: [PATCH 06/10] cgroup: improve old cgroup handling in cgroup_attach_proc()

In reply to Tejun Heo
Quoted 15 lines · Tejun Heo

cgroup_attach_proc() behaves differently from cgroup_attach_task() in the following aspects.

* All hooks are invoked even if no task is actually being moved.

* ->can_attach_task() is called for all tasks in the group whether the new cgrp is different from the current cgrp or not; however, ->attach_task() is skipped if new equals new. This makes the calls asymmetric.

This patch improves old cgroup handling in cgroup_attach_proc() by looking up the current cgroup at the head, recording it in the flex array along with the task itself, and using it to remove the above two differences. This will also ease further changes.

Quoted 7 lines

-v2: nr_todo renamed to nr_migrating_tasks as per Paul Menage's
suggestion.

author maintainer
#105 lore raw

PATCH 6/10 cgroup: improve old cgroup handling in cgroup_attach_proc()

In reply to Tejun Heo

cgroup_attach_proc() behaves differently from cgroup_attach_task() in the following aspects.

* All hooks are invoked even if no task is actually being moved.

* ->can_attach_task() is called for all tasks in the group whether the new cgrp is different from the current cgrp or not; however, ->attach_task() is skipped if new equals new. This makes the calls asymmetric.

This patch improves old cgroup handling in cgroup_attach_proc() by looking up the current cgroup at the head, recording it in the flex array along with the task itself, and using it to remove the above two differences. This will also ease further changes.

-v2: nr_todo renamed to nr_migrating_tasks as per Paul Menage's
suggestion.

---
 kernel/cgroup.c |   66 +++++++++++++++++++++++++++++++++++--------------------
 1 files changed, 42 insertions(+), 24 deletions(-)
kernel/cgroup.c +42 -24
index 3679fb6..19396d6 100644
@@ -1757,6 +1757,11 @@ int cgroup_path(const struct cgroup *cgrp, char *buf, int buflen)
 }
 EXPORT_SYMBOL_GPL(cgroup_path);
 
+struct task_and_cgroup {
+	struct task_struct	*task;
+	struct cgroup		*cgrp;
+};
+
 /*
  * cgroup_task_migrate - move a task from one cgroup to another.
  *
@@ -2008,15 +2013,15 @@ static int css_set_prefetch(struct cgroup *cgrp, struct css_set *cg,
  */
 int cgroup_attach_proc(struct cgroup *cgrp, struct task_struct *leader)
 {
-	int retval, i, group_size;
+	int retval, i, group_size, nr_migrating_tasks;
 	struct cgroup_subsys *ss, *failed_ss = NULL;
 	bool cancel_failed_ss = false;
 	/* guaranteed to be initialized later, but the compiler needs this */
-	struct cgroup *oldcgrp = NULL;
 	struct css_set *oldcg;
 	struct cgroupfs_root *root = cgrp->root;
 	/* threadgroup list cursor and array */
 	struct task_struct *tsk;
+	struct task_and_cgroup *tc;
 	struct flex_array *group;
 	/*
 	 * we need to make sure we have css_sets for all the tasks we're
@@ -2035,8 +2040,7 @@ int cgroup_attach_proc(struct cgroup *cgrp, struct task_struct *leader)
 	 */
 	group_size = get_nr_threads(leader);
 	/* flex_array supports very large thread-groups better than kmalloc. */
-	group = flex_array_alloc(sizeof(struct task_struct *), group_size,
-				 GFP_KERNEL);
+	group = flex_array_alloc(sizeof(*tc), group_size, GFP_KERNEL);
 	if (!group)
 		return -ENOMEM;
 	/* pre-allocate to guarantee space while iterating in rcu read-side. */
@@ -2060,8 +2064,10 @@ int cgroup_attach_proc(struct cgroup *cgrp, struct task_struct *leader)
 	}
 	/* take a reference on each task in the group to go in the array. */
 	tsk = leader;
-	i = 0;
+	i = nr_migrating_tasks = 0;
 	do {
+		struct task_and_cgroup ent;
+
 		/* @tsk either already exited or can't exit until the end */
 		if (tsk->flags & PF_EXITING)
 			continue;
@@ -2073,14 +2079,23 @@ int cgroup_attach_proc(struct cgroup *cgrp, struct task_struct *leader)
 		 * saying GFP_ATOMIC has no effect here because we did prealloc
 		 * earlier, but it's good form to communicate our expectations.
 		 */
-		retval = flex_array_put_ptr(group, i, tsk, GFP_ATOMIC);
+		ent.task = tsk;
+		ent.cgrp = task_cgroup_from_root(tsk, root);
+		retval = flex_array_put(group, i, &ent, GFP_ATOMIC);
 		BUG_ON(retval != 0);
 		i++;
+		if (ent.cgrp != cgrp)
+			nr_migrating_tasks++;
 	} while_each_thread(leader, tsk);
 	/* remember the number of threads in the array for later. */
 	group_size = i;
 	rcu_read_unlock();
 
+	/* methods shouldn't be called if no task is actually migrating */
+	retval = 0;
+	if (!nr_migrating_tasks)
+		goto out_put_tasks;
+
 	/*
 	 * step 1: check that we can legitimately attach to the cgroup.
 	 */
@@ -2096,8 +2111,10 @@ int cgroup_attach_proc(struct cgroup *cgrp, struct task_struct *leader)
 		if (ss->can_attach_task) {
 			/* run on each task in the threadgroup. */
 			for (i = 0; i < group_size; i++) {
-				tsk = flex_array_get_ptr(group, i);
-				retval = ss->can_attach_task(cgrp, tsk);
+				tc = flex_array_get(group, i);
+				if (tc->cgrp == cgrp)
+					continue;
+				retval = ss->can_attach_task(cgrp, tc->task);
 				if (retval) {
 					failed_ss = ss;
 					cancel_failed_ss = true;
@@ -2113,18 +2130,17 @@ int cgroup_attach_proc(struct cgroup *cgrp, struct task_struct *leader)
 	 */
 	INIT_LIST_HEAD(&newcg_list);
 	for (i = 0; i < group_size; i++) {
-		tsk = flex_array_get_ptr(group, i);
+		tc = flex_array_get(group, i);
 		/* nothing to do if this task is already in the cgroup */
-		oldcgrp = task_cgroup_from_root(tsk, root);
-		if (cgrp == oldcgrp)
+		if (tc->cgrp == cgrp)
 			continue;
 		/* get old css_set pointer */
-		task_lock(tsk);
-		oldcg = tsk->cgroups;
+		task_lock(tc->task);
+		oldcg = tc->task->cgroups;
 		get_css_set(oldcg);
-		task_unlock(tsk);
+		task_unlock(tc->task);
 		/* see if the new one for us is already in the list? */
-		if (css_set_check_fetched(cgrp, tsk, oldcg, &newcg_list)) {
+		if (css_set_check_fetched(cgrp, tc->task, oldcg, &newcg_list)) {
 			/* was already there, nothing to do. */
 			put_css_set(oldcg);
 		} else {
@@ -2147,19 +2163,18 @@ int cgroup_attach_proc(struct cgroup *cgrp, struct task_struct *leader)
 			ss->pre_attach(cgrp);
 	}
 	for (i = 0; i < group_size; i++) {
-		tsk = flex_array_get_ptr(group, i);
+		tc = flex_array_get(group, i);
 		/* leave current thread as it is if it's already there */
-		oldcgrp = task_cgroup_from_root(tsk, root);
-		if (cgrp == oldcgrp)
+		if (tc->cgrp == cgrp)
 			continue;
 
-		retval = cgroup_task_migrate(cgrp, oldcgrp, tsk, true);
+		retval = cgroup_task_migrate(cgrp, tc->cgrp, tc->task, true);
 		BUG_ON(retval != 0);
 
 		/* attach each task to each subsystem */
 		for_each_subsys(root, ss) {
 			if (ss->attach_task)
-				ss->attach_task(cgrp, tsk);
+				ss->attach_task(cgrp, tc->task);
 		}
 	}
 	/* nothing is sensitive to fork() after this point. */
@@ -2170,8 +2185,10 @@ int cgroup_attach_proc(struct cgroup *cgrp, struct task_struct *leader)
 	 * being moved, this call will need to be reworked to communicate that.
 	 */
 	for_each_subsys(root, ss) {
-		if (ss->attach)
-			ss->attach(ss, cgrp, oldcgrp, leader);
+		if (ss->attach) {
+			tc = flex_array_get(group, 0);
+			ss->attach(ss, cgrp, tc->cgrp, tc->task);
+		}
 	}
 
 	/*
@@ -2200,10 +2217,11 @@ out_cancel_attach:
 				ss->cancel_attach(ss, cgrp, leader);
 		}
 	}
+out_put_tasks:
 	/* clean up the array of referenced threads in the group. */
 	for (i = 0; i < group_size; i++) {
-		tsk = flex_array_get_ptr(group, i);
-		put_task_struct(tsk);
+		tc = flex_array_get(group, i);
+		put_task_struct(tc->task);
 	}
 out_free_group_list:
 	flex_array_free(group);
Signature · 1 line
1.7.3.1
author maintainer
#106 lore raw

PATCH 7/10 cgroup: introduce cgroup_taskset and use it in subsys->can_attach(), cancel_attach() and attach()

In reply to Tejun Heo

Currently, there's no way to pass multiple tasks to cgroup_subsys methods necessitating the need for separate per-process and per-task methods. This patch introduces cgroup_taskset which can be used to pass multiple tasks and their associated cgroups to cgroup_subsys methods.

Three methods - can_attach(), cancel_attach() and attach() - are converted to use cgroup_taskset. This unifies passed parameters so that all methods have access to all information. Conversions in this patchset are identical and don't introduce any behavior change.

-v2: documentation updated as per Paul Menage's suggestion.

Cc Li Zefan <[email protected]>
Cc Balbir Singh <[email protected]>
Cc Daisuke Nishimura <[email protected]>
Cc KAMEZAWA Hiroyuki <[email protected]>
Cc James Morris <jmorris-gx6/[email protected]>
---
 Documentation/cgroups/cgroups.txt |   31 ++++++++----
 include/linux/cgroup.h            |   28 +++++++++-
 kernel/cgroup.c                   |   99 +++++++++++++++++++++++++++++++++----
 kernel/cgroup_freezer.c           |    2 +-
 kernel/cpuset.c                   |   18 ++++---
 mm/memcontrol.c                   |   16 +++---
 security/device_cgroup.c          |    7 ++-
 7 files changed, 158 insertions(+), 43 deletions(-)
Documentation/cgroups/cgroups.txt +21 -10
index cd67e90..bf5d6c9 100644
@@ -594,15 +594,25 @@ rmdir() will fail with it. From this behavior, pre_destroy() can be
 called multiple times against a cgroup.
 
 int can_attach(struct cgroup_subsys *ss, struct cgroup *cgrp,
-	       struct task_struct *task)
+	       struct cgroup_taskset *tset)
 (cgroup_mutex held by caller)
 
-Called prior to moving a task into a cgroup; if the subsystem
-returns an error, this will abort the attach operation.  If a NULL
-task is passed, then a successful result indicates that *any*
-unspecified task can be moved into the cgroup. Note that this isn't
-called on a fork. If this method returns 0 (success) then this should
-remain valid while the caller holds cgroup_mutex and it is ensured that either
+Called prior to moving one or more tasks into a cgroup; if the
+subsystem returns an error, this will abort the attach operation.
+@tset contains the tasks to be attached and is guaranteed to have at
+least one task in it.
+
+If there are multiple tasks in the taskset, then:
+  - it's guaranteed that all are from the same thread group
+  - @tset contains all tasks from the thread group whether or not
+    they're switching cgroups
+  - the first task is the leader
+
+Each @tset entry also contains the task's old cgroup and tasks which
+aren't switching cgroup can be skipped easily using the
+cgroup_taskset_for_each() iterator. Note that this isn't called on a
+fork. If this method returns 0 (success) then this should remain valid
+while the caller holds cgroup_mutex and it is ensured that either
 attach() or cancel_attach() will be called in future.
 
 int can_attach_task(struct cgroup *cgrp, struct task_struct *tsk);
@@ -613,14 +623,14 @@ attached (possibly many when using cgroup_attach_proc). Called after
 can_attach.
 
 void cancel_attach(struct cgroup_subsys *ss, struct cgroup *cgrp,
-	       struct task_struct *task, bool threadgroup)
+		   struct cgroup_taskset *tset)
 (cgroup_mutex held by caller)
 
 Called when a task attach operation has failed after can_attach() has succeeded.
 A subsystem whose can_attach() has some side-effects should provide this
 function, so that the subsystem can implement a rollback. If not, not necessary.
 This will be called only about subsystems whose can_attach() operation have
-succeeded.
+succeeded. The parameters are identical to can_attach().
 
 void pre_attach(struct cgroup *cgrp);
 (cgroup_mutex held by caller)
@@ -629,11 +639,12 @@ For any non-per-thread attachment work that needs to happen before
 attach_task. Needed by cpuset.
 
 void attach(struct cgroup_subsys *ss, struct cgroup *cgrp,
-	    struct cgroup *old_cgrp, struct task_struct *task)
+	    struct cgroup_taskset *tset)
 (cgroup_mutex held by caller)
 
 Called after the task has been attached to the cgroup, to allow any
 post-attachment activity that requires memory allocations or blocking.
+The parameters are identical to can_attach().
 
 void attach_task(struct cgroup *cgrp, struct task_struct *tsk);
 (cgroup_mutex held by caller)
include/linux/cgroup.h +25 -3
index da7e4bc..2470c8e 100644
@@ -457,6 +457,28 @@ void cgroup_exclude_rmdir(struct cgroup_subsys_state *css);
 void cgroup_release_and_wakeup_rmdir(struct cgroup_subsys_state *css);
 
 /*
+ * Control Group taskset, used to pass around set of tasks to cgroup_subsys
+ * methods.
+ */
+struct cgroup_taskset;
+struct task_struct *cgroup_taskset_first(struct cgroup_taskset *tset);
+struct task_struct *cgroup_taskset_next(struct cgroup_taskset *tset);
+struct cgroup *cgroup_taskset_cur_cgroup(struct cgroup_taskset *tset);
+int cgroup_taskset_size(struct cgroup_taskset *tset);
+
+/**
+ * cgroup_taskset_for_each - iterate cgroup_taskset
+ * @task: the loop cursor
+ * @skip_cgrp: skip if task's cgroup matches this, %NULL to iterate through all
+ * @tset: taskset to iterate
+ */
+#define cgroup_taskset_for_each(task, skip_cgrp, tset)			\
+	for ((task) = cgroup_taskset_first((tset)); (task);		\
+	     (task) = cgroup_taskset_next((tset)))			\
+		if (!(skip_cgrp) ||					\
+		    cgroup_taskset_cur_cgroup((tset)) != (skip_cgrp))
+
+/*
  * Control Group subsystem type.
  * See Documentation/cgroups/cgroups.txt for details
  */
@@ -467,14 +489,14 @@ struct cgroup_subsys {
 	int (*pre_destroy)(struct cgroup_subsys *ss, struct cgroup *cgrp);
 	void (*destroy)(struct cgroup_subsys *ss, struct cgroup *cgrp);
 	int (*can_attach)(struct cgroup_subsys *ss, struct cgroup *cgrp,
-			  struct task_struct *tsk);
+			  struct cgroup_taskset *tset);
 	int (*can_attach_task)(struct cgroup *cgrp, struct task_struct *tsk);
 	void (*cancel_attach)(struct cgroup_subsys *ss, struct cgroup *cgrp,
-			      struct task_struct *tsk);
+			      struct cgroup_taskset *tset);
 	void (*pre_attach)(struct cgroup *cgrp);
 	void (*attach_task)(struct cgroup *cgrp, struct task_struct *tsk);
 	void (*attach)(struct cgroup_subsys *ss, struct cgroup *cgrp,
-		       struct cgroup *old_cgrp, struct task_struct *tsk);
+		       struct cgroup_taskset *tset);
 	void (*fork)(struct cgroup_subsys *ss, struct task_struct *task);
 	void (*exit)(struct cgroup_subsys *ss, struct cgroup *cgrp,
 			struct cgroup *old_cgrp, struct task_struct *task);
kernel/cgroup.c +89 -10

169 lines not on this page · raw message

kernel/cgroup_freezer.c +1 -1
index e691818..a72d5fa 100644
@@ -160,7 +160,7 @@ static void freezer_destroy(struct cgroup_subsys *ss,
  */
 static int freezer_can_attach(struct cgroup_subsys *ss,
 			      struct cgroup *new_cgroup,
-			      struct task_struct *task)
+			      struct cgroup_taskset *tset)
 {
 	struct freezer *freezer;
 
kernel/cpuset.c +10 -8
index 10131fd..2e5825b 100644
@@ -1368,10 +1368,10 @@ static int fmeter_getrate(struct fmeter *fmp)
 }
 
 /* Called by cgroups to determine if a cpuset is usable; cgroup_mutex held */
-static int cpuset_can_attach(struct cgroup_subsys *ss, struct cgroup *cont,
-			     struct task_struct *tsk)
+static int cpuset_can_attach(struct cgroup_subsys *ss, struct cgroup *cgrp,
+			     struct cgroup_taskset *tset)
 {
-	struct cpuset *cs = cgroup_cs(cont);
+	struct cpuset *cs = cgroup_cs(cgrp);
 
 	if (cpumask_empty(cs->cpus_allowed) || nodes_empty(cs->mems_allowed))
 		return -ENOSPC;
@@ -1384,7 +1384,7 @@ static int cpuset_can_attach(struct cgroup_subsys *ss, struct cgroup *cont,
 	 * set_cpus_allowed_ptr() on all attached tasks before cpus_allowed may
 	 * be changed.
 	 */
-	if (tsk->flags & PF_THREAD_BOUND)
+	if (cgroup_taskset_first(tset)->flags & PF_THREAD_BOUND)
 		return -EINVAL;
 
 	return 0;
@@ -1434,12 +1434,14 @@ static void cpuset_attach_task(struct cgroup *cont, struct task_struct *tsk)
 	cpuset_update_task_spread_flag(cs, tsk);
 }
 
-static void cpuset_attach(struct cgroup_subsys *ss, struct cgroup *cont,
-			  struct cgroup *oldcont, struct task_struct *tsk)
+static void cpuset_attach(struct cgroup_subsys *ss, struct cgroup *cgrp,
+			  struct cgroup_taskset *tset)
 {
 	struct mm_struct *mm;
-	struct cpuset *cs = cgroup_cs(cont);
-	struct cpuset *oldcs = cgroup_cs(oldcont);
+	struct task_struct *tsk = cgroup_taskset_first(tset);
+	struct cgroup *oldcgrp = cgroup_taskset_cur_cgroup(tset);
+	struct cpuset *cs = cgroup_cs(cgrp);
+	struct cpuset *oldcs = cgroup_cs(oldcgrp);
 
 	/*
 	 * Change mm, possibly for multiple threads in a threadgroup. This is
mm/memcontrol.c +8 -8
index 3508777..5019787 100644
@@ -5288,8 +5288,9 @@ static void mem_cgroup_clear_mc(void)
 
 static int mem_cgroup_can_attach(struct cgroup_subsys *ss,
 				struct cgroup *cgroup,
-				struct task_struct *p)
+				struct cgroup_taskset *tset)
 {
+	struct task_struct *p = cgroup_taskset_first(tset);
 	int ret = 0;
 	struct mem_cgroup *mem = mem_cgroup_from_cont(cgroup);
 
@@ -5327,7 +5328,7 @@ static int mem_cgroup_can_attach(struct cgroup_subsys *ss,
 
 static void mem_cgroup_cancel_attach(struct cgroup_subsys *ss,
 				struct cgroup *cgroup,
-				struct task_struct *p)
+				struct cgroup_taskset *tset)
 {
 	mem_cgroup_clear_mc();
 }
@@ -5444,9 +5445,9 @@ retry:
 
 static void mem_cgroup_move_task(struct cgroup_subsys *ss,
 				struct cgroup *cont,
-				struct cgroup *old_cont,
-				struct task_struct *p)
+				struct cgroup_taskset *tset)
 {
+	struct task_struct *p = cgroup_taskset_first(tset);
 	struct mm_struct *mm = get_task_mm(p);
 
 	if (mm) {
@@ -5461,19 +5462,18 @@ static void mem_cgroup_move_task(struct cgroup_subsys *ss,
 #else	/* !CONFIG_MMU */
 static int mem_cgroup_can_attach(struct cgroup_subsys *ss,
 				struct cgroup *cgroup,
-				struct task_struct *p)
+				struct cgroup_taskset *tset)
 {
 	return 0;
 }
 static void mem_cgroup_cancel_attach(struct cgroup_subsys *ss,
 				struct cgroup *cgroup,
-				struct task_struct *p)
+				struct cgroup_taskset *tset)
 {
 }
 static void mem_cgroup_move_task(struct cgroup_subsys *ss,
 				struct cgroup *cont,
-				struct cgroup *old_cont,
-				struct task_struct *p)
+				struct cgroup_taskset *tset)
 {
 }
 #endif
security/device_cgroup.c +4 -3
index 4450fbe..8b5b5d8 100644
@@ -62,11 +62,12 @@ static inline struct dev_cgroup *task_devcgroup(struct task_struct *task)
 struct cgroup_subsys devices_subsys;
 
 static int devcgroup_can_attach(struct cgroup_subsys *ss,
-		struct cgroup *new_cgroup, struct task_struct *task)
+			struct cgroup *new_cgrp, struct cgroup_taskset *set)
 {
-	if (current != task && !capable(CAP_SYS_ADMIN))
-			return -EPERM;
+	struct task_struct *task = cgroup_taskset_first(set);
 
+	if (current != task && !capable(CAP_SYS_ADMIN))
+		return -EPERM;
 	return 0;
 }
 
Signature · 1 line
1.7.3.1
Reviewed-by on v1 7/10
#107 lore raw

Re: [PATCH 07/10] cgroup: introduce cgroup_taskset and use it in subsys->can_attach(), cancel_attach() and attach()

Quoted 13 lines · Tejun Heo

Currently, there's no way to pass multiple tasks to cgroup_subsys methods necessitating the need for separate per-process and per-task methods. This patch introduces cgroup_taskset which can be used to pass multiple tasks and their associated cgroups to cgroup_subsys methods.

Three methods - can_attach(), cancel_attach() and attach() - are converted to use cgroup_taskset. This unifies passed parameters so that all methods have access to all information. Conversions in this patchset are identical and don't introduce any behavior change.

-v2: documentation updated as per Paul Menage's suggestion.

Quoted 8 lines
Cc Li Zefan <[email protected]>
Cc Balbir Singh <[email protected]>
Cc Daisuke Nishimura <[email protected]>
Cc KAMEZAWA Hiroyuki <[email protected]>
Cc James Morris <jmorris-gx6/[email protected]>
Reviewed-by on v1 7/10
#108 lore raw

Re: [PATCH 07/10] cgroup: introduce cgroup_taskset and use it in subsys->can_attach(), cancel_attach() and attach()

In reply to Tejun Heo
Quoted 13 lines · Tejun Heo

Currently, there's no way to pass multiple tasks to cgroup_subsys methods necessitating the need for separate per-process and per-task methods. This patch introduces cgroup_taskset which can be used to pass multiple tasks and their associated cgroups to cgroup_subsys methods.

Three methods - can_attach(), cancel_attach() and attach() - are converted to use cgroup_taskset. This unifies passed parameters so that all methods have access to all information. Conversions in this patchset are identical and don't introduce any behavior change.

-v2: documentation updated as per Paul Menage's suggestion.

Quoted 8 lines
Cc Li Zefan <[email protected]>
Cc Balbir Singh <[email protected]>
Cc Daisuke Nishimura <[email protected]>
Cc KAMEZAWA Hiroyuki <[email protected]>
Cc James Morris <[email protected]>
author maintainer
#109 lore raw

PATCH 7/10 cgroup: introduce cgroup_taskset and use it in subsys->can_attach(), cancel_attach() and attach()

In reply to Tejun Heo

Currently, there's no way to pass multiple tasks to cgroup_subsys methods necessitating the need for separate per-process and per-task methods. This patch introduces cgroup_taskset which can be used to pass multiple tasks and their associated cgroups to cgroup_subsys methods.

Three methods - can_attach(), cancel_attach() and attach() - are converted to use cgroup_taskset. This unifies passed parameters so that all methods have access to all information. Conversions in this patchset are identical and don't introduce any behavior change.

-v2: documentation updated as per Paul Menage's suggestion.

Cc Li Zefan <[email protected]>
Cc Balbir Singh <[email protected]>
Cc Daisuke Nishimura <[email protected]>
Cc KAMEZAWA Hiroyuki <[email protected]>
Cc James Morris <[email protected]>
---
 Documentation/cgroups/cgroups.txt |   31 ++++++++----
 include/linux/cgroup.h            |   28 +++++++++-
 kernel/cgroup.c                   |   99 +++++++++++++++++++++++++++++++++----
 kernel/cgroup_freezer.c           |    2 +-
 kernel/cpuset.c                   |   18 ++++---
 mm/memcontrol.c                   |   16 +++---
 security/device_cgroup.c          |    7 ++-
 7 files changed, 158 insertions(+), 43 deletions(-)
Documentation/cgroups/cgroups.txt +21 -10
index cd67e90..bf5d6c9 100644
@@ -594,15 +594,25 @@ rmdir() will fail with it. From this behavior, pre_destroy() can be
 called multiple times against a cgroup.
 
 int can_attach(struct cgroup_subsys *ss, struct cgroup *cgrp,
-	       struct task_struct *task)
+	       struct cgroup_taskset *tset)
 (cgroup_mutex held by caller)
 
-Called prior to moving a task into a cgroup; if the subsystem
-returns an error, this will abort the attach operation.  If a NULL
-task is passed, then a successful result indicates that *any*
-unspecified task can be moved into the cgroup. Note that this isn't
-called on a fork. If this method returns 0 (success) then this should
-remain valid while the caller holds cgroup_mutex and it is ensured that either
+Called prior to moving one or more tasks into a cgroup; if the
+subsystem returns an error, this will abort the attach operation.
+@tset contains the tasks to be attached and is guaranteed to have at
+least one task in it.
+
+If there are multiple tasks in the taskset, then:
+  - it's guaranteed that all are from the same thread group
+  - @tset contains all tasks from the thread group whether or not
+    they're switching cgroups
+  - the first task is the leader
+
+Each @tset entry also contains the task's old cgroup and tasks which
+aren't switching cgroup can be skipped easily using the
+cgroup_taskset_for_each() iterator. Note that this isn't called on a
+fork. If this method returns 0 (success) then this should remain valid
+while the caller holds cgroup_mutex and it is ensured that either
 attach() or cancel_attach() will be called in future.
 
 int can_attach_task(struct cgroup *cgrp, struct task_struct *tsk);
@@ -613,14 +623,14 @@ attached (possibly many when using cgroup_attach_proc). Called after
 can_attach.
 
 void cancel_attach(struct cgroup_subsys *ss, struct cgroup *cgrp,
-	       struct task_struct *task, bool threadgroup)
+		   struct cgroup_taskset *tset)
 (cgroup_mutex held by caller)
 
 Called when a task attach operation has failed after can_attach() has succeeded.
 A subsystem whose can_attach() has some side-effects should provide this
 function, so that the subsystem can implement a rollback. If not, not necessary.
 This will be called only about subsystems whose can_attach() operation have
-succeeded.
+succeeded. The parameters are identical to can_attach().
 
 void pre_attach(struct cgroup *cgrp);
 (cgroup_mutex held by caller)
@@ -629,11 +639,12 @@ For any non-per-thread attachment work that needs to happen before
 attach_task. Needed by cpuset.
 
 void attach(struct cgroup_subsys *ss, struct cgroup *cgrp,
-	    struct cgroup *old_cgrp, struct task_struct *task)
+	    struct cgroup_taskset *tset)
 (cgroup_mutex held by caller)
 
 Called after the task has been attached to the cgroup, to allow any
 post-attachment activity that requires memory allocations or blocking.
+The parameters are identical to can_attach().
 
 void attach_task(struct cgroup *cgrp, struct task_struct *tsk);
 (cgroup_mutex held by caller)
include/linux/cgroup.h +25 -3
index da7e4bc..2470c8e 100644
@@ -457,6 +457,28 @@ void cgroup_exclude_rmdir(struct cgroup_subsys_state *css);
 void cgroup_release_and_wakeup_rmdir(struct cgroup_subsys_state *css);
 
 /*
+ * Control Group taskset, used to pass around set of tasks to cgroup_subsys
+ * methods.
+ */
+struct cgroup_taskset;
+struct task_struct *cgroup_taskset_first(struct cgroup_taskset *tset);
+struct task_struct *cgroup_taskset_next(struct cgroup_taskset *tset);
+struct cgroup *cgroup_taskset_cur_cgroup(struct cgroup_taskset *tset);
+int cgroup_taskset_size(struct cgroup_taskset *tset);
+
+/**
+ * cgroup_taskset_for_each - iterate cgroup_taskset
+ * @task: the loop cursor
+ * @skip_cgrp: skip if task's cgroup matches this, %NULL to iterate through all
+ * @tset: taskset to iterate
+ */
+#define cgroup_taskset_for_each(task, skip_cgrp, tset)			\
+	for ((task) = cgroup_taskset_first((tset)); (task);		\
+	     (task) = cgroup_taskset_next((tset)))			\
+		if (!(skip_cgrp) ||					\
+		    cgroup_taskset_cur_cgroup((tset)) != (skip_cgrp))
+
+/*
  * Control Group subsystem type.
  * See Documentation/cgroups/cgroups.txt for details
  */
@@ -467,14 +489,14 @@ struct cgroup_subsys {
 	int (*pre_destroy)(struct cgroup_subsys *ss, struct cgroup *cgrp);
 	void (*destroy)(struct cgroup_subsys *ss, struct cgroup *cgrp);
 	int (*can_attach)(struct cgroup_subsys *ss, struct cgroup *cgrp,
-			  struct task_struct *tsk);
+			  struct cgroup_taskset *tset);
 	int (*can_attach_task)(struct cgroup *cgrp, struct task_struct *tsk);
 	void (*cancel_attach)(struct cgroup_subsys *ss, struct cgroup *cgrp,
-			      struct task_struct *tsk);
+			      struct cgroup_taskset *tset);
 	void (*pre_attach)(struct cgroup *cgrp);
 	void (*attach_task)(struct cgroup *cgrp, struct task_struct *tsk);
 	void (*attach)(struct cgroup_subsys *ss, struct cgroup *cgrp,
-		       struct cgroup *old_cgrp, struct task_struct *tsk);
+		       struct cgroup_taskset *tset);
 	void (*fork)(struct cgroup_subsys *ss, struct task_struct *task);
 	void (*exit)(struct cgroup_subsys *ss, struct cgroup *cgrp,
 			struct cgroup *old_cgrp, struct task_struct *task);
kernel/cgroup.c +89 -10

169 lines not on this page · raw message

kernel/cgroup_freezer.c +1 -1
index e691818..a72d5fa 100644
@@ -160,7 +160,7 @@ static void freezer_destroy(struct cgroup_subsys *ss,
  */
 static int freezer_can_attach(struct cgroup_subsys *ss,
 			      struct cgroup *new_cgroup,
-			      struct task_struct *task)
+			      struct cgroup_taskset *tset)
 {
 	struct freezer *freezer;
 
kernel/cpuset.c +10 -8
index 10131fd..2e5825b 100644
@@ -1368,10 +1368,10 @@ static int fmeter_getrate(struct fmeter *fmp)
 }
 
 /* Called by cgroups to determine if a cpuset is usable; cgroup_mutex held */
-static int cpuset_can_attach(struct cgroup_subsys *ss, struct cgroup *cont,
-			     struct task_struct *tsk)
+static int cpuset_can_attach(struct cgroup_subsys *ss, struct cgroup *cgrp,
+			     struct cgroup_taskset *tset)
 {
-	struct cpuset *cs = cgroup_cs(cont);
+	struct cpuset *cs = cgroup_cs(cgrp);
 
 	if (cpumask_empty(cs->cpus_allowed) || nodes_empty(cs->mems_allowed))
 		return -ENOSPC;
@@ -1384,7 +1384,7 @@ static int cpuset_can_attach(struct cgroup_subsys *ss, struct cgroup *cont,
 	 * set_cpus_allowed_ptr() on all attached tasks before cpus_allowed may
 	 * be changed.
 	 */
-	if (tsk->flags & PF_THREAD_BOUND)
+	if (cgroup_taskset_first(tset)->flags & PF_THREAD_BOUND)
 		return -EINVAL;
 
 	return 0;
@@ -1434,12 +1434,14 @@ static void cpuset_attach_task(struct cgroup *cont, struct task_struct *tsk)
 	cpuset_update_task_spread_flag(cs, tsk);
 }
 
-static void cpuset_attach(struct cgroup_subsys *ss, struct cgroup *cont,
-			  struct cgroup *oldcont, struct task_struct *tsk)
+static void cpuset_attach(struct cgroup_subsys *ss, struct cgroup *cgrp,
+			  struct cgroup_taskset *tset)
 {
 	struct mm_struct *mm;
-	struct cpuset *cs = cgroup_cs(cont);
-	struct cpuset *oldcs = cgroup_cs(oldcont);
+	struct task_struct *tsk = cgroup_taskset_first(tset);
+	struct cgroup *oldcgrp = cgroup_taskset_cur_cgroup(tset);
+	struct cpuset *cs = cgroup_cs(cgrp);
+	struct cpuset *oldcs = cgroup_cs(oldcgrp);
 
 	/*
 	 * Change mm, possibly for multiple threads in a threadgroup. This is
mm/memcontrol.c +8 -8
index 3508777..5019787 100644
@@ -5288,8 +5288,9 @@ static void mem_cgroup_clear_mc(void)
 
 static int mem_cgroup_can_attach(struct cgroup_subsys *ss,
 				struct cgroup *cgroup,
-				struct task_struct *p)
+				struct cgroup_taskset *tset)
 {
+	struct task_struct *p = cgroup_taskset_first(tset);
 	int ret = 0;
 	struct mem_cgroup *mem = mem_cgroup_from_cont(cgroup);
 
@@ -5327,7 +5328,7 @@ static int mem_cgroup_can_attach(struct cgroup_subsys *ss,
 
 static void mem_cgroup_cancel_attach(struct cgroup_subsys *ss,
 				struct cgroup *cgroup,
-				struct task_struct *p)
+				struct cgroup_taskset *tset)
 {
 	mem_cgroup_clear_mc();
 }
@@ -5444,9 +5445,9 @@ retry:
 
 static void mem_cgroup_move_task(struct cgroup_subsys *ss,
 				struct cgroup *cont,
-				struct cgroup *old_cont,
-				struct task_struct *p)
+				struct cgroup_taskset *tset)
 {
+	struct task_struct *p = cgroup_taskset_first(tset);
 	struct mm_struct *mm = get_task_mm(p);
 
 	if (mm) {
@@ -5461,19 +5462,18 @@ static void mem_cgroup_move_task(struct cgroup_subsys *ss,
 #else	/* !CONFIG_MMU */
 static int mem_cgroup_can_attach(struct cgroup_subsys *ss,
 				struct cgroup *cgroup,
-				struct task_struct *p)
+				struct cgroup_taskset *tset)
 {
 	return 0;
 }
 static void mem_cgroup_cancel_attach(struct cgroup_subsys *ss,
 				struct cgroup *cgroup,
-				struct task_struct *p)
+				struct cgroup_taskset *tset)
 {
 }
 static void mem_cgroup_move_task(struct cgroup_subsys *ss,
 				struct cgroup *cont,
-				struct cgroup *old_cont,
-				struct task_struct *p)
+				struct cgroup_taskset *tset)
 {
 }
 #endif
security/device_cgroup.c +4 -3
index 4450fbe..8b5b5d8 100644
@@ -62,11 +62,12 @@ static inline struct dev_cgroup *task_devcgroup(struct task_struct *task)
 struct cgroup_subsys devices_subsys;
 
 static int devcgroup_can_attach(struct cgroup_subsys *ss,
-		struct cgroup *new_cgroup, struct task_struct *task)
+			struct cgroup *new_cgrp, struct cgroup_taskset *set)
 {
-	if (current != task && !capable(CAP_SYS_ADMIN))
-			return -EPERM;
+	struct task_struct *task = cgroup_taskset_first(set);
 
+	if (current != task && !capable(CAP_SYS_ADMIN))
+		return -EPERM;
 	return 0;
 }
 
Signature · 1 line
1.7.3.1
author maintainer
#110 lore raw

PATCH 8/10 cgroup: don't use subsys->can_attach_task() or ->attach_task()

In reply to Tejun Heo

Now that subsys->can_attach() and attach() take @tset instead of @task, they can handle per-task operations. Convert ->can_attach_task() and ->attach_task() users to use ->can_attach() and attach() instead. Most converions are straight-forward. Noteworthy changes are,

* In cgroup_freezer, remove unnecessary NULL assignments to unused methods. It's useless and very prone to get out of sync, which already happened.

* In cpuset, PF_THREAD_BOUND test is checked for each task. This doesn't make any practical difference but is conceptually cleaner.

Cc Paul Menage <[email protected]>
Cc Li Zefan <[email protected]>
Cc Balbir Singh <[email protected]>
Cc Daisuke Nishimura <[email protected]>
Cc KAMEZAWA Hiroyuki <[email protected]>
Cc James Morris <[email protected]>
Cc Ingo Molnar <[email protected]>
Cc Peter Zijlstra <[email protected]>
---
 block/blk-cgroup.c      |   45 +++++++++++++++++++-----------
 kernel/cgroup_freezer.c |   25 ++++++-----------
 kernel/cpuset.c         |   70 +++++++++++++++++++++-------------------------
 kernel/events/core.c    |   13 +++++---
 kernel/sched.c          |   31 +++++++++++++--------
 5 files changed, 96 insertions(+), 88 deletions(-)
block/blk-cgroup.c +28 -17
index b596e54..b5e3fef 100644
@@ -30,8 +30,10 @@ EXPORT_SYMBOL_GPL(blkio_root_cgroup);
 
 static struct cgroup_subsys_state *blkiocg_create(struct cgroup_subsys *,
 						  struct cgroup *);
-static int blkiocg_can_attach_task(struct cgroup *, struct task_struct *);
-static void blkiocg_attach_task(struct cgroup *, struct task_struct *);
+static int blkiocg_can_attach(struct cgroup_subsys *, struct cgroup *,
+			      struct cgroup_taskset *);
+static void blkiocg_attach(struct cgroup_subsys *, struct cgroup *,
+			   struct cgroup_taskset *);
 static void blkiocg_destroy(struct cgroup_subsys *, struct cgroup *);
 static int blkiocg_populate(struct cgroup_subsys *, struct cgroup *);
 
@@ -44,8 +46,8 @@ static int blkiocg_populate(struct cgroup_subsys *, struct cgroup *);
 struct cgroup_subsys blkio_subsys = {
 	.name = "blkio",
 	.create = blkiocg_create,
-	.can_attach_task = blkiocg_can_attach_task,
-	.attach_task = blkiocg_attach_task,
+	.can_attach = blkiocg_can_attach,
+	.attach = blkiocg_attach,
 	.destroy = blkiocg_destroy,
 	.populate = blkiocg_populate,
 #ifdef CONFIG_BLK_CGROUP
@@ -1609,30 +1611,39 @@ done:
  * of the main cic data structures.  For now we allow a task to change
  * its cgroup only if it's the only owner of its ioc.
  */
-static int blkiocg_can_attach_task(struct cgroup *cgrp, struct task_struct *tsk)
+static int blkiocg_can_attach(struct cgroup_subsys *ss, struct cgroup *cgrp,
+			      struct cgroup_taskset *tset)
 {
+	struct task_struct *task;
 	struct io_context *ioc;
 	int ret = 0;
 
 	/* task_lock() is needed to avoid races with exit_io_context() */
-	task_lock(tsk);
-	ioc = tsk->io_context;
-	if (ioc && atomic_read(&ioc->nr_tasks) > 1)
-		ret = -EINVAL;
-	task_unlock(tsk);
-
+	cgroup_taskset_for_each(task, cgrp, tset) {
+		task_lock(task);
+		ioc = task->io_context;
+		if (ioc && atomic_read(&ioc->nr_tasks) > 1)
+			ret = -EINVAL;
+		task_unlock(task);
+		if (ret)
+			break;
+	}
 	return ret;
 }
 
-static void blkiocg_attach_task(struct cgroup *cgrp, struct task_struct *tsk)
+static void blkiocg_attach(struct cgroup_subsys *ss, struct cgroup *cgrp,
+			   struct cgroup_taskset *tset)
 {
+	struct task_struct *task;
 	struct io_context *ioc;
 
-	task_lock(tsk);
-	ioc = tsk->io_context;
-	if (ioc)
-		ioc->cgroup_changed = 1;
-	task_unlock(tsk);
+	cgroup_taskset_for_each(task, cgrp, tset) {
+		task_lock(task);
+		ioc = task->io_context;
+		if (ioc)
+			ioc->cgroup_changed = 1;
+		task_unlock(task);
+	}
 }
 
 void blkio_policy_register(struct blkio_policy_type *blkiop)
kernel/cgroup_freezer.c +9 -16
index a72d5fa..68ef861 100644
@@ -163,10 +163,19 @@ static int freezer_can_attach(struct cgroup_subsys *ss,
 			      struct cgroup_taskset *tset)
 {
 	struct freezer *freezer;
+	struct task_struct *task;
 
 	/*
 	 * Anything frozen can't move or be moved to/from.
 	 */
+	rcu_read_lock();
+	cgroup_taskset_for_each(task, new_cgroup, tset) {
+		if (__cgroup_freezing_or_frozen(task)) {
+			rcu_read_unlock();
+			return -EBUSY;
+		}
+	}
+	rcu_read_unlock();
 
 	freezer = cgroup_freezer(new_cgroup);
 	if (freezer->state != CGROUP_THAWED)
@@ -175,17 +184,6 @@ static int freezer_can_attach(struct cgroup_subsys *ss,
 	return 0;
 }
 
-static int freezer_can_attach_task(struct cgroup *cgrp, struct task_struct *tsk)
-{
-	rcu_read_lock();
-	if (__cgroup_freezing_or_frozen(tsk)) {
-		rcu_read_unlock();
-		return -EBUSY;
-	}
-	rcu_read_unlock();
-	return 0;
-}
-
 static void freezer_fork(struct cgroup_subsys *ss, struct task_struct *task)
 {
 	struct freezer *freezer;
@@ -381,10 +379,5 @@ struct cgroup_subsys freezer_subsys = {
 	.populate	= freezer_populate,
 	.subsys_id	= freezer_subsys_id,
 	.can_attach	= freezer_can_attach,
-	.can_attach_task = freezer_can_attach_task,
-	.pre_attach	= NULL,
-	.attach_task	= NULL,
-	.attach		= NULL,
 	.fork		= freezer_fork,
-	.exit		= NULL,
 };
kernel/cpuset.c +32 -38

113 lines not on this page · raw message

kernel/events/core.c +8 -5
index d1a1bee..ed35211 100644
@@ -7136,10 +7136,13 @@ static int __perf_cgroup_move(void *info)
 	return 0;
 }
 
-static void
-perf_cgroup_attach_task(struct cgroup *cgrp, struct task_struct *task)
+static void perf_cgroup_attach(struct cgroup_subsys *ss, struct cgroup *cgrp,
+			       struct cgroup_taskset *tset)
 {
-	task_function_call(task, __perf_cgroup_move, task);
+	struct task_struct *task;
+
+	cgroup_taskset_for_each(task, cgrp, tset)
+		task_function_call(task, __perf_cgroup_move, task);
 }
 
 static void perf_cgroup_exit(struct cgroup_subsys *ss, struct cgroup *cgrp,
@@ -7153,7 +7156,7 @@ static void perf_cgroup_exit(struct cgroup_subsys *ss, struct cgroup *cgrp,
 	if (!(task->flags & PF_EXITING))
 		return;
 
-	perf_cgroup_attach_task(cgrp, task);
+	task_function_call(task, __perf_cgroup_move, task);
 }
 
 struct cgroup_subsys perf_subsys = {
@@ -7162,6 +7165,6 @@ struct cgroup_subsys perf_subsys = {
 	.create		= perf_cgroup_create,
 	.destroy	= perf_cgroup_destroy,
 	.exit		= perf_cgroup_exit,
-	.attach_task	= perf_cgroup_attach_task,
+	.attach		= perf_cgroup_attach,
 };
 #endif /* CONFIG_CGROUP_PERF */
kernel/sched.c +19 -12
index d87c6e5..49e3605 100644
@@ -9129,24 +9129,31 @@ cpu_cgroup_destroy(struct cgroup_subsys *ss, struct cgroup *cgrp)
 	sched_destroy_group(tg);
 }
 
-static int
-cpu_cgroup_can_attach_task(struct cgroup *cgrp, struct task_struct *tsk)
+static int cpu_cgroup_can_attach(struct cgroup_subsys *ss, struct cgroup *cgrp,
+				 struct cgroup_taskset *tset)
 {
+	struct task_struct *task;
+
+	cgroup_taskset_for_each(task, cgrp, tset) {
 #ifdef CONFIG_RT_GROUP_SCHED
-	if (!sched_rt_can_attach(cgroup_tg(cgrp), tsk))
-		return -EINVAL;
+		if (!sched_rt_can_attach(cgroup_tg(cgrp), task))
+			return -EINVAL;
 #else
-	/* We don't support RT-tasks being in separate groups */
-	if (tsk->sched_class != &fair_sched_class)
-		return -EINVAL;
+		/* We don't support RT-tasks being in separate groups */
+		if (task->sched_class != &fair_sched_class)
+			return -EINVAL;
 #endif
+	}
 	return 0;
 }
 
-static void
-cpu_cgroup_attach_task(struct cgroup *cgrp, struct task_struct *tsk)
+static void cpu_cgroup_attach(struct cgroup_subsys *ss, struct cgroup *cgrp,
+			      struct cgroup_taskset *tset)
 {
-	sched_move_task(tsk);
+	struct task_struct *task;
+
+	cgroup_taskset_for_each(task, cgrp, tset)
+		sched_move_task(task);
 }
 
 static void
@@ -9482,8 +9489,8 @@ struct cgroup_subsys cpu_cgroup_subsys = {
 	.name		= "cpu",
 	.create		= cpu_cgroup_create,
 	.destroy	= cpu_cgroup_destroy,
-	.can_attach_task = cpu_cgroup_can_attach_task,
-	.attach_task	= cpu_cgroup_attach_task,
+	.can_attach	= cpu_cgroup_can_attach,
+	.attach		= cpu_cgroup_attach,
 	.exit		= cpu_cgroup_exit,
 	.populate	= cpu_cgroup_populate,
 	.subsys_id	= cpu_cgroup_subsys_id,
Signature · 1 line
1.7.3.1

A message that is not in the archive (sent off-list or not yet received): 1320191193-8110-9-git-send-email-tj-DgEjT+Ai2ygdnm+yROfE0A@public.gmane.org

Reviewed-by
#112 lore raw

Re: [PATCH 08/10] cgroup: don't use subsys->can_attach_task() or ->attach_task()

Quoted 13 lines · On Tue, 1 Nov 2011 16:46:31 -0700 Tejun Heo…

Now that subsys->can_attach() and attach() take @tset instead of @task, they can handle per-task operations. Convert ->can_attach_task() and ->attach_task() users to use ->can_attach() and attach() instead. Most converions are straight-forward. Noteworthy changes are,

* In cgroup_freezer, remove unnecessary NULL assignments to unused methods. It's useless and very prone to get out of sync, which already happened.

* In cpuset, PF_THREAD_BOUND test is checked for each task. This doesn't make any practical difference but is conceptually cleaner.

Quoted 9 lines
Cc Paul Menage <[email protected]>
Cc Li Zefan <[email protected]>
Cc Balbir Singh <[email protected]>
Cc Daisuke Nishimura <[email protected]>
Cc KAMEZAWA Hiroyuki <[email protected]>
Cc James Morris <jmorris-gx6/[email protected]>
Cc Ingo Molnar <[email protected]>
Cc Peter Zijlstra <[email protected]>
Reviewed-by on v1 8/10
#113 lore raw

Re: [PATCH 08/10] cgroup: don't use subsys->can_attach_task() or ->attach_task()

In reply to Tejun Heo
Quoted 13 lines · On Tue, 1 Nov 2011 16:46:31 -0700 Tejun Heo <[email protected]> wrote:

Now that subsys->can_attach() and attach() take @tset instead of @task, they can handle per-task operations. Convert ->can_attach_task() and ->attach_task() users to use ->can_attach() and attach() instead. Most converions are straight-forward. Noteworthy changes are,

* In cgroup_freezer, remove unnecessary NULL assignments to unused methods. It's useless and very prone to get out of sync, which already happened.

* In cpuset, PF_THREAD_BOUND test is checked for each task. This doesn't make any practical difference but is conceptually cleaner.

Quoted 9 lines
Cc Paul Menage <[email protected]>
Cc Li Zefan <[email protected]>
Cc Balbir Singh <[email protected]>
Cc Daisuke Nishimura <[email protected]>
Cc KAMEZAWA Hiroyuki <[email protected]>
Cc James Morris <[email protected]>
Cc Ingo Molnar <[email protected]>
Cc Peter Zijlstra <[email protected]>
Reviewed-by on v1 8/10
#114 lore raw

Re: [PATCH 08/10] cgroup: don't use subsys->can_attach_task() or ->attach_task()

In reply to Tejun Heo
Quoted 13 lines · On Tue, 1 Nov 2011 16:46:31 -0700 Tejun Heo <[email protected]> wrote:

Now that subsys->can_attach() and attach() take @tset instead of @task, they can handle per-task operations. Convert ->can_attach_task() and ->attach_task() users to use ->can_attach() and attach() instead. Most converions are straight-forward. Noteworthy changes are,

* In cgroup_freezer, remove unnecessary NULL assignments to unused methods. It's useless and very prone to get out of sync, which already happened.

* In cpuset, PF_THREAD_BOUND test is checked for each task. This doesn't make any practical difference but is conceptually cleaner.

Quoted 9 lines
Cc Paul Menage <[email protected]>
Cc Li Zefan <[email protected]>
Cc Balbir Singh <[email protected]>
Cc Daisuke Nishimura <[email protected]>
Cc KAMEZAWA Hiroyuki <[email protected]>
Cc James Morris <[email protected]>
Cc Ingo Molnar <[email protected]>
Cc Peter Zijlstra <[email protected]>
Reviewed-by on v1 8/10
#115 lore raw

Re: [PATCH 08/10] cgroup: don't use subsys->can_attach_task() or ->attach_task()

In reply to Tejun Heo
Quoted 13 lines · Tejun Heo

Now that subsys->can_attach() and attach() take @tset instead of @task, they can handle per-task operations. Convert ->can_attach_task() and ->attach_task() users to use ->can_attach() and attach() instead. Most converions are straight-forward. Noteworthy changes are,

* In cgroup_freezer, remove unnecessary NULL assignments to unused methods. It's useless and very prone to get out of sync, which already happened.

* In cpuset, PF_THREAD_BOUND test is checked for each task. This doesn't make any practical difference but is conceptually cleaner.

Quoted 9 lines
Cc Paul Menage <[email protected]>
Cc Li Zefan <[email protected]>
Cc Balbir Singh <[email protected]>
Cc Daisuke Nishimura <[email protected]>
Cc KAMEZAWA Hiroyuki <[email protected]>
Cc James Morris <jmorris-gx6/[email protected]>
Cc Ingo Molnar <[email protected]>
Cc Peter Zijlstra <[email protected]>
Reviewed-by on v1 8/10
#116 lore raw

Re: [PATCH 08/10] cgroup: don't use subsys->can_attach_task() or ->attach_task()

In reply to Tejun Heo
Quoted 13 lines · Tejun Heo

Now that subsys->can_attach() and attach() take @tset instead of @task, they can handle per-task operations. Convert ->can_attach_task() and ->attach_task() users to use ->can_attach() and attach() instead. Most converions are straight-forward. Noteworthy changes are,

* In cgroup_freezer, remove unnecessary NULL assignments to unused methods. It's useless and very prone to get out of sync, which already happened.

* In cpuset, PF_THREAD_BOUND test is checked for each task. This doesn't make any practical difference but is conceptually cleaner.

Quoted 9 lines
Cc Paul Menage <[email protected]>
Cc Li Zefan <[email protected]>
Cc Balbir Singh <[email protected]>
Cc Daisuke Nishimura <[email protected]>
Cc KAMEZAWA Hiroyuki <[email protected]>
Cc James Morris <[email protected]>
Cc Ingo Molnar <[email protected]>
Cc Peter Zijlstra <[email protected]>
author maintainer
#117 lore raw

PATCH 9/10 cgroup, cpuset: don't use ss->pre_attach()

In reply to Tejun Heo

->pre_attach() is supposed to be called before migration, which is observed during process migration but task migration does it the other way around. The only ->pre_attach() user is cpuset which can do the same operaitons in ->can_attach(). Collapse cpuset_pre_attach() into cpuset_can_attach().

-v2: Patch contamination from later patch removed. Spotted by Paul
Menage.

---
 kernel/cpuset.c |   29 ++++++++++++-----------------
 1 files changed, 12 insertions(+), 17 deletions(-)
kernel/cpuset.c +12 -17
index 472ddd6..f0b8df3 100644
@@ -1367,6 +1367,15 @@ static int fmeter_getrate(struct fmeter *fmp)
 	return val;
 }
 
+/*
+ * Protected by cgroup_lock. The nodemasks must be stored globally because
+ * dynamically allocating them is not allowed in can_attach, and they must
+ * persist until attach.
+ */
+static cpumask_var_t cpus_attach;
+static nodemask_t cpuset_attach_nodemask_from;
+static nodemask_t cpuset_attach_nodemask_to;
+
 /* Called by cgroups to determine if a cpuset is usable; cgroup_mutex held */
 static int cpuset_can_attach(struct cgroup_subsys *ss, struct cgroup *cgrp,
 			     struct cgroup_taskset *tset)
@@ -1393,29 +1402,16 @@ static int cpuset_can_attach(struct cgroup_subsys *ss, struct cgroup *cgrp,
 		if ((ret = security_task_setscheduler(task)))
 			return ret;
 	}
-	return 0;
-}
-
-/*
- * Protected by cgroup_lock. The nodemasks must be stored globally because
- * dynamically allocating them is not allowed in pre_attach, and they must
- * persist among pre_attach, and attach.
- */
-static cpumask_var_t cpus_attach;
-static nodemask_t cpuset_attach_nodemask_from;
-static nodemask_t cpuset_attach_nodemask_to;
-
-/* Set-up work for before attaching each task. */
-static void cpuset_pre_attach(struct cgroup *cont)
-{
-	struct cpuset *cs = cgroup_cs(cont);
 
+	/* prepare for attach */
 	if (cs == &top_cpuset)
 		cpumask_copy(cpus_attach, cpu_possible_mask);
 	else
 		guarantee_online_cpus(cs, cpus_attach);
 
 	guarantee_online_mems(cs, &cpuset_attach_nodemask_to);
+
+	return 0;
 }
 
 static void cpuset_attach(struct cgroup_subsys *ss, struct cgroup *cgrp,
@@ -1901,7 +1897,6 @@ struct cgroup_subsys cpuset_subsys = {
 	.create = cpuset_create,
 	.destroy = cpuset_destroy,
 	.can_attach = cpuset_can_attach,
-	.pre_attach = cpuset_pre_attach,
 	.attach = cpuset_attach,
 	.populate = cpuset_populate,
 	.post_clone = cpuset_post_clone,
Signature · 1 line
1.7.3.1
Reviewed-by on v1 9/10
#118 lore raw

Re: [PATCH 09/10] cgroup, cpuset: don't use ss->pre_attach()

Quoted 6 lines · Tejun Heo

->pre_attach() is supposed to be called before migration, which is observed during process migration but task migration does it the other way around. The only ->pre_attach() user is cpuset which can do the same operaitons in ->can_attach(). Collapse cpuset_pre_attach() into cpuset_can_attach().

Quoted 6 lines

-v2: Patch contamination from later patch removed. Spotted by Paul
Menage.

Reviewed-by on v1 9/10
#119 lore raw

Re: [PATCH 09/10] cgroup, cpuset: don't use ss->pre_attach()

In reply to Tejun Heo
Quoted 6 lines · Tejun Heo

->pre_attach() is supposed to be called before migration, which is observed during process migration but task migration does it the other way around. The only ->pre_attach() user is cpuset which can do the same operaitons in ->can_attach(). Collapse cpuset_pre_attach() into cpuset_can_attach().

Quoted 6 lines

-v2: Patch contamination from later patch removed. Spotted by Paul
Menage.

author maintainer
#120 lore raw

PATCH 9/10 cgroup, cpuset: don't use ss->pre_attach()

In reply to Tejun Heo

->pre_attach() is supposed to be called before migration, which is observed during process migration but task migration does it the other way around. The only ->pre_attach() user is cpuset which can do the same operaitons in ->can_attach(). Collapse cpuset_pre_attach() into cpuset_can_attach().

-v2: Patch contamination from later patch removed. Spotted by Paul
Menage.

---
 kernel/cpuset.c |   29 ++++++++++++-----------------
 1 files changed, 12 insertions(+), 17 deletions(-)
kernel/cpuset.c +12 -17
index 472ddd6..f0b8df3 100644
@@ -1367,6 +1367,15 @@ static int fmeter_getrate(struct fmeter *fmp)
 	return val;
 }
 
+/*
+ * Protected by cgroup_lock. The nodemasks must be stored globally because
+ * dynamically allocating them is not allowed in can_attach, and they must
+ * persist until attach.
+ */
+static cpumask_var_t cpus_attach;
+static nodemask_t cpuset_attach_nodemask_from;
+static nodemask_t cpuset_attach_nodemask_to;
+
 /* Called by cgroups to determine if a cpuset is usable; cgroup_mutex held */
 static int cpuset_can_attach(struct cgroup_subsys *ss, struct cgroup *cgrp,
 			     struct cgroup_taskset *tset)
@@ -1393,29 +1402,16 @@ static int cpuset_can_attach(struct cgroup_subsys *ss, struct cgroup *cgrp,
 		if ((ret = security_task_setscheduler(task)))
 			return ret;
 	}
-	return 0;
-}
-
-/*
- * Protected by cgroup_lock. The nodemasks must be stored globally because
- * dynamically allocating them is not allowed in pre_attach, and they must
- * persist among pre_attach, and attach.
- */
-static cpumask_var_t cpus_attach;
-static nodemask_t cpuset_attach_nodemask_from;
-static nodemask_t cpuset_attach_nodemask_to;
-
-/* Set-up work for before attaching each task. */
-static void cpuset_pre_attach(struct cgroup *cont)
-{
-	struct cpuset *cs = cgroup_cs(cont);
 
+	/* prepare for attach */
 	if (cs == &top_cpuset)
 		cpumask_copy(cpus_attach, cpu_possible_mask);
 	else
 		guarantee_online_cpus(cs, cpus_attach);
 
 	guarantee_online_mems(cs, &cpuset_attach_nodemask_to);
+
+	return 0;
 }
 
 static void cpuset_attach(struct cgroup_subsys *ss, struct cgroup *cgrp,
@@ -1901,7 +1897,6 @@ struct cgroup_subsys cpuset_subsys = {
 	.create = cpuset_create,
 	.destroy = cpuset_destroy,
 	.can_attach = cpuset_can_attach,
-	.pre_attach = cpuset_pre_attach,
 	.attach = cpuset_attach,
 	.populate = cpuset_populate,
 	.post_clone = cpuset_post_clone,
Signature · 1 line
1.7.3.1
author maintainer
#121 lore raw

PATCH 10/10 cgroup: kill subsys->can_attach_task(), pre_attach() and attach_task()

In reply to Tejun Heo

These three methods are no longer used. Kill them.

---
 Documentation/cgroups/cgroups.txt |   20 --------------
 include/linux/cgroup.h            |    3 --
 kernel/cgroup.c                   |   53 +++---------------------------------
 3 files changed, 5 insertions(+), 71 deletions(-)
Documentation/cgroups/cgroups.txt +0 -20
index bf5d6c9..eb1b609 100644
@@ -615,13 +615,6 @@ fork. If this method returns 0 (success) then this should remain valid
 while the caller holds cgroup_mutex and it is ensured that either
 attach() or cancel_attach() will be called in future.
 
-int can_attach_task(struct cgroup *cgrp, struct task_struct *tsk);
-(cgroup_mutex held by caller)
-
-As can_attach, but for operations that must be run once per task to be
-attached (possibly many when using cgroup_attach_proc). Called after
-can_attach.
-
 void cancel_attach(struct cgroup_subsys *ss, struct cgroup *cgrp,
 		   struct cgroup_taskset *tset)
 (cgroup_mutex held by caller)
@@ -632,12 +625,6 @@ function, so that the subsystem can implement a rollback. If not, not necessary.
 This will be called only about subsystems whose can_attach() operation have
 succeeded. The parameters are identical to can_attach().
 
-void pre_attach(struct cgroup *cgrp);
-(cgroup_mutex held by caller)
-
-For any non-per-thread attachment work that needs to happen before
-attach_task. Needed by cpuset.
-
 void attach(struct cgroup_subsys *ss, struct cgroup *cgrp,
 	    struct cgroup_taskset *tset)
 (cgroup_mutex held by caller)
@@ -646,13 +633,6 @@ Called after the task has been attached to the cgroup, to allow any
 post-attachment activity that requires memory allocations or blocking.
 The parameters are identical to can_attach().
 
-void attach_task(struct cgroup *cgrp, struct task_struct *tsk);
-(cgroup_mutex held by caller)
-
-As attach, but for operations that must be run once per task to be attached,
-like can_attach_task. Called before attach. Currently does not support any
-subsystem that might need the old_cgrp for every thread in the group.
-
 void fork(struct cgroup_subsy *ss, struct task_struct *task)
 
 Called when a task is forked into a cgroup.
include/linux/cgroup.h +0 -3
index 2470c8e..5659d37 100644
@@ -490,11 +490,8 @@ struct cgroup_subsys {
 	void (*destroy)(struct cgroup_subsys *ss, struct cgroup *cgrp);
 	int (*can_attach)(struct cgroup_subsys *ss, struct cgroup *cgrp,
 			  struct cgroup_taskset *tset);
-	int (*can_attach_task)(struct cgroup *cgrp, struct task_struct *tsk);
 	void (*cancel_attach)(struct cgroup_subsys *ss, struct cgroup *cgrp,
 			      struct cgroup_taskset *tset);
-	void (*pre_attach)(struct cgroup *cgrp);
-	void (*attach_task)(struct cgroup *cgrp, struct task_struct *tsk);
 	void (*attach)(struct cgroup_subsys *ss, struct cgroup *cgrp,
 		       struct cgroup_taskset *tset);
 	void (*fork)(struct cgroup_subsys *ss, struct task_struct *task);
kernel/cgroup.c +5 -48

101 lines not on this page · raw message

Signature · 1 line
1.7.3.1

A message that is not in the archive (sent off-list or not yet received): 1320191193-8110-11-git-send-email-tj-DgEjT+Ai2ygdnm+yROfE0A@public.gmane.org

author maintainer
#128 lore raw

PATCH 10/10 cgroup: kill subsys->can_attach_task(), pre_attach() and attach_task()

In reply to Tejun Heo

These three methods are no longer used. Kill them.

---
 Documentation/cgroups/cgroups.txt |   20 --------------
 include/linux/cgroup.h            |    3 --
 kernel/cgroup.c                   |   53 +++---------------------------------
 3 files changed, 5 insertions(+), 71 deletions(-)
Documentation/cgroups/cgroups.txt +0 -20
index bf5d6c9..eb1b609 100644
@@ -615,13 +615,6 @@ fork. If this method returns 0 (success) then this should remain valid
 while the caller holds cgroup_mutex and it is ensured that either
 attach() or cancel_attach() will be called in future.
 
-int can_attach_task(struct cgroup *cgrp, struct task_struct *tsk);
-(cgroup_mutex held by caller)
-
-As can_attach, but for operations that must be run once per task to be
-attached (possibly many when using cgroup_attach_proc). Called after
-can_attach.
-
 void cancel_attach(struct cgroup_subsys *ss, struct cgroup *cgrp,
 		   struct cgroup_taskset *tset)
 (cgroup_mutex held by caller)
@@ -632,12 +625,6 @@ function, so that the subsystem can implement a rollback. If not, not necessary.
 This will be called only about subsystems whose can_attach() operation have
 succeeded. The parameters are identical to can_attach().
 
-void pre_attach(struct cgroup *cgrp);
-(cgroup_mutex held by caller)
-
-For any non-per-thread attachment work that needs to happen before
-attach_task. Needed by cpuset.
-
 void attach(struct cgroup_subsys *ss, struct cgroup *cgrp,
 	    struct cgroup_taskset *tset)
 (cgroup_mutex held by caller)
@@ -646,13 +633,6 @@ Called after the task has been attached to the cgroup, to allow any
 post-attachment activity that requires memory allocations or blocking.
 The parameters are identical to can_attach().
 
-void attach_task(struct cgroup *cgrp, struct task_struct *tsk);
-(cgroup_mutex held by caller)
-
-As attach, but for operations that must be run once per task to be attached,
-like can_attach_task. Called before attach. Currently does not support any
-subsystem that might need the old_cgrp for every thread in the group.
-
 void fork(struct cgroup_subsy *ss, struct task_struct *task)
 
 Called when a task is forked into a cgroup.
include/linux/cgroup.h +0 -3
index 2470c8e..5659d37 100644
@@ -490,11 +490,8 @@ struct cgroup_subsys {
 	void (*destroy)(struct cgroup_subsys *ss, struct cgroup *cgrp);
 	int (*can_attach)(struct cgroup_subsys *ss, struct cgroup *cgrp,
 			  struct cgroup_taskset *tset);
-	int (*can_attach_task)(struct cgroup *cgrp, struct task_struct *tsk);
 	void (*cancel_attach)(struct cgroup_subsys *ss, struct cgroup *cgrp,
 			      struct cgroup_taskset *tset);
-	void (*pre_attach)(struct cgroup *cgrp);
-	void (*attach_task)(struct cgroup *cgrp, struct task_struct *tsk);
 	void (*attach)(struct cgroup_subsys *ss, struct cgroup *cgrp,
 		       struct cgroup_taskset *tset);
 	void (*fork)(struct cgroup_subsys *ss, struct task_struct *task);
kernel/cgroup.c +5 -48

101 lines not on this page · raw message

Signature · 1 line
1.7.3.1
author maintainer
#129 lore raw
In reply to Tejun Heo
Quoted 4 lines · Tejun Heo

This patchset is combination of the following two patchsets.

[1] cgroup: extend threadgroup locking
[2] cgroup: introduce cgroup_taskset and consolidate subsys methods, take#2

Li, do you agree with the proposed patches? If so, I'll wait for Frederic's response, repost updated series and merge it into the cgroup tree.

Thank you.

Signature · 1 line
tejun
#130 lore raw
Quoted 10 lines · Tejun Heo
Quoted 4 lines · Tejun Heo

This patchset is combination of the following two patchsets.

[1] cgroup: extend threadgroup locking
[2] cgroup: introduce cgroup_taskset and consolidate subsys methods, take#2

Li, do you agree with the proposed patches? If so, I'll wait for Frederic's response, repost updated series and merge it into the cgroup tree.

I remember I've acked both patchsets in your earlier post. :)

--
Li Zefan

author maintainer
#131 lore raw
Quoted 7 lines · Li Zefan
Quoted 6 lines · Tejun Heo
Quoted 4 lines · Tejun Heo

This patchset is combination of the following two patchsets.

[1] cgroup: extend threadgroup locking
[2] cgroup: introduce cgroup_taskset and consolidate subsys methods, take#2

Quoted 6 lines
Quoted 4 lines

Li, do you agree with the proposed patches? If so, I'll wait for Frederic's response, repost updated series and merge it into the cgroup tree.

I remember I've acked both patchsets in your earlier post. :)

Heh, right, it has been too long and I forgot. I'll add the acks.

Thank you.

Signature · 1 line
tejun
author maintainer
#132 lore raw
In reply to Li Zefan
Quoted 7 lines · Li Zefan
Quoted 6 lines · Tejun Heo
Quoted 4 lines · Tejun Heo

This patchset is combination of the following two patchsets.

[1] cgroup: extend threadgroup locking
[2] cgroup: introduce cgroup_taskset and consolidate subsys methods, take#2

Quoted 6 lines
Quoted 4 lines

Li, do you agree with the proposed patches? If so, I'll wait for Frederic's response, repost updated series and merge it into the cgroup tree.

I remember I've acked both patchsets in your earlier post. :)

Heh, right, it has been too long and I forgot. I'll add the acks.

Thank you.

Signature · 1 line
tejun
#133 lore raw
In reply to Tejun Heo
Quoted 10 lines · Tejun Heo
Quoted 4 lines · Tejun Heo

This patchset is combination of the following two patchsets.

[1] cgroup: extend threadgroup locking
[2] cgroup: introduce cgroup_taskset and consolidate subsys methods, take#2

Li, do you agree with the proposed patches? If so, I'll wait for Frederic's response, repost updated series and merge it into the cgroup tree.

I remember I've acked both patchsets in your earlier post. :)

--
Li Zefan

author maintainer
#134 lore raw
In reply to Tejun Heo
Quoted 4 lines · Tejun Heo

This patchset is combination of the following two patchsets.

[1] cgroup: extend threadgroup locking
[2] cgroup: introduce cgroup_taskset and consolidate subsys methods, take#2

Li, do you agree with the proposed patches? If so, I'll wait for Frederic's response, repost updated series and merge it into the cgroup tree.

Thank you.

Signature · 1 line
tejun
author maintainer
#135 lore raw
In reply to Tejun Heo

Series committed to cgroup/for-3.3 with updated patch and pushed out to linux-next.

Thank you.

Signature · 1 line
tejun