Skip to contentSkip to conversation

cgroup: add "procs" control file

RFC PATCH In review Li Zefan 8 participants 13 messages Jun 18, 2008 – Jun 21, 2008 4 messages not in the archive

Subsystems (2)
lore
Share & tools
lore

Li Zefan

author
#0 lore raw

RFC PATCH 1/1 cgroup: add "procs" control file

This control file is the equivalent of the "tasks" control file, but acting/reporting on entire thread groups.

For example, we have a process with pid 1000 and its sub-thread with tid 1001, to attach them into a cgroup:

 # echo 1000 > procs
Then show the process list and the task list respectively:
 # cat procs
 1000
 # cat tasks
 1000
 1001

Questions:
- What to do if the attaching of a thread failed? continue to attach other threads, or stop and return error?
- When a sub-thread of a process is in the cgroup, but not its thread cgroup leader, what to do when 'cat procs'? just skip those threads?

---
 Documentation/cgroups.txt |    9 ++++
 include/linux/cgroup.h    |    2 +-
 kernel/cgroup.c           |  112 ++++++++++++++++++++++++++++++++++++++------
 kernel/cpuset.c           |    2 +-
 4 files changed, 107 insertions(+), 18 deletions(-)
Documentation/cgroups.txt +9 -0
index 8252f5b..03c7b21 100644
@@ -227,6 +227,7 @@ Each cgroup is represented by a directory in the cgroup file system
 containing the following files describing that cgroup:
 
  - tasks: list of tasks (by pid) attached to that cgroup
+ - procs: list of processes (by pid) attached to that cgroup
  - releasable flag: cgroup currently removeable?
  - notify_on_release flag: run the release agent on exit?
  - release_agent: the path to use for release notifications (this file
@@ -380,6 +381,8 @@ reference).
 2.2 Attaching processes
 -----------------------
 
+To attach a task to a cgroup:
+
 # /bin/echo PID > tasks
 
 Note that it is PID, not PIDs. You can only attach ONE task at a time.
@@ -390,6 +393,12 @@ If you have several tasks to attach, you have to do it one after another:
 	...
 # /bin/echo PIDn > tasks
 
+To attach an entire thread group to a cgroup:
+
+# /bin/echo PID > procs
+
+PID should be the pid of the thread group leader.
+
 3. Kernel API
 =============
 
include/linux/cgroup.h +1 -1
index fc99ba4..f125324 100644
@@ -376,7 +376,7 @@ struct task_struct *cgroup_iter_next(struct cgroup *cgrp,
 					struct cgroup_iter *it);
 void cgroup_iter_end(struct cgroup *cgrp, struct cgroup_iter *it);
 int cgroup_scan_tasks(struct cgroup_scanner *scan);
-int cgroup_attach_task(struct cgroup *, struct task_struct *);
+int cgroup_attach_task(struct cgroup *, struct task_struct *, struct css_set *);
 
 #else /* !CONFIG_CGROUPS */
 
kernel/cgroup.c +96 -16

217 lines not on this page · raw message

kernel/cpuset.c +1 -1
index 039baa4..dec96cf 100644
@@ -1754,7 +1754,7 @@ static void cpuset_do_move_task(struct task_struct *tsk,
 	struct cpuset_hotplug_scanner *chsp;
 
 	chsp = container_of(scan, struct cpuset_hotplug_scanner, scan);
-	cgroup_attach_task(chsp->to, tsk);
+	cgroup_attach_task(chsp->to, tsk, NULL);
 }
 
 /**
Signature · 1 line
1.5.4.rc3
#1 lore raw
Quoted 13 lines · On Wed, 18 Jun 2008 16:02:25 +0800 Li Zefan <[email protected]> wrote:

This control file is the equivalent of the "tasks" control file, but acting/reporting on entire thread groups.

For example, we have a process with pid 1000 and its sub-thread with tid 1001, to attach them into a cgroup:

 # echo 1000 > procs
Then show the process list and the task list respectively:
 # cat procs
 1000
 # cat tasks
 1000
 1001
Quoted 6 lines

Questions:
- What to do if the attaching of a thread failed? continue to attach other threads, or stop and return error?
- When a sub-thread of a process is in the cgroup, but not its thread cgroup leader, what to do when 'cat procs'? just skip those threads?

I think this feature make sense. But not meets a theory that cgroup handles a thread not a process. So, how about changing the definition of this interface from
- showing procs
to
- showing threads which is thread-group-leader.

One possible problem is a case that thread-group-leader exits while other members are alive. In such case, thread-group-leader calls cgroup_exit() but will be still alive until all sub-threads exit. So, this interface cannot show correct information.
(right ??? please point out if I miss something)

So, how about this kind of interface ? showing both of TID and PID.

%/cat/procs
TID PID
1001 1001
1234 1001
3856 1001
728 728
....
....

nonsense ?

Thanks,
-Kame

Quoted 306 lines
---
 Documentation/cgroups.txt |    9 ++++
 include/linux/cgroup.h    |    2 +-
 kernel/cgroup.c           |  112 ++++++++++++++++++++++++++++++++++++++------
 kernel/cpuset.c           |    2 +-
 4 files changed, 107 insertions(+), 18 deletions(-)
Documentation/cgroups.txt +9 -0
index 8252f5b..03c7b21 100644
@@ -227,6 +227,7 @@ Each cgroup is represented by a directory in the cgroup file system
 containing the following files describing that cgroup:
 
  - tasks: list of tasks (by pid) attached to that cgroup
+ - procs: list of processes (by pid) attached to that cgroup
  - releasable flag: cgroup currently removeable?
  - notify_on_release flag: run the release agent on exit?
  - release_agent: the path to use for release notifications (this file
@@ -380,6 +381,8 @@ reference).
 2.2 Attaching processes
 -----------------------
 
+To attach a task to a cgroup:
+
 # /bin/echo PID > tasks
 
 Note that it is PID, not PIDs. You can only attach ONE task at a time.
@@ -390,6 +393,12 @@ If you have several tasks to attach, you have to do it one after another:
...
 # /bin/echo PIDn > tasks
 
+To attach an entire thread group to a cgroup:
+
+# /bin/echo PID > procs
+
+PID should be the pid of the thread group leader.
+
 3. Kernel API
 =============
include/linux/cgroup.h +1 -1
index fc99ba4..f125324 100644
@@ -376,7 +376,7 @@ struct task_struct *cgroup_iter_next(struct cgroup *cgrp,
 					struct cgroup_iter *it);
 void cgroup_iter_end(struct cgroup *cgrp, struct cgroup_iter *it);
 int cgroup_scan_tasks(struct cgroup_scanner *scan);
-int cgroup_attach_task(struct cgroup *, struct task_struct *);
+int cgroup_attach_task(struct cgroup *, struct task_struct *, struct css_set *);
 
 #else /* !CONFIG_CGROUPS */
 
