|
@@ -1121,7 +1121,8 @@ static void init_cgroup_housekeeping(struct cgroup *cgrp)
|
|
|
INIT_LIST_HEAD(&cgrp->children);
|
|
|
INIT_LIST_HEAD(&cgrp->css_sets);
|
|
|
INIT_LIST_HEAD(&cgrp->release_list);
|
|
|
- init_rwsem(&cgrp->pids_mutex);
|
|
|
+ init_rwsem(&(cgrp->tasks.mutex));
|
|
|
+ init_rwsem(&(cgrp->procs.mutex));
|
|
|
}
|
|
|
|
|
|
static void init_cgroup_root(struct cgroupfs_root *root)
|
|
@@ -1637,15 +1638,6 @@ static int cgroup_tasks_write(struct cgroup *cgrp, struct cftype *cft, u64 pid)
|
|
|
return ret;
|
|
|
}
|
|
|
|
|
|
-/* The various types of files and directories in a cgroup file system */
|
|
|
-enum cgroup_filetype {
|
|
|
- FILE_ROOT,
|
|
|
- FILE_DIR,
|
|
|
- FILE_TASKLIST,
|
|
|
- FILE_NOTIFY_ON_RELEASE,
|
|
|
- FILE_RELEASE_AGENT,
|
|
|
-};
|
|
|
-
|
|
|
/**
|
|
|
* cgroup_lock_live_group - take cgroup_mutex and check that cgrp is alive.
|
|
|
* @cgrp: the cgroup to be checked for liveness
|
|
@@ -2343,7 +2335,7 @@ int cgroup_scan_tasks(struct cgroup_scanner *scan)
|
|
|
}
|
|
|
|
|
|
/*
|
|
|
- * Stuff for reading the 'tasks' file.
|
|
|
+ * Stuff for reading the 'tasks'/'procs' files.
|
|
|
*
|
|
|
* Reading this file can return large amounts of data if a cgroup has
|
|
|
* *lots* of attached tasks. So it may need several calls to read(),
|
|
@@ -2353,27 +2345,106 @@ int cgroup_scan_tasks(struct cgroup_scanner *scan)
|
|
|
*/
|
|
|
|
|
|
/*
|
|
|
- * Load into 'pidarray' up to 'npids' of the tasks using cgroup
|
|
|
- * 'cgrp'. Return actual number of pids loaded. No need to
|
|
|
- * task_lock(p) when reading out p->cgroup, since we're in an RCU
|
|
|
- * read section, so the css_set can't go away, and is
|
|
|
- * immutable after creation.
|
|
|
+ * pidlist_uniq - given a kmalloc()ed list, strip out all duplicate entries
|
|
|
+ * If the new stripped list is sufficiently smaller and there's enough memory
|
|
|
+ * to allocate a new buffer, will let go of the unneeded memory. Returns the
|
|
|
+ * number of unique elements.
|
|
|
*/
|
|
|
-static int pid_array_load(pid_t *pidarray, int npids, struct cgroup *cgrp)
|
|
|
+/* is the size difference enough that we should re-allocate the array? */
|
|
|
+#define PIDLIST_REALLOC_DIFFERENCE(old, new) ((old) - PAGE_SIZE >= (new))
|
|
|
+static int pidlist_uniq(pid_t **p, int length)
|
|
|
{
|
|
|
- int n = 0, pid;
|
|
|
+ int src, dest = 1;
|
|
|
+ pid_t *list = *p;
|
|
|
+ pid_t *newlist;
|
|
|
+
|
|
|
+ /*
|
|
|
+ * we presume the 0th element is unique, so i starts at 1. trivial
|
|
|
+ * edge cases first; no work needs to be done for either
|
|
|
+ */
|
|
|
+ if (length == 0 || length == 1)
|
|
|
+ return length;
|
|
|
+ /* src and dest walk down the list; dest counts unique elements */
|
|
|
+ for (src = 1; src < length; src++) {
|
|
|
+ /* find next unique element */
|
|
|
+ while (list[src] == list[src-1]) {
|
|
|
+ src++;
|
|
|
+ if (src == length)
|
|
|
+ goto after;
|
|
|
+ }
|
|
|
+ /* dest always points to where the next unique element goes */
|
|
|
+ list[dest] = list[src];
|
|
|
+ dest++;
|
|
|
+ }
|
|
|
+after:
|
|
|
+ /*
|
|
|
+ * if the length difference is large enough, we want to allocate a
|
|
|
+ * smaller buffer to save memory. if this fails due to out of memory,
|
|
|
+ * we'll just stay with what we've got.
|
|
|
+ */
|
|
|
+ if (PIDLIST_REALLOC_DIFFERENCE(length, dest)) {
|
|
|
+ newlist = krealloc(list, dest * sizeof(pid_t), GFP_KERNEL);
|
|
|
+ if (newlist)
|
|
|
+ *p = newlist;
|
|
|
+ }
|
|
|
+ return dest;
|
|
|
+}
|
|
|
+
|
|
|
+static int cmppid(const void *a, const void *b)
|
|
|
+{
|
|
|
+ return *(pid_t *)a - *(pid_t *)b;
|
|
|
+}
|
|
|
+
|
|
|
+/*
|
|
|
+ * Load a cgroup's pidarray with either procs' tgids or tasks' pids
|
|
|
+ */
|
|
|
+static int pidlist_array_load(struct cgroup *cgrp, bool procs)
|
|
|
+{
|
|
|
+ pid_t *array;
|
|
|
+ int length;
|
|
|
+ int pid, n = 0; /* used for populating the array */
|
|
|
struct cgroup_iter it;
|
|
|
struct task_struct *tsk;
|
|
|
+ struct cgroup_pidlist *l;
|
|
|
+
|
|
|
+ /*
|
|
|
+ * If cgroup gets more users after we read count, we won't have
|
|
|
+ * enough space - tough. This race is indistinguishable to the
|
|
|
+ * caller from the case that the additional cgroup users didn't
|
|
|
+ * show up until sometime later on.
|
|
|
+ */
|
|
|
+ length = cgroup_task_count(cgrp);
|
|
|
+ array = kmalloc(length * sizeof(pid_t), GFP_KERNEL);
|
|
|
+ if (!array)
|
|
|
+ return -ENOMEM;
|
|
|
+ /* now, populate the array */
|
|
|
cgroup_iter_start(cgrp, &it);
|
|
|
while ((tsk = cgroup_iter_next(cgrp, &it))) {
|
|
|
- if (unlikely(n == npids))
|
|
|
+ if (unlikely(n == length))
|
|
|
break;
|
|
|
- pid = task_pid_vnr(tsk);
|
|
|
- if (pid > 0)
|
|
|
- pidarray[n++] = pid;
|
|
|
+ /* get tgid or pid for procs or tasks file respectively */
|
|
|
+ pid = (procs ? task_tgid_vnr(tsk) : task_pid_vnr(tsk));
|
|
|
+ if (pid > 0) /* make sure to only use valid results */
|
|
|
+ array[n++] = pid;
|
|
|
}
|
|
|
cgroup_iter_end(cgrp, &it);
|
|
|
- return n;
|
|
|
+ length = n;
|
|
|
+ /* now sort & (if procs) strip out duplicates */
|
|
|
+ sort(array, length, sizeof(pid_t), cmppid, NULL);
|
|
|
+ if (procs) {
|
|
|
+ length = pidlist_uniq(&array, length);
|
|
|
+ l = &(cgrp->procs);
|
|
|
+ } else {
|
|
|
+ l = &(cgrp->tasks);
|
|
|
+ }
|
|
|
+ /* store array in cgroup, freeing old if necessary */
|
|
|
+ down_write(&l->mutex);
|
|
|
+ kfree(l->list);
|
|
|
+ l->list = array;
|
|
|
+ l->length = length;
|
|
|
+ l->use_count++;
|
|
|
+ up_write(&l->mutex);
|
|
|
+ return 0;
|
|
|
}
|
|
|
|
|
|
/**
|
|
@@ -2430,19 +2501,14 @@ err:
|
|
|
return ret;
|
|
|
}
|
|
|
|
|
|
-static int cmppid(const void *a, const void *b)
|
|
|
-{
|
|
|
- return *(pid_t *)a - *(pid_t *)b;
|
|
|
-}
|
|
|
-
|
|
|
|
|
|
/*
|
|
|
- * seq_file methods for the "tasks" file. The seq_file position is the
|
|
|
+ * seq_file methods for the tasks/procs files. The seq_file position is the
|
|
|
* next pid to display; the seq_file iterator is a pointer to the pid
|
|
|
- * in the cgroup->tasks_pids array.
|
|
|
+ * in the cgroup->l->list array.
|
|
|
*/
|
|
|
|
|
|
-static void *cgroup_tasks_start(struct seq_file *s, loff_t *pos)
|
|
|
+static void *cgroup_pidlist_start(struct seq_file *s, loff_t *pos)
|
|
|
{
|
|
|
/*
|
|
|
* Initially we receive a position value that corresponds to
|
|
@@ -2450,46 +2516,45 @@ static void *cgroup_tasks_start(struct seq_file *s, loff_t *pos)
|
|
|
* after a seek to the start). Use a binary-search to find the
|
|
|
* next pid to display, if any
|
|
|
*/
|
|
|
- struct cgroup *cgrp = s->private;
|
|
|
+ struct cgroup_pidlist *l = s->private;
|
|
|
int index = 0, pid = *pos;
|
|
|
int *iter;
|
|
|
|
|
|
- down_read(&cgrp->pids_mutex);
|
|
|
+ down_read(&l->mutex);
|
|
|
if (pid) {
|
|
|
- int end = cgrp->pids_length;
|
|
|
+ int end = l->length;
|
|
|
|
|
|
while (index < end) {
|
|
|
int mid = (index + end) / 2;
|
|
|
- if (cgrp->tasks_pids[mid] == pid) {
|
|
|
+ if (l->list[mid] == pid) {
|
|
|
index = mid;
|
|
|
break;
|
|
|
- } else if (cgrp->tasks_pids[mid] <= pid)
|
|
|
+ } else if (l->list[mid] <= pid)
|
|
|
index = mid + 1;
|
|
|
else
|
|
|
end = mid;
|
|
|
}
|
|
|
}
|
|
|
/* If we're off the end of the array, we're done */
|
|
|
- if (index >= cgrp->pids_length)
|
|
|
+ if (index >= l->length)
|
|
|
return NULL;
|
|
|
/* Update the abstract position to be the actual pid that we found */
|
|
|
- iter = cgrp->tasks_pids + index;
|
|
|
+ iter = l->list + index;
|
|
|
*pos = *iter;
|
|
|
return iter;
|
|
|
}
|
|
|
|
|
|
-static void cgroup_tasks_stop(struct seq_file *s, void *v)
|
|
|
+static void cgroup_pidlist_stop(struct seq_file *s, void *v)
|
|
|
{
|
|
|
- struct cgroup *cgrp = s->private;
|
|
|
- up_read(&cgrp->pids_mutex);
|
|
|
+ struct cgroup_pidlist *l = s->private;
|
|
|
+ up_read(&l->mutex);
|
|
|
}
|
|
|
|
|
|
-static void *cgroup_tasks_next(struct seq_file *s, void *v, loff_t *pos)
|
|
|
+static void *cgroup_pidlist_next(struct seq_file *s, void *v, loff_t *pos)
|
|
|
{
|
|
|
- struct cgroup *cgrp = s->private;
|
|
|
- int *p = v;
|
|
|
- int *end = cgrp->tasks_pids + cgrp->pids_length;
|
|
|
-
|
|
|
+ struct cgroup_pidlist *l = s->private;
|
|
|
+ pid_t *p = v;
|
|
|
+ pid_t *end = l->list + l->length;
|
|
|
/*
|
|
|
* Advance to the next pid in the array. If this goes off the
|
|
|
* end, we're done
|
|
@@ -2503,98 +2568,94 @@ static void *cgroup_tasks_next(struct seq_file *s, void *v, loff_t *pos)
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-static int cgroup_tasks_show(struct seq_file *s, void *v)
|
|
|
+static int cgroup_pidlist_show(struct seq_file *s, void *v)
|
|
|
{
|
|
|
return seq_printf(s, "%d\n", *(int *)v);
|
|
|
}
|
|
|
|
|
|
-static const struct seq_operations cgroup_tasks_seq_operations = {
|
|
|
- .start = cgroup_tasks_start,
|
|
|
- .stop = cgroup_tasks_stop,
|
|
|
- .next = cgroup_tasks_next,
|
|
|
- .show = cgroup_tasks_show,
|
|
|
+/*
|
|
|
+ * seq_operations functions for iterating on pidlists through seq_file -
|
|
|
+ * independent of whether it's tasks or procs
|
|
|
+ */
|
|
|
+static const struct seq_operations cgroup_pidlist_seq_operations = {
|
|
|
+ .start = cgroup_pidlist_start,
|
|
|
+ .stop = cgroup_pidlist_stop,
|
|
|
+ .next = cgroup_pidlist_next,
|
|
|
+ .show = cgroup_pidlist_show,
|
|
|
};
|
|
|
|
|
|
-static void release_cgroup_pid_array(struct cgroup *cgrp)
|
|
|
+static void cgroup_release_pid_array(struct cgroup_pidlist *l)
|
|
|
{
|
|
|
- down_write(&cgrp->pids_mutex);
|
|
|
- BUG_ON(!cgrp->pids_use_count);
|
|
|
- if (!--cgrp->pids_use_count) {
|
|
|
- kfree(cgrp->tasks_pids);
|
|
|
- cgrp->tasks_pids = NULL;
|
|
|
- cgrp->pids_length = 0;
|
|
|
+ down_write(&l->mutex);
|
|
|
+ BUG_ON(!l->use_count);
|
|
|
+ if (!--l->use_count) {
|
|
|
+ kfree(l->list);
|
|
|
+ l->list = NULL;
|
|
|
+ l->length = 0;
|
|
|
}
|
|
|
- up_write(&cgrp->pids_mutex);
|
|
|
+ up_write(&l->mutex);
|
|
|
}
|
|
|
|
|
|
-static int cgroup_tasks_release(struct inode *inode, struct file *file)
|
|
|
+static int cgroup_pidlist_release(struct inode *inode, struct file *file)
|
|
|
{
|
|
|
- struct cgroup *cgrp = __d_cgrp(file->f_dentry->d_parent);
|
|
|
-
|
|
|
+ struct cgroup_pidlist *l;
|
|
|
if (!(file->f_mode & FMODE_READ))
|
|
|
return 0;
|
|
|
-
|
|
|
- release_cgroup_pid_array(cgrp);
|
|
|
+ /*
|
|
|
+ * the seq_file will only be initialized if the file was opened for
|
|
|
+ * reading; hence we check if it's not null only in that case.
|
|
|
+ */
|
|
|
+ l = ((struct seq_file *)file->private_data)->private;
|
|
|
+ cgroup_release_pid_array(l);
|
|
|
return seq_release(inode, file);
|
|
|
}
|
|
|
|
|
|
-static struct file_operations cgroup_tasks_operations = {
|
|
|
+static const struct file_operations cgroup_pidlist_operations = {
|
|
|
.read = seq_read,
|
|
|
.llseek = seq_lseek,
|
|
|
.write = cgroup_file_write,
|
|
|
- .release = cgroup_tasks_release,
|
|
|
+ .release = cgroup_pidlist_release,
|
|
|
};
|
|
|
|
|
|
/*
|
|
|
- * Handle an open on 'tasks' file. Prepare an array containing the
|
|
|
- * process id's of tasks currently attached to the cgroup being opened.
|
|
|
+ * The following functions handle opens on a file that displays a pidlist
|
|
|
+ * (tasks or procs). Prepare an array of the process/thread IDs of whoever's
|
|
|
+ * in the cgroup.
|
|
|
*/
|
|
|
-
|
|
|
-static int cgroup_tasks_open(struct inode *unused, struct file *file)
|
|
|
+/* helper function for the two below it */
|
|
|
+static int cgroup_pidlist_open(struct file *file, bool procs)
|
|
|
{
|
|
|
struct cgroup *cgrp = __d_cgrp(file->f_dentry->d_parent);
|
|
|
- pid_t *pidarray;
|
|
|
- int npids;
|
|
|
+ struct cgroup_pidlist *l = (procs ? &cgrp->procs : &cgrp->tasks);
|
|
|
int retval;
|
|
|
|
|
|
/* Nothing to do for write-only files */
|
|
|
if (!(file->f_mode & FMODE_READ))
|
|
|
return 0;
|
|
|
|
|
|
- /*
|
|
|
- * If cgroup gets more users after we read count, we won't have
|
|
|
- * enough space - tough. This race is indistinguishable to the
|
|
|
- * caller from the case that the additional cgroup users didn't
|
|
|
- * show up until sometime later on.
|
|
|
- */
|
|
|
- npids = cgroup_task_count(cgrp);
|
|
|
- pidarray = kmalloc(npids * sizeof(pid_t), GFP_KERNEL);
|
|
|
- if (!pidarray)
|
|
|
- return -ENOMEM;
|
|
|
- npids = pid_array_load(pidarray, npids, cgrp);
|
|
|
- sort(pidarray, npids, sizeof(pid_t), cmppid, NULL);
|
|
|
-
|
|
|
- /*
|
|
|
- * Store the array in the cgroup, freeing the old
|
|
|
- * array if necessary
|
|
|
- */
|
|
|
- down_write(&cgrp->pids_mutex);
|
|
|
- kfree(cgrp->tasks_pids);
|
|
|
- cgrp->tasks_pids = pidarray;
|
|
|
- cgrp->pids_length = npids;
|
|
|
- cgrp->pids_use_count++;
|
|
|
- up_write(&cgrp->pids_mutex);
|
|
|
-
|
|
|
- file->f_op = &cgroup_tasks_operations;
|
|
|
+ /* have the array populated */
|
|
|
+ retval = pidlist_array_load(cgrp, procs);
|
|
|
+ if (retval)
|
|
|
+ return retval;
|
|
|
+ /* configure file information */
|
|
|
+ file->f_op = &cgroup_pidlist_operations;
|
|
|
|
|
|
- retval = seq_open(file, &cgroup_tasks_seq_operations);
|
|
|
+ retval = seq_open(file, &cgroup_pidlist_seq_operations);
|
|
|
if (retval) {
|
|
|
- release_cgroup_pid_array(cgrp);
|
|
|
+ cgroup_release_pid_array(l);
|
|
|
return retval;
|
|
|
}
|
|
|
- ((struct seq_file *)file->private_data)->private = cgrp;
|
|
|
+ ((struct seq_file *)file->private_data)->private = l;
|
|
|
return 0;
|
|
|
}
|
|
|
+static int cgroup_tasks_open(struct inode *unused, struct file *file)
|
|
|
+{
|
|
|
+ return cgroup_pidlist_open(file, false);
|
|
|
+}
|
|
|
+static int cgroup_procs_open(struct inode *unused, struct file *file)
|
|
|
+{
|
|
|
+ return cgroup_pidlist_open(file, true);
|
|
|
+}
|
|
|
|
|
|
static u64 cgroup_read_notify_on_release(struct cgroup *cgrp,
|
|
|
struct cftype *cft)
|
|
@@ -2617,21 +2678,27 @@ static int cgroup_write_notify_on_release(struct cgroup *cgrp,
|
|
|
/*
|
|
|
* for the common functions, 'private' gives the type of file
|
|
|
*/
|
|
|
+/* for hysterical raisins, we can't put this on the older files */
|
|
|
+#define CGROUP_FILE_GENERIC_PREFIX "cgroup."
|
|
|
static struct cftype files[] = {
|
|
|
{
|
|
|
.name = "tasks",
|
|
|
.open = cgroup_tasks_open,
|
|
|
.write_u64 = cgroup_tasks_write,
|
|
|
- .release = cgroup_tasks_release,
|
|
|
- .private = FILE_TASKLIST,
|
|
|
+ .release = cgroup_pidlist_release,
|
|
|
.mode = S_IRUGO | S_IWUSR,
|
|
|
},
|
|
|
-
|
|
|
+ {
|
|
|
+ .name = CGROUP_FILE_GENERIC_PREFIX "procs",
|
|
|
+ .open = cgroup_procs_open,
|
|
|
+ /* .write_u64 = cgroup_procs_write, TODO */
|
|
|
+ .release = cgroup_pidlist_release,
|
|
|
+ .mode = S_IRUGO,
|
|
|
+ },
|
|
|
{
|
|
|
.name = "notify_on_release",
|
|
|
.read_u64 = cgroup_read_notify_on_release,
|
|
|
.write_u64 = cgroup_write_notify_on_release,
|
|
|
- .private = FILE_NOTIFY_ON_RELEASE,
|
|
|
},
|
|
|
};
|
|
|
|
|
@@ -2640,7 +2707,6 @@ static struct cftype cft_release_agent = {
|
|
|
.read_seq_string = cgroup_release_agent_show,
|
|
|
.write_string = cgroup_release_agent_write,
|
|
|
.max_write_len = PATH_MAX,
|
|
|
- .private = FILE_RELEASE_AGENT,
|
|
|
};
|
|
|
|
|
|
static int cgroup_populate_dir(struct cgroup *cgrp)
|