cgroups: add a read-only "procs" file similar to "tasks" that shows only unique tgids
struct cgroup used to have a bunch of fields for keeping track of the pidlist for the tasks file. Those are now separated into a new struct cgroup_pidlist, of which two are had, one for procs and one for tasks. The way the seq_file operations are set up is changed so that just the pidlist struct gets passed around as the private data. Interface example: Suppose a multithreaded process has pid 1000 and other threads with ids 1001, 1002, 1003: $ cat tasks 1000 1001 1002 1003 $ cat cgroup.procs 1000 $ Signed-off-by: Ben Blum <bblum@google.com> Signed-off-by: Paul Menage <menage@google.com> Acked-by: Li Zefan <lizf@cn.fujitsu.com> Cc: Matt Helsley <matthltc@us.ibm.com> Cc: "Eric W. Biederman" <ebiederm@xmission.com> Cc: Oleg Nesterov <oleg@redhat.com> Signed-off-by: Andrew Morton <akpm@linux-foundation.org> Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
This commit is contained in:
parent
8f3ff20862
commit
102a775e36
|
@ -141,6 +141,17 @@ enum {
|
|||
CGRP_WAIT_ON_RMDIR,
|
||||
};
|
||||
|
||||
struct cgroup_pidlist {
|
||||
/* protects the other fields */
|
||||
struct rw_semaphore mutex;
|
||||
/* array of xids */
|
||||
pid_t *list;
|
||||
/* how many elements the above list has */
|
||||
int length;
|
||||
/* how many files are using the current array */
|
||||
int use_count;
|
||||
};
|
||||
|
||||
struct cgroup {
|
||||
unsigned long flags; /* "unsigned long" so bitops work */
|
||||
|
||||
|
@ -179,14 +190,9 @@ struct cgroup {
|
|||
*/
|
||||
struct list_head release_list;
|
||||
|
||||
/* pids_mutex protects the fields below */
|
||||
struct rw_semaphore pids_mutex;
|
||||
/* Array of process ids in the cgroup */
|
||||
pid_t *tasks_pids;
|
||||
/* How many files are using the current tasks_pids array */
|
||||
int pids_use_count;
|
||||
/* Length of the current tasks_pids array */
|
||||
int pids_length;
|
||||
/* we will have two separate pidlists, one for pids (the tasks file)
|
||||
* and one for tgids (the procs file). */
|
||||
struct cgroup_pidlist tasks, procs;
|
||||
|
||||
/* For RCU-protected deletion */
|
||||
struct rcu_head rcu_head;
|
||||
|
|
278
kernel/cgroup.c
278
kernel/cgroup.c
|
@ -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);
|
||||
/* have the array populated */
|
||||
retval = pidlist_array_load(cgrp, procs);
|
||||
if (retval)
|
||||
return retval;
|
||||
/* configure file information */
|
||||
file->f_op = &cgroup_pidlist_operations;
|
||||
|
||||
/*
|
||||
* 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;
|
||||
|
||||
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)
|
||||
|
|
Loading…
Reference in New Issue