kernel/cgroup.c +96 -16
index a4c8671..2cfc733 100644
@@ -1246,17 +1246,18 @@ static void get_first_subsys(const struct cgroup *cgrp,
  * cgroup_attach_task - attach task 'tsk' to cgroup 'cgrp'
  * @cgrp: the cgroup the task is attaching to
  * @tsk: the task to be attached
+ * @newcg: the new css_set for this task, can be NULL
  *
  * Call holding cgroup_mutex. May take task_lock of
  * the task 'tsk' during call.
  */
-int cgroup_attach_task(struct cgroup *cgrp, struct task_struct *tsk)
+int cgroup_attach_task(struct cgroup *cgrp, struct task_struct *tsk,
+		       struct css_set *newcg)
 {
 	int retval = 0;
 	struct cgroup_subsys *ss;
 	struct cgroup *oldcgrp;
 	struct css_set *cg = tsk->cgroups;
-	struct css_set *newcg;
 	struct cgroupfs_root *root = cgrp->root;
 	int subsys_id;
 
@@ -1279,9 +1280,12 @@ int cgroup_attach_task(struct cgroup *cgrp, struct task_struct *tsk)
 	 * Locate or allocate a new css_set for this task,
 	 * based on its final set of cgroups
 	 */
-	newcg = find_css_set(cg, cgrp);
-	if (!newcg)
-		return -ENOMEM;
+	if (!newcg) {
+		newcg = find_css_set(cg, cgrp);
+		if (!newcg)
+			return -ENOMEM;
+	} else
+		get_css_set(newcg);
 
 	task_lock(tsk);
 	if (tsk->flags & PF_EXITING) {
@@ -1311,24 +1315,23 @@ int cgroup_attach_task(struct cgroup *cgrp, struct task_struct *tsk)
 }
 
 /*
- * Attach task with pid 'pid' to cgroup 'cgrp'. Call with
- * cgroup_mutex, may take task_lock of task
+ * Get the task with the provided pid. The caller should
+ * call put_task_struct() with the returned task.
  */
-static int attach_task_by_pid(struct cgroup *cgrp, char *pidbuf)
+static struct task_struct *attach_get_task(struct cgroup *cgrp, char *pidbuf)
 {
 	pid_t pid;
 	struct task_struct *tsk;
-	int ret;
 
 	if (sscanf(pidbuf, "%d", &pid) != 1)
-		return -EIO;
+		return ERR_PTR(-EIO);
 
 	if (pid) {
 		rcu_read_lock();
 		tsk = find_task_by_vpid(pid);
 		if (!tsk || tsk->flags & PF_EXITING) {
 			rcu_read_unlock();
-			return -ESRCH;
+			return ERR_PTR(-ESRCH);
 		}
 		get_task_struct(tsk);
 		rcu_read_unlock();
@@ -1336,23 +1339,82 @@ static int attach_task_by_pid(struct cgroup *cgrp, char *pidbuf)
 		if ((current->euid) && (current->euid != tsk->uid)
 		    && (current->euid != tsk->suid)) {
 			put_task_struct(tsk);
-			return -EACCES;
+			return ERR_PTR(-EACCES);
 		}
 	} else {
 		tsk = current;
 		get_task_struct(tsk);
 	}
 
-	ret = cgroup_attach_task(cgrp, tsk);
+	return tsk;
+}
+
+/*
+ * Attach task with pid 'pid' to cgroup 'cgrp'. Call with
+ * cgroup_mutex, may take task_lock of task
+ */
+static int attach_task_by_pid(struct cgroup *cgrp, char *pidbuf)
+{
+	struct task_struct *tsk;
+	int ret;
+
+	tsk = attach_get_task(cgrp, pidbuf);
+	if (IS_ERR(tsk))
+		return PTR_ERR(tsk);
+
+	ret = cgroup_attach_task(cgrp, tsk, NULL);
 	put_task_struct(tsk);
 	return ret;
 }
 
+/*
+ * Attach the entire thread groups whose group leader is 'pid'. Call
+ * with cgroup_mutex, may take task_lock of task.
+ */
+static int attach_thread_group(struct cgroup *cgrp, char *pidbuf)
+{
+	struct task_struct *tsk;
+	struct task_struct *t;
+	struct css_set *cg;
+	int ret1, ret2;
+
+	tsk = attach_get_task(cgrp, pidbuf);
+	if (IS_ERR(tsk))
+		return PTR_ERR(tsk);
+
+	/* attach thread group leader */
+	ret1 = cgroup_attach_task(cgrp, tsk, NULL);
+	if (ret1) {
+		put_task_struct(tsk);
+		return ret1;
+	}
+
+	cg = tsk->cgroups;
+
+	/* attach all sub-threads */
+	rcu_read_lock();
+	for (t = next_thread(tsk); t != tsk; t = next_thread(t)) {
+		get_task_struct(t);
+		ret2 = cgroup_attach_task(cgrp, t, cg);
+		if (ret2) {
+			printk(KERN_ERR "cgroup: failed to attach thread %d\n",
+			       (int)task_pid_vnr(t));
+			ret1 = ret2;
+		}
+		put_task_struct(t);
+	}
+	rcu_read_unlock();
+
+	put_task_struct(tsk);
+	return ret1;
+}
+
 /* The various types of files and directories in a cgroup file system */
 enum cgroup_filetype {
 	FILE_ROOT,
 	FILE_DIR,
 	FILE_TASKLIST,
+	FILE_PROCLIST,
 	FILE_NOTIFY_ON_RELEASE,
 	FILE_RELEASE_AGENT,
 };
@@ -1431,6 +1493,9 @@ static ssize_t cgroup_common_file_write(struct cgroup *cgrp,
 	case FILE_TASKLIST:
 		retval = attach_task_by_pid(cgrp, buffer);
 		break;
+	case FILE_PROCLIST:
+		retval = attach_thread_group(cgrp, buffer);
+		break;
 	case FILE_NOTIFY_ON_RELEASE:
 		clear_bit(CGRP_RELEASABLE, &cgrp->flags);
 		if (simple_strtoul(buffer, NULL, 10) != 0)
@@ -2081,7 +2146,8 @@ struct ctr_struct {
  * read section, so the css_set can't go away, and is
  * immutable after creation.
  */
-static int pid_array_load(pid_t *pidarray, int npids, struct cgroup *cgrp)
+static int pid_array_load(pid_t *pidarray, int npids,
+			  struct cgroup *cgrp, int procs)
 {
 	int n = 0;
 	struct cgroup_iter it;
@@ -2090,6 +2156,8 @@ static int pid_array_load(pid_t *pidarray, int npids, struct cgroup *cgrp)
 	while ((tsk = cgroup_iter_next(cgrp, &it))) {
 		if (unlikely(n == npids))
 			break;
+		if (procs && !thread_group_leader(tsk))
+			continue;
 		pidarray[n++] = task_pid_vnr(tsk);
 	}
 	cgroup_iter_end(cgrp, &it);
@@ -2178,9 +2246,11 @@ static int pid_array_to_buf(char *buf, int sz, pid_t *a, int npids)
 static int cgroup_tasks_open(struct inode *unused, struct file *file)
 {
 	struct cgroup *cgrp = __d_cgrp(file->f_dentry->d_parent);
+	struct cftype *cft = __d_cft(file->f_dentry);
 	struct ctr_struct *ctr;
 	pid_t *pidarray;
 	int npids;
+	int procs;
 	char c;
 
 	if (!(file->f_mode & FMODE_READ))
@@ -2202,7 +2272,8 @@ static int cgroup_tasks_open(struct inode *unused, struct file *file)
 		if (!pidarray)
 			goto err1;
 
-		npids = pid_array_load(pidarray, npids, cgrp);
+		procs = (cft->private == FILE_PROCLIST);
+		npids = pid_array_load(pidarray, npids, cgrp, procs);
 		sort(pidarray, npids, sizeof(pid_t), cmppid, NULL);
 
 		/* Call pid_array_to_buf() twice, first just to get bufsz */
@@ -2271,6 +2342,15 @@ static struct cftype files[] = {
 	},
 
 	{
+		.name = "procs",
+		.open = cgroup_tasks_open,
+		.read = cgroup_tasks_read,
+		.write = cgroup_common_file_write,
+		.release = cgroup_tasks_release,
+		.private = FILE_PROCLIST,
+	},
+
+	{
 		.name = "notify_on_release",
 		.read_u64 = cgroup_read_notify_on_release,
 		.write = cgroup_common_file_write,
@@ -3002,7 +3082,7 @@ int cgroup_clone(struct task_struct *tsk, struct cgroup_subsys *subsys)
 	}
 
 	/* All seems fine. Finish by moving the task into the new cgroup */
-	ret = cgroup_attach_task(child, tsk);
+	ret = cgroup_attach_task(child, tsk, NULL);
 	mutex_unlock(&cgroup_mutex);
 
  out_release:
kernel/cpuset.c +1 -1
index 039baa4..dec96cf 100644
@@ -1754,7 +1754,7 @@ static void cpuset_do_move_task(struct task_struct *tsk,
 	struct cpuset_hotplug_scanner *chsp;
 
 	chsp = container_of(scan, struct cpuset_hotplug_scanner, scan);
-	cgroup_attach_task(chsp->to, tsk);
+	cgroup_attach_task(chsp->to, tsk, NULL);
 }
 
 /**
Signature · 1 line
1.5.4.rc3

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

#3 lore raw
Quoted 46 lines · KAMEZAWA Hiroyuki
Quoted 13 lines · On Wed, 18 Jun 2008 16:02:25 +0800 Li Zefan…

This control file is the equivalent of the "tasks" control file, but acting/reporting on entire thread groups.

For example, we have a process with pid 1000 and its sub-thread with tid 1001, to attach them into a cgroup:

 # echo 1000 > procs
Then show the process list and the task list respectively:
 # cat procs
 1000
 # cat tasks
 1000
 1001
Quoted 6 lines

Questions:
- What to do if the attaching of a thread failed? continue to attach other threads, or stop and return error?
- When a sub-thread of a process is in the cgroup, but not its thread cgroup leader, what to do when 'cat procs'? just skip those threads?

I think this feature make sense. But not meets a theory that cgroup handles a thread not a process. So, how about changing the definition of this interface from
- showing procs
to
- showing threads which is thread-group-leader.

One possible problem is a case that thread-group-leader exits while other members are alive. In such case, thread-group-leader calls cgroup_exit() but will be still alive until all sub-threads exit. So, this interface cannot show correct information.
(right ??? please point out if I miss something)

So, how about this kind of interface ? showing both of TID and PID.

%/cat/procs
TID PID
1001 1001
1234 1001
3856 1001
728 728
....
....

Quoted 2 lines

nonsense ?

Then the left column is exactly the same with the contents of cat tasks, so IMO it won't be useful. Besides, the tasks of pids listed in the right column may not all belongs to that group, so how we can make use of this column?

#4 lore raw
In reply to a message not in the archive
Quoted 29 lines · KAMEZAWA Hiroyuki
Quoted 13 lines · On Wed, 18 Jun 2008 16:02:25 +0800 Li Zefan…

This control file is the equivalent of the "tasks" control file, but acting/reporting on entire thread groups.

For example, we have a process with pid 1000 and its sub-thread with tid 1001, to attach them into a cgroup:

 # echo 1000 > procs
Then show the process list and the task list respectively:
 # cat procs
 1000
 # cat tasks
 1000
 1001
Quoted 6 lines

Questions:
- What to do if the attaching of a thread failed? continue to attach other threads, or stop and return error?
- When a sub-thread of a process is in the cgroup, but not its thread cgroup leader, what to do when 'cat procs'? just skip those threads?

I think this feature make sense. But not meets a theory that cgroup handles a thread not a process. So, how about changing the definition of this interface from
- showing procs
to
- showing threads which is thread-group-leader.

Quoted 6 lines

One possible problem is a case that thread-group-leader exits while other members are alive. In such case, thread-group-leader calls cgroup_exit() but will be still alive until all sub-threads exit. So, this interface cannot show correct information.
(right ??? please point out if I miss something)

It can show the thread group leader in zombie state with [TID]?

Quoted 11 lines

So, how about this kind of interface ? showing both of TID and PID.

%/cat/procs
TID PID
1001 1001
1234 1001
3856 1001
728 728
....
....

Quoted 1 line

nonsense ?

This information is also available from other means /proc/pid/tasks for example. I like Li's original interface for procs and tasks. I would also suggest adding groups.

Signature · 4 lines
	Warm Regards,
	Balbir Singh
	Linux Technology Center
	IBM, ISTL
author
#5 lore raw
In reply to KAMEZAWA Hiroyuki
Quoted 46 lines · KAMEZAWA Hiroyuki
Quoted 13 lines · On Wed, 18 Jun 2008 16:02:25 +0800 Li Zefan <[email protected]> wrote:

This control file is the equivalent of the "tasks" control file, but acting/reporting on entire thread groups.

For example, we have a process with pid 1000 and its sub-thread with tid 1001, to attach them into a cgroup:

 # echo 1000 > procs
Then show the process list and the task list respectively:
 # cat procs
 1000
 # cat tasks
 1000
 1001
Quoted 6 lines

Questions:
- What to do if the attaching of a thread failed? continue to attach other threads, or stop and return error?
- When a sub-thread of a process is in the cgroup, but not its thread cgroup leader, what to do when 'cat procs'? just skip those threads?

I think this feature make sense. But not meets a theory that cgroup handles a thread not a process. So, how about changing the definition of this interface from
- showing procs
to
- showing threads which is thread-group-leader.

One possible problem is a case that thread-group-leader exits while other members are alive. In such case, thread-group-leader calls cgroup_exit() but will be still alive until all sub-threads exit. So, this interface cannot show correct information.
(right ??? please point out if I miss something)

So, how about this kind of interface ? showing both of TID and PID.

%/cat/procs
TID PID
1001 1001
1234 1001
3856 1001
728 728
....
....

Quoted 2 lines

nonsense ?

Then the left column is exactly the same with the contents of cat tasks, so IMO it won't be useful. Besides, the tasks of pids listed in the right column may not all belongs to that group, so how we can make use of this column?

#6 lore raw
Quoted 11 lines · On Thu, 19 Jun 2008 11:07:14 +0800 Li Zefan <[email protected]> wrote:
Quoted 11 lines

So, how about this kind of interface ? showing both of TID and PID.

%/cat/procs
TID PID
1001 1001
1234 1001
3856 1001
728 728
....
....

Quoted 7 lines
Quoted 2 lines

nonsense ?

Then the left column is exactly the same with the contents of cat tasks, so IMO it won't be useful. Besides, the tasks of pids listed in the right column may not all belongs to that group, so how we can make use of this column?

Then, how useful /procs is ;) it's of-no-use.

Thanks,
-Kame

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

#8 lore raw
Quoted 11 lines · On Thu, 19 Jun 2008 11:07:14 +0800 Li Zefan…
Quoted 11 lines

So, how about this kind of interface ? showing both of TID and PID.

%/cat/procs
TID PID
1001 1001
1234 1001
3856 1001
728 728
....
....

Quoted 7 lines
Quoted 2 lines

nonsense ?

Then the left column is exactly the same with the contents of cat tasks, so IMO it won't be useful. Besides, the tasks of pids listed in the right column may not all belongs to that group, so how we can make use of this column?

Then, how useful /procs is ;) it's of-no-use.

Thanks,
-Kame

#9 lore raw
In reply to KAMEZAWA Hiroyuki
Quoted 29 lines · KAMEZAWA Hiroyuki
Quoted 13 lines · On Wed, 18 Jun 2008 16:02:25 +0800 Li Zefan <[email protected]> wrote:

This control file is the equivalent of the "tasks" control file, but acting/reporting on entire thread groups.

For example, we have a process with pid 1000 and its sub-thread with tid 1001, to attach them into a cgroup:

 # echo 1000 > procs
Then show the process list and the task list respectively:
 # cat procs
 1000
 # cat tasks
 1000
 1001
Quoted 6 lines

Questions:
- What to do if the attaching of a thread failed? continue to attach other threads, or stop and return error?
- When a sub-thread of a process is in the cgroup, but not its thread cgroup leader, what to do when 'cat procs'? just skip those threads?

I think this feature make sense. But not meets a theory that cgroup handles a thread not a process. So, how about changing the definition of this interface from
- showing procs
to
- showing threads which is thread-group-leader.

Quoted 6 lines

One possible problem is a case that thread-group-leader exits while other members are alive. In such case, thread-group-leader calls cgroup_exit() but will be still alive until all sub-threads exit. So, this interface cannot show correct information.
(right ??? please point out if I miss something)

It can show the thread group leader in zombie state with [TID]?

Quoted 11 lines

So, how about this kind of interface ? showing both of TID and PID.

%/cat/procs
TID PID
1001 1001
1234 1001
3856 1001
728 728
....
....

Quoted 1 line

nonsense ?

This information is also available from other means /proc/pid/tasks for example. I like Li's original interface for procs and tasks. I would also suggest adding groups.

Signature · 4 lines
	Warm Regards,
	Balbir Singh
	Linux Technology Center
	IBM, ISTL

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

#11 lore raw
Quoted 13 lines · On Wed, 18 Jun 2008 16:02:25 +0800 Li Zefan…

This control file is the equivalent of the "tasks" control file, but acting/reporting on entire thread groups.

For example, we have a process with pid 1000 and its sub-thread with tid 1001, to attach them into a cgroup:

 # echo 1000 > procs
Then show the process list and the task list respectively:
 # cat procs
 1000
 # cat tasks
 1000
 1001
Quoted 6 lines

Questions:
- What to do if the attaching of a thread failed? continue to attach other threads, or stop and return error?
- When a sub-thread of a process is in the cgroup, but not its thread cgroup leader, what to do when 'cat procs'? just skip those threads?

I think this feature make sense. But not meets a theory that cgroup handles a thread not a process. So, how about changing the definition of this interface from
- showing procs
to
- showing threads which is thread-group-leader.

One possible problem is a case that thread-group-leader exits while other members are alive. In such case, thread-group-leader calls cgroup_exit() but will be still alive until all sub-threads exit. So, this interface cannot show correct information.
(right ??? please point out if I miss something)

So, how about this kind of interface ? showing both of TID and PID.

%/cat/procs
TID PID
1001 1001
1234 1001
3856 1001
728 728
....
....

nonsense ?

Thanks,
-Kame

Quoted 306 lines
---
 Documentation/cgroups.txt |    9 ++++
 include/linux/cgroup.h    |    2 +-
 kernel/cgroup.c           |  112 ++++++++++++++++++++++++++++++++++++++------
 kernel/cpuset.c           |    2 +-
 4 files changed, 107 insertions(+), 18 deletions(-)
Documentation/cgroups.txt +9 -0
index 8252f5b..03c7b21 100644
@@ -227,6 +227,7 @@ Each cgroup is represented by a directory in the cgroup file system
 containing the following files describing that cgroup:
 
  - tasks: list of tasks (by pid) attached to that cgroup
+ - procs: list of processes (by pid) attached to that cgroup
  - releasable flag: cgroup currently removeable?
  - notify_on_release flag: run the release agent on exit?
  - release_agent: the path to use for release notifications (this file
@@ -380,6 +381,8 @@ reference).
 2.2 Attaching processes
 -----------------------
 
+To attach a task to a cgroup:
+
 # /bin/echo PID > tasks
 
 Note that it is PID, not PIDs. You can only attach ONE task at a time.
@@ -390,6 +393,12 @@ If you have several tasks to attach, you have to do it one after another:
...
 # /bin/echo PIDn > tasks
 
+To attach an entire thread group to a cgroup:
+
+# /bin/echo PID > procs
+
+PID should be the pid of the thread group leader.
+
 3. Kernel API
 =============
include/linux/cgroup.h +1 -1
index fc99ba4..f125324 100644
@@ -376,7 +376,7 @@ struct task_struct *cgroup_iter_next(struct cgroup *cgrp,
 					struct cgroup_iter *it);
 void cgroup_iter_end(struct cgroup *cgrp, struct cgroup_iter *it);
 int cgroup_scan_tasks(struct cgroup_scanner *scan);
-int cgroup_attach_task(struct cgroup *, struct task_struct *);
+int cgroup_attach_task(struct cgroup *, struct task_struct *, struct css_set *);
 
 #else /* !CONFIG_CGROUPS */
 
kernel/cgroup.c +96 -16
index a4c8671..2cfc733 100644
@@ -1246,17 +1246,18 @@ static void get_first_subsys(const struct cgroup *cgrp,
  * cgroup_attach_task - attach task 'tsk' to cgroup 'cgrp'
  * @cgrp: the cgroup the task is attaching to
  * @tsk: the task to be attached
+ * @newcg: the new css_set for this task, can be NULL
  *
  * Call holding cgroup_mutex. May take task_lock of
  * the task 'tsk' during call.
  */
-int cgroup_attach_task(struct cgroup *cgrp, struct task_struct *tsk)
+int cgroup_attach_task(struct cgroup *cgrp, struct task_struct *tsk,
+		       struct css_set *newcg)
 {
 	int retval = 0;
 	struct cgroup_subsys *ss;
 	struct cgroup *oldcgrp;
 	struct css_set *cg = tsk->cgroups;
-	struct css_set *newcg;
 	struct cgroupfs_root *root = cgrp->root;
 	int subsys_id;
 
@@ -1279,9 +1280,12 @@ int cgroup_attach_task(struct cgroup *cgrp, struct task_struct *tsk)
 	 * Locate or allocate a new css_set for this task,
 	 * based on its final set of cgroups
 	 */
-	newcg = find_css_set(cg, cgrp);
-	if (!newcg)
-		return -ENOMEM;
+	if (!newcg) {
+		newcg = find_css_set(cg, cgrp);
+		if (!newcg)
+			return -ENOMEM;
+	} else
+		get_css_set(newcg);
 
 	task_lock(tsk);
 	if (tsk->flags & PF_EXITING) {
@@ -1311,24 +1315,23 @@ int cgroup_attach_task(struct cgroup *cgrp, struct task_struct *tsk)
 }
 
 /*
- * Attach task with pid 'pid' to cgroup 'cgrp'. Call with
- * cgroup_mutex, may take task_lock of task
+ * Get the task with the provided pid. The caller should
+ * call put_task_struct() with the returned task.
  */
-static int attach_task_by_pid(struct cgroup *cgrp, char *pidbuf)
+static struct task_struct *attach_get_task(struct cgroup *cgrp, char *pidbuf)
 {
 	pid_t pid;
 	struct task_struct *tsk;
-	int ret;
 
 	if (sscanf(pidbuf, "%d", &pid) != 1)
-		return -EIO;
+		return ERR_PTR(-EIO);
 
 	if (pid) {
 		rcu_read_lock();
 		tsk = find_task_by_vpid(pid);
 		if (!tsk || tsk->flags & PF_EXITING) {
 			rcu_read_unlock();
-			return -ESRCH;
+			return ERR_PTR(-ESRCH);
 		}
 		get_task_struct(tsk);
 		rcu_read_unlock();
@@ -1336,23 +1339,82 @@ static int attach_task_by_pid(struct cgroup *cgrp, char *pidbuf)
 		if ((current->euid) && (current->euid != tsk->uid)
 		    && (current->euid != tsk->suid)) {
 			put_task_struct(tsk);
-			return -EACCES;
+			return ERR_PTR(-EACCES);
 		}
 	} else {
 		tsk = current;
 		get_task_struct(tsk);
 	}
 
-	ret = cgroup_attach_task(cgrp, tsk);
+	return tsk;
+}
+
+/*
+ * Attach task with pid 'pid' to cgroup 'cgrp'. Call with
+ * cgroup_mutex, may take task_lock of task
+ */
+static int attach_task_by_pid(struct cgroup *cgrp, char *pidbuf)
+{
+	struct task_struct *tsk;
+	int ret;
+
+	tsk = attach_get_task(cgrp, pidbuf);
+	if (IS_ERR(tsk))
+		return PTR_ERR(tsk);
+
+	ret = cgroup_attach_task(cgrp, tsk, NULL);
 	put_task_struct(tsk);
 	return ret;
 }
 
+/*
+ * Attach the entire thread groups whose group leader is 'pid'. Call
+ * with cgroup_mutex, may take task_lock of task.
+ */
+static int attach_thread_group(struct cgroup *cgrp, char *pidbuf)
+{
+	struct task_struct *tsk;
+	struct task_struct *t;
+	struct css_set *cg;
+	int ret1, ret2;
+
+	tsk = attach_get_task(cgrp, pidbuf);
+	if (IS_ERR(tsk))
+		return PTR_ERR(tsk);
+
+	/* attach thread group leader */
+	ret1 = cgroup_attach_task(cgrp, tsk, NULL);
+	if (ret1) {
+		put_task_struct(tsk);
+		return ret1;
+	}
+
+	cg = tsk->cgroups;
+
+	/* attach all sub-threads */
+	rcu_read_lock();
+	for (t = next_thread(tsk); t != tsk; t = next_thread(t)) {
+		get_task_struct(t);
+		ret2 = cgroup_attach_task(cgrp, t, cg);
+		if (ret2) {
+			printk(KERN_ERR "cgroup: failed to attach thread %d\n",
+			       (int)task_pid_vnr(t));
+			ret1 = ret2;
+		}
+		put_task_struct(t);
+	}
+	rcu_read_unlock();
+
+	put_task_struct(tsk);
+	return ret1;
+}
+
 /* The various types of files and directories in a cgroup file system */
 enum cgroup_filetype {
 	FILE_ROOT,
 	FILE_DIR,
 	FILE_TASKLIST,
+	FILE_PROCLIST,
 	FILE_NOTIFY_ON_RELEASE,
 	FILE_RELEASE_AGENT,
 };
@@ -1431,6 +1493,9 @@ static ssize_t cgroup_common_file_write(struct cgroup *cgrp,
 	case FILE_TASKLIST:
 		retval = attach_task_by_pid(cgrp, buffer);
 		break;
+	case FILE_PROCLIST:
+		retval = attach_thread_group(cgrp, buffer);
+		break;
 	case FILE_NOTIFY_ON_RELEASE:
 		clear_bit(CGRP_RELEASABLE, &cgrp->flags);
 		if (simple_strtoul(buffer, NULL, 10) != 0)
@@ -2081,7 +2146,8 @@ struct ctr_struct {
  * read section, so the css_set can't go away, and is
  * immutable after creation.
  */
-static int pid_array_load(pid_t *pidarray, int npids, struct cgroup *cgrp)
+static int pid_array_load(pid_t *pidarray, int npids,
+			  struct cgroup *cgrp, int procs)
 {
 	int n = 0;
 	struct cgroup_iter it;
@@ -2090,6 +2156,8 @@ static int pid_array_load(pid_t *pidarray, int npids, struct cgroup *cgrp)
 	while ((tsk = cgroup_iter_next(cgrp, &it))) {
 		if (unlikely(n == npids))
 			break;
+		if (procs && !thread_group_leader(tsk))
+			continue;
 		pidarray[n++] = task_pid_vnr(tsk);
 	}
 	cgroup_iter_end(cgrp, &it);
@@ -2178,9 +2246,11 @@ static int pid_array_to_buf(char *buf, int sz, pid_t *a, int npids)
 static int cgroup_tasks_open(struct inode *unused, struct file *file)
 {
 	struct cgroup *cgrp = __d_cgrp(file->f_dentry->d_parent);
+	struct cftype *cft = __d_cft(file->f_dentry);
 	struct ctr_struct *ctr;
 	pid_t *pidarray;
 	int npids;
+	int procs;
 	char c;
 
 	if (!(file->f_mode & FMODE_READ))
@@ -2202,7 +2272,8 @@ static int cgroup_tasks_open(struct inode *unused, struct file *file)
 		if (!pidarray)
 			goto err1;
 
-		npids = pid_array_load(pidarray, npids, cgrp);
+		procs = (cft->private == FILE_PROCLIST);
+		npids = pid_array_load(pidarray, npids, cgrp, procs);
 		sort(pidarray, npids, sizeof(pid_t), cmppid, NULL);
 
 		/* Call pid_array_to_buf() twice, first just to get bufsz */
@@ -2271,6 +2342,15 @@ static struct cftype files[] = {
 	},
 
 	{
+		.name = "procs",
+		.open = cgroup_tasks_open,
+		.read = cgroup_tasks_read,
+		.write = cgroup_common_file_write,
+		.release = cgroup_tasks_release,
+		.private = FILE_PROCLIST,
+	},
+
+	{
 		.name = "notify_on_release",
 		.read_u64 = cgroup_read_notify_on_release,
 		.write = cgroup_common_file_write,
@@ -3002,7 +3082,7 @@ int cgroup_clone(struct task_struct *tsk, struct cgroup_subsys *subsys)
 	}
 
 	/* All seems fine. Finish by moving the task into the new cgroup */
-	ret = cgroup_attach_task(child, tsk);
+	ret = cgroup_attach_task(child, tsk, NULL);
 	mutex_unlock(&cgroup_mutex);
 
  out_release:
kernel/cpuset.c +1 -1
index 039baa4..dec96cf 100644
@@ -1754,7 +1754,7 @@ static void cpuset_do_move_task(struct task_struct *tsk,
 	struct cpuset_hotplug_scanner *chsp;
 
 	chsp = container_of(scan, struct cpuset_hotplug_scanner, scan);
-	cgroup_attach_task(chsp->to, tsk);
+	cgroup_attach_task(chsp->to, tsk, NULL);
 }
 
 /**
Signature · 1 line
1.5.4.rc3
#12 lore raw
In reply to a message not in the archive
Quoted 2 lines · Li Zefan

- What to do if the attaching of a thread failed? continue to attach other threads, or stop and return error?

I think this is something that will have to be handled in the design of transactional cgroup attach.

Quoted 2 lines

- When a sub-thread of a process is in the cgroup, but not its thread cgroup leader, what to do when 'cat procs'? just skip those threads?

Sounds reasonable. I think that in general the procs file is more useful as a write API than a read API anyway, for the reasons you indicate there.

kernel/cgroup.c static int attach_task_by_pid(struct cgroup *cgrp, char *pidbuf) · line 1385

Quoted 5 lines
kernel/cgroup.c +5 -0
+       tsk = attach_get_task(cgrp, pidbuf);
+       if (IS_ERR(tsk))
+               return PTR_ERR(tsk);
+
+       /* attach thread group leader */

Should we check that this is in fact a thread group leader?

kernel/cgroup.c static int attach_task_by_pid(struct cgroup *cgrp, char *pidbuf) · line 1395

Quoted 3 lines
kernel/cgroup.c +3 -0
+
+       /* attach all sub-threads */
+       rcu_read_lock();

cgroup_attach_task() calls synchronize_rcu(), so it doesn't seem likely that rcu_read_lock() is useful here, and might even deadlock?

What are you trying to protect against with the RCU lock?

Quoted 2 lines
       {

+ .name = "procs",

Maybe call it "cgroup.procs" to avoid name clashes in future? We had a debate a while back where I tried to get the cgroup files like "tasks" and "notify_on_release" prefixed with "cgroup." , which were argued against on grounds of backwards compatibility. But there's no compatibility issue here. The only question is whether it's too ugly to have the legacy filenames without a prefix and the new ones with a prefix.

Paul

#13 lore raw
In reply to Li Zefan
Quoted 2 lines · Li Zefan

- What to do if the attaching of a thread failed? continue to attach other threads, or stop and return error?

I think this is something that will have to be handled in the design of transactional cgroup attach.

Quoted 2 lines

- When a sub-thread of a process is in the cgroup, but not its thread cgroup leader, what to do when 'cat procs'? just skip those threads?

Sounds reasonable. I think that in general the procs file is more useful as a write API than a read API anyway, for the reasons you indicate there.

kernel/cgroup.c static int attach_task_by_pid(struct cgroup *cgrp, char *pidbuf) · line 1385

Quoted 5 lines
kernel/cgroup.c +5 -0
+       tsk = attach_get_task(cgrp, pidbuf);
+       if (IS_ERR(tsk))
+               return PTR_ERR(tsk);
+
+       /* attach thread group leader */

Should we check that this is in fact a thread group leader?

kernel/cgroup.c static int attach_task_by_pid(struct cgroup *cgrp, char *pidbuf) · line 1395

Quoted 3 lines
kernel/cgroup.c +3 -0
+
+       /* attach all sub-threads */
+       rcu_read_lock();

cgroup_attach_task() calls synchronize_rcu(), so it doesn't seem likely that rcu_read_lock() is useful here, and might even deadlock?

What are you trying to protect against with the RCU lock?

Quoted 2 lines
       {

+ .name = "procs",

Maybe call it "cgroup.procs" to avoid name clashes in future? We had a debate a while back where I tried to get the cgroup files like "tasks" and "notify_on_release" prefixed with "cgroup." , which were argued against on grounds of backwards compatibility. But there's no compatibility issue here. The only question is whether it's too ugly to have the legacy filenames without a prefix and the new ones with a prefix.

Paul

A message that is not in the archive (sent off-list or not yet received): 6599ad830806192237y8b10c4ao923375074aff56c5-JsoAwUIsXosN+BqQ9rBEUg@public.gmane.org

#15 lore raw
Quoted 7 lines · Paul Menage
Quoted 2 lines · Li Zefan

- What to do if the attaching of a thread failed? continue to attach other threads, or stop and return error?

I think this is something that will have to be handled in the design of transactional cgroup attach.

Is the following proposal feasable?
- call can_attach() for each thread before attaching them into the new group. This works for cpuset, doesn't it?
- the above may not always reasonable, for example for Kosaki-san's task cgroup. in this case, we require the subsystem to provide a can_attach_thread_group(), like:

static int task_cgroup_can_attach_group(struct cgroup_subsys *ss,
struct cgroup *cgrp, struct task_struct *tsk)

{
	struct task_cgroup *taskcg = task_cgroup_from_cgrp(cgrp);
	struct task_struct *t;
	int ret = 0;
	int nr_threads = 1;

	for (t = next_thread(tsk); t != tsk; t = next_thread(t)
		nr_threads++;

	spin_lock(&taskcg->lock);
	if (taskcg->nr_tasks + nr_threads > taskcg->max_tasks)
		ret = -EBUSY;
	spin_unlock(&taskcg->lock);

	return ret;
}
Quoted 8 lines
Quoted 2 lines

- When a sub-thread of a process is in the cgroup, but not its thread cgroup leader, what to do when 'cat procs'? just skip those threads?

Sounds reasonable. I think that in general the procs file is more useful as a write API than a read API anyway, for the reasons you indicate there.

Quoted 8 lines

kernel/cgroup.c static int attach_task_by_pid(struct cgroup *cgrp, char *pidbuf) · line 1385

Quoted 5 lines
kernel/cgroup.c +5 -0
+       tsk = attach_get_task(cgrp, pidbuf);
+       if (IS_ERR(tsk))
+               return PTR_ERR(tsk);
+
+       /* attach thread group leader */

Should we check that this is in fact a thread group leader?

No need actually, I added this check originally and then removed it, but forgot to remove the comment.

Quoted 9 lines

kernel/cgroup.c static int attach_task_by_pid(struct cgroup *cgrp, char *pidbuf) · line 1395

Quoted 3 lines
kernel/cgroup.c +3 -0
+
+       /* attach all sub-threads */
+       rcu_read_lock();

cgroup_attach_task() calls synchronize_rcu(), so it doesn't seem likely that rcu_read_lock() is useful here, and might even deadlock?

What are you trying to protect against with the RCU lock?

Ah yes, bad here. I am trying to protect the thread list.

Quoted 11 lines
Quoted 2 lines
       {

+ .name = "procs",

Maybe call it "cgroup.procs" to avoid name clashes in future? We had a debate a while back where I tried to get the cgroup files like "tasks" and "notify_on_release" prefixed with "cgroup." , which were argued against on grounds of backwards compatibility. But there's no compatibility issue here. The only question is whether it's too ugly to have the legacy filenames without a prefix and the new ones with a prefix.

Yes it's ugly.. Is possible name clash of "procs" a kind of breaking compatibility that should be avoid in any case?

author
#16 lore raw
In reply to Paul Menage
Quoted 7 lines · Paul Menage
Quoted 2 lines · Li Zefan

- What to do if the attaching of a thread failed? continue to attach other threads, or stop and return error?

I think this is something that will have to be handled in the design of transactional cgroup attach.

Is the following proposal feasable?
- call can_attach() for each thread before attaching them into the new group. This works for cpuset, doesn't it?
- the above may not always reasonable, for example for Kosaki-san's task cgroup. in this case, we require the subsystem to provide a can_attach_thread_group(), like:

static int task_cgroup_can_attach_group(struct cgroup_subsys *ss,
struct cgroup *cgrp, struct task_struct *tsk)

{
	struct task_cgroup *taskcg = task_cgroup_from_cgrp(cgrp);
	struct task_struct *t;
	int ret = 0;
	int nr_threads = 1;

	for (t = next_thread(tsk); t != tsk; t = next_thread(t)
		nr_threads++;

	spin_lock(&taskcg->lock);
	if (taskcg->nr_tasks + nr_threads > taskcg->max_tasks)
		ret = -EBUSY;
	spin_unlock(&taskcg->lock);

	return ret;
}
Quoted 8 lines
Quoted 2 lines

- When a sub-thread of a process is in the cgroup, but not its thread cgroup leader, what to do when 'cat procs'? just skip those threads?

Sounds reasonable. I think that in general the procs file is more useful as a write API than a read API anyway, for the reasons you indicate there.

Quoted 8 lines

kernel/cgroup.c static int attach_task_by_pid(struct cgroup *cgrp, char *pidbuf) · line 1385

Quoted 5 lines
kernel/cgroup.c +5 -0
+       tsk = attach_get_task(cgrp, pidbuf);
+       if (IS_ERR(tsk))
+               return PTR_ERR(tsk);
+
+       /* attach thread group leader */

Should we check that this is in fact a thread group leader?

No need actually, I added this check originally and then removed it, but forgot to remove the comment.

Quoted 9 lines

kernel/cgroup.c static int attach_task_by_pid(struct cgroup *cgrp, char *pidbuf) · line 1395

Quoted 3 lines
kernel/cgroup.c +3 -0
+
+       /* attach all sub-threads */
+       rcu_read_lock();

cgroup_attach_task() calls synchronize_rcu(), so it doesn't seem likely that rcu_read_lock() is useful here, and might even deadlock?

What are you trying to protect against with the RCU lock?

Ah yes, bad here. I am trying to protect the thread list.

Quoted 11 lines
Quoted 2 lines
       {

+ .name = "procs",

Maybe call it "cgroup.procs" to avoid name clashes in future? We had a debate a while back where I tried to get the cgroup files like "tasks" and "notify_on_release" prefixed with "cgroup." , which were argued against on grounds of backwards compatibility. But there's no compatibility issue here. The only question is whether it's too ugly to have the legacy filenames without a prefix and the new ones with a prefix.

Yes it's ugly.. Is possible name clash of "procs" a kind of breaking compatibility that should be avoid in any case?