static int cgroup_mkdir(struct inode *dir, struct dentry *dentry, int mode)
在这个函数中,主要分配并初始化了一个cgroup结构.并且将它和它的上一层目录以及整个cgroupfs_root构成一个空间层次关系.然后,再调用subsys>create()操作函数.来让subsys知道已经创建了一个cgroup结构.
为了理顺这一部份.将前面分析的cgroup文件系统挂载和cgroup的创建.以及接下来要分析的attach_task()操作总结成一个图.如下示:
八:cgroup中文件的操作
接下来,就来看cgroup文件的操作.在上面曾分析到:文件对应的操作集为cgroup_file_operations.如下所示:
static struct file_operations cgroup_file_operations = {
.read = cgroup_file_read,
.write = cgroup_file_write,
.llseek = generic_file_llseek,
.open = cgroup_file_open,
.release = cgroup_file_release,
}
7.1:cgrou文件的open操作
对应的函数为cgroup_file_open().代码如下:
static int cgroup_file_open(struct inode *inode, struct file *file)
{
int err;
struct cftype *cft;
err = generic_file_open(inode, file);
if (err)
return err;
/*取得文件对应的struct cftype*/
cft = __d_cft(file->f_dentry);
if (!cft)
return -ENODEV;
/*如果定义了read_map或者是read_seq_string*/
if (cft->read_map || cft->read_seq_string) {
struct cgroup_seqfile_state *state =
kzalloc(sizeof(*state), GFP_USER);
if (!state)
return -ENOMEM;
state->cft = cft;
state->cgroup = __d_cgrp(file->f_dentry->d_parent);
file->f_op = &cgroup_seqfile_operations;
err = single_open(file, cgroup_seqfile_show, state);
if (err < 0)
kfree(state);
}
/*否则调用cft->open()*/
else if (cft->open)
err = cft->open(inode, file);
else
err = 0;
return err;
}
有两种情况.一种是定义了read_map或者是read_seq_string的情况.这种情况下,它对应的操作集为cgroup_seqfile_operations.如果是其它的情况.调用cftype的open()函数.第一种情况,我们等以后遇到了这样的情况再来详细分析.
7.2:cgroup文件的read操作
对应函数为cgroup_file_read().代码如下:
static ssize_t cgroup_file_read(struct file *file, char __user *buf,
size_t nbytes, loff_t *ppos)
{
struct cftype *cft = __d_cft(file->f_dentry);
struct cgroup *cgrp = __d_cgrp(file->f_dentry->d_parent);
if (!cft || cgroup_is_removed(cgrp))
return -ENODEV;
if (cft->read)
return cft->read(cgrp, cft, file, buf, nbytes, ppos);
if (cft->read_u64)
return cgroup_read_u64(cgrp, cft, file, buf, nbytes, ppos);
if (cft->read_s64)
return cgroup_read_s64(cgrp, cft, file, buf, nbytes, ppos);
return -EINVAL;
}
如上代码所示.read操作会转入到cftype的read()或者read_u64或者read_s64的函数中.
7.3:cgroup文件的wirte操作
对应的操作函数是cgroup_file_write().如下示:
static ssize_t cgroup_file_write(struct file *file, const char __user *buf,
size_t nbytes, loff_t *ppos)
{
struct cftype *cft = __d_cft(file->f_dentry);
struct cgroup *cgrp = __d_cgrp(file->f_dentry->d_parent);
if (!cft || cgroup_is_removed(cgrp))
return -ENODEV;
if (cft->write)
return cft->write(cgrp, cft, file, buf, nbytes, ppos);
if (cft->write_u64 || cft->write_s64)
return cgroup_write_X64(cgrp, cft, file, buf, nbytes, ppos);
if (cft->write_string)
return cgroup_write_string(cgrp, cft, file, buf, nbytes, ppos);
if (cft->trigger) {
int ret = cft->trigger(cgrp, (unsigned int)cft->private);
return ret ? ret : nbytes;
}
return -EINVAL;
}
从上面可以看到.最终的操作会转入到cftype的write或者wirte_u64或者wirte_string或者trigger函数中.
7.4:debug subsytem分析
以debug subsystem为例来说明cgroup中的文件操作
Debug subsys定义如下:
struct cgroup_subsys debug_subsys = {
.name = "debug",
.create = debug_create,
.destroy = debug_destroy,
.populate = debug_populate,
.subsys_id = debug_subsys_id,
}
在cgroup_init_subsys()中,会以dummytop为参数调用debug.create().对应函数为debug_create().代码如下:
static struct cgroup_subsys_state *debug_create(struct cgroup_subsys *ss,
struct cgroup *cont)
{
struct cgroup_subsys_state *css = kzalloc(sizeof(*css), GFP_KERNEL);
if (!css)
return ERR_PTR(-ENOMEM);
return css;
}
这里没啥好说的,就是分配了一个cgroup_subsys_state结构.
然后,将cgroup挂载.指令如下:
[root@localhost ~]# mount -t cgroup cgroup -o debug /dev/cgroup/
在rebind_subsystems()中,会调用subsys的bind函数.但在debug中无此接口.故不需要考虑.
然后在cgroup_populate_dir()中会调用populate接口.对应函数为debug_populate().代码如下:
static int debug_populate(struct cgroup_subsys *ss, struct cgroup *cont)
{
return cgroup_add_files(cont, ss, files, ARRAY_SIZE(files));
}
Debug中的files定义如下:
static struct cftype files[] = {
{
.name = "cgroup_refcount",
.read_u64 = cgroup_refcount_read,
},
{
.name = "taskcount",
.read_u64 = taskcount_read,
},
{
.name = "current_css_set",
.read_u64 = current_css_set_read,
},
{
.name = "current_css_set_refcount",
.read_u64 = current_css_set_refcount_read,
},
{
.name = "releasable",
.read_u64 = releasable_read,
},
}
来观察一下 /dev/cgroup下的文件:
[root@localhost ~]# tree /dev/cgroup/
/dev/cgroup/
|-- debug.cgroup_refcount
|-- debug.current_css_set
|-- debug.current_css_set_refcount
|-- debug.releasable
|-- debug.taskcount
|-- notify_on_release
|-- release_agent
`-- tasks
0 directories, 8 files
上面带debug字样的文件是从debug subsys中创建的.其它的是cgroup.c的files中创建的.
我们先来分析每一个subsys共有的文件.即tasks,release_agent和notify_on_release.
7.5:task文件操作
Tasks文件对应的cftype结构如下:
static struct cftype files[] = {
{
.name = "tasks",
.open = cgroup_tasks_open,
.write_u64 = cgroup_tasks_write,
.release = cgroup_tasks_release,
.private = FILE_TASKLIST,
}
7.5.1:task文件的open操作
当打开文件时,流程就会转入cgroup_tasks_open().代码如下:
static int cgroup_tasks_open(struct inode *unused, struct file *file)
{
/*取得该文件所在层次的cgroup*/
struct cgroup *cgrp = __d_cgrp(file->f_dentry->d_parent);
pid_t *pidarray;
int npids;
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.
*/
/*得到该层cgroup所关联的进程个数*/
npids = cgroup_task_count(cgrp);
/*为npids个进程的pid存放分配空间*/
pidarray = kmalloc(npids * sizeof(pid_t), GFP_KERNEL);
if (!pidarray)
return -ENOMEM;
/* 将与cgroup关联进程的pid存放到pid_array_load数组.
* 并且按照从小到大的顺序排列
*/
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
*/
/* 将npids,pidarray信息存放到cgroup中.如果cgroup之前
* 就有task_pids.将其占放的空间释放
*/
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);
/*将文件对应的操作集更改为cgroup_task_operations*/
file->f_op = &cgroup_tasks_operations;
retval = seq_open(file, &cgroup_tasks_seq_operations);
/*如果操作失败,将cgroup中的pid信息释放*/
if (retval) {
release_cgroup_pid_array(cgrp);
return retval;
}
((struct seq_file *)file->private_data)->private = cgrp;
return 0;
}
首先,我们来思考一下这个问题:怎么得到与cgroup关联的进程呢?
回到在上面列出来的数据结构关系图.每个进程都会指向一个css_set.而与这个css_set关联的所有进程都会链入到css_set->tasks链表.而cgroup又可能通过一个中间结构cg_cgroup_link来寻找所有与之关联的所有css_set.从而可以得到与cgroup关联的所有进程.
在上面的代码中,通过调用cgroup_task_count()来得到与之关联的进程数目,代码如下:
int cgroup_task_count(const struct cgroup *cgrp)
{
int count = 0;
struct cg_cgroup_link *link;
read_lock(&css_set_lock);
list_for_each_entry(link, &cgrp->css_sets, cgrp_link_list) {
count += atomic_read(&link->cg->refcount);
}
read_unlock(&css_set_lock);
return count;
}
它就是遍历cgro->css_sets.并调其转换为cg_cgroup_link.再从这个link得到css_set.这个css_set的引用计数就是与这个指向这个css_set的task数目.
在代码中,是通过pid_array_load()来得到与cgroup关联的task,并且将进程的pid写入数组pidarray中.代码如下:
static int pid_array_load(pid_t *pidarray, int npids, struct cgroup *cgrp)
{
int n = 0;
struct cgroup_iter it;
struct task_struct *tsk;
cgroup_iter_start(cgrp, &it);
while ((tsk = cgroup_iter_next(cgrp, &it))) {
if (unlikely(n == npids))
break;
pidarray[n++] = task_pid_vnr(tsk);
}
cgroup_iter_end(cgrp, &it);
return n;
}
我们在这里遇到了一个新的结构:struct cgroup_iter.它是cgroup的一个迭代器,通过它可以遍历取得与cgroup关联的task.它的使用方法为:
1:调用cgroup_iter_start()来初始化这个迭代码.
2:调用cgroup_iter_next()用来取得cgroup中的下一个task
3:使用完了,调用cgroup_iner_end().
下面来分析这三个过程:
Cgroup_iter_start()代码如下:
void cgroup_iter_start(struct cgroup *cgrp, struct cgroup_iter *it)
{
/*
* The first time anyone tries to iterate across a cgroup,
* we need to enable the list linking each css_set to its
* tasks, and fix up all existing tasks.
*/
if (!use_task_css_set_links)
cgroup_enable_task_cg_lists();
read_lock(&css_set_lock);
it->cg_link = &cgrp->css_sets;
cgroup_advance_iter(cgrp, it);
}
我们在这里再次遇到了use_task_css_set_links变量.在之前分析cgroup_post_fork()中的时候,我们曾说过,只有在use_task_css_set_link设置为1的时候,才会调task->cg_list链入到css_set->tasks中.
所以,在这个地方,如果use_task_css_set_link为0.那就必须要将之前所有的进程都链入到它所指向的css_set->tasks链表.这个过程是在cgroup_enable_task_cg_lists()完成的,这个函数相当简单,就是一个task的遍历,然后就是链表的链入,在这里就不再详细分析了.请自行阅读它的代码.*^_^*
然后,将it->cg_link指向cgrp->css_sets.我们在前面说过,可以通过cgrp->css_sets就可以得得所有的与cgroup关联的css_set.
到这里,这个迭代器里面还是空的,接下来往里面填充数据.这个过程是在cgroup_advance_iter()中完成,代码如下示:
static void cgroup_advance_iter(struct cgroup *cgrp,
struct cgroup_iter *it)
{
struct list_head *l = it->cg_link;
struct cg_cgroup_link *link;
struct css_set *cg;
/* Advance to the next non-empty css_set */
do {
l = l->next;
if (l == &cgrp->css_sets) {
it->cg_link = NULL;
return;
}
link = list_entry(l, struct cg_cgroup_link, cgrp_link_list);
cg = link->cg;
} while (list_empty(&cg->tasks));
it->cg_link = l;
it->task = cg->tasks.next;
}
通过前面的分析可得知,可通过it->cg_link找到与之关联的css_set,然后再通过css_set找到与它关联的task链表.因此每次往cgroup迭代器里填充数据,就是找到一个tasks链表不为空的css_set.取数据就从css_set->tasks中取.如果数据取完了,就找下一个tasks链表不为空的css_set.
这样,这个函数的代码就很简单了.它就是找到it->cg_link上tasks链表不为空的css_set项.
cgroup_iter_next()的代码如下:
struct task_struct *cgroup_iter_next(struct cgroup *cgrp,
struct cgroup_iter *it)
{
struct task_struct *res;
struct list_head *l = it->task;
/* If the iterator cg is NULL, we have no tasks */
if (!it->cg_link)
return NULL;
res = list_entry(l, struct task_struct, cg_list);
/* Advance iterator to find next entry */
l = l->next;
if (l == &res->cgroups->tasks) {
/* We reached the end of this task list - move on to
* the next cg_cgroup_link */
cgroup_advance_iter(cgrp, it);
} else {
it->task = l;
}
return res;
}
如果it->cg_link为空表示it->cg_link已经遍历完了,也就不存放在task了.否则,从it->task中取得task.如果已经是最后一个task就必须要调用cgroup_advance_iter()填充迭代器里面的数据.最后将取得的task返回.
cgroup_iter_end()用来对迭代码进行收尾的工作,代码如下:
void cgroup_iter_end(struct cgroup *cgrp, struct cgroup_iter *it)
{
read_unlock(&css_set_lock);
}
它就是释放了在cgroup_iter_start()中持有的锁.
回到cgroup_tasks_open()中.我们接下来会遇到kernel为sequential file提供的一组接口.首先在代码遇到的是seq_open().代码如下:
int seq_open(struct file *file, const struct seq_operations *op)
{
struct seq_file *p = file->private_data;
if (!p) {
p = kmalloc(sizeof(*p), GFP_KERNEL);
if (!p)
return -ENOMEM;
file->private_data = p;
}
memset(p, 0, sizeof(*p));
mutex_init(&p->lock);
p->op = op;
file->f_version = 0;
/* SEQ files support lseek, but not pread/pwrite */
file->f_mode &= ~(FMODE_PREAD | FMODE_PWRITE);
return 0;
}
从代码中可以看出,它就是初始化了一个struct seq_file结构.并且将其关联到file->private_data.在这里要注意将seq_file->op设置成了参数op.在我们分析的这个情景中,也就是cgroup_tasks_seq_operations.这个在我们分析文件的读操作的时候会用到的.
7.5.2:task文件的read操作
从上面的代码中可看到.在open的时候,更改了file->f_op.将其指向了cgroup_tasks_operations.该结构如下:
static struct file_operations cgroup_tasks_operations = {
.read = seq_read,
.llseek = seq_lseek,
.write = cgroup_file_write,
.release = cgroup_tasks_release,
}
相应的,read操作就会转入到seq_read()中.由于该函数篇幅较大,这里就不列出了.感兴趣的可以自己跟踪看一下,其它就是循环调用seq_file->op->start() à seq_file->op->show() à seq_file->op->next() à seq_file->op->stop()的过程.
我们在上面分析task文件的open操作的时候,曾经提配过,seq_file->op被指向了cgroup_tasks_seq_operations.定义如下:
static struct seq_operations cgroup_tasks_seq_operations = {
.start = cgroup_tasks_start,
.stop = cgroup_tasks_stop,
.next = cgroup_tasks_next,
.show = cgroup_tasks_show,
}
Cgroup_tasks_start()代码如下:
static void *cgroup_tasks_start(struct seq_file *s, loff_t *pos)
{
/*
* Initially we receive a position value that corresponds to
* one more than the last pid shown (or 0 on the first call or
* after a seek to the start). Use a binary-search to find the
* next pid to display, if any
*/
struct cgroup *cgrp = s->private;
int index = 0, pid = *pos;
int *iter;
down_read(&cgrp->pids_mutex);
if (pid) {
int end = cgrp->pids_length;
while (index < end) {
int mid = (index + end) / 2;
if (cgrp->tasks_pids[mid] == pid) {
index = mid;
break;
} else if (cgrp->tasks_pids[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)
return NULL;
/* Update the abstract position to be the actual pid that we found */
iter = cgrp->tasks_pids + index;
*pos = *iter;
return iter;
}
它以二分法从cgrp->tasks_pids[ ]中去寻找第一个大于或者等于参数*pos值的项.如果找到了,返回该项.如果没找到.返回NULL.
cgroup_tasks_show()代码如下:
static int cgroup_tasks_show(struct seq_file *s, void *v)
{
return seq_printf(s, "%d\n", *(int *)v);
}
它就是将pid转换为了字符串.
cgroup_tasks_next()就是找到数组中的下一项.代码如下:
static void *cgroup_tasks_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;
/*
* Advance to the next pid in the array. If this goes off the
* end, we're done
*/
p++;
if (p >= end) {
return NULL;
} else {
*pos = *p;
return p;
}
}
cgroup_tasks_stop()代码如下:
static void cgroup_tasks_stop(struct seq_file *s, void *v)
{
struct cgroup *cgrp = s->private;
up_read(&cgrp->pids_mutex);
}
它只是释放了在cgroup_tasks_start()中持有的读写锁.
7.5.3:task文件的close操作
Task文件close时,调用的相应接口为cgroup_tasks_release().代码如下:
static int cgroup_tasks_release(struct inode *inode, struct file *file)
{
struct cgroup *cgrp = __d_cgrp(file->f_dentry->d_parent);
if (!(file->f_mode & FMODE_READ))
return 0;
release_cgroup_pid_array(cgrp);
return seq_release(inode, file);
}
它就是将cgroup中的pid信息与seqfile信息释放掉.
到这里,我们已经分析完了task文件的open,read,close操作.我们现在就可以实现一下,看上面的分析是否正确.
在前面已经分析中cgroupfs_root.top_cgroup会将系统中的所有css_set与之关联起来,那么通过cgroupfs_root_top_cgroup找到的进程应该是系统当前的所有进程.那么相应的,在挂载目录的task文件的内容.应该是系统中所有进程的pid.
如下所示:
[root@localhost cgroup]# cat tasks
1
2
3
………
………
2578
其实,这样做是cgroup子系统开发者特意设置的.它表示所有的进程都在hierarchy的控制之下.
反过来,当我们在挂载目录mkdir一个目录,它下面的task文件内容应该是空的.因为在mkdir后,它对应的cgroup并没有关联任何task.
如下所示:
[root@localhost cgroup]# mkdir eric
[root@localhost cgroup]# cat eric/tasks
[root@localhost cgroup]#
下面我们来看一下task文件的写操作,也就是怎样将进程添加进cgroup.
7.5.4:task文件的write操作
根据上面的文件,可得知task文件的write操作对应的函数为int cgroup_tasks_write().代码如下:
static int cgroup_tasks_write(struct cgroup *cgrp, struct cftype *cft, u64 pid)
{
int ret;
/*如果cgroup已经被移除了,非法*/
if (!cgroup_lock_live_group(cgrp))
return -ENODEV;
/*将PID为pid的进程与cgroup关联*/
ret = attach_task_by_pid(cgrp, pid);
cgroup_unlock();
return ret;
}
Attach_task_by_pid()的代码如下:
static int attach_task_by_pid(struct cgroup *cgrp, u64 pid)
{
struct task_struct *tsk;
int ret;
/*如果pid不为0.寻找PID为pid的task.并增加其引用计数*/
if (pid) {
rcu_read_lock();
tsk = find_task_by_vpid(pid);
if (!tsk || tsk->flags & PF_EXITING) {
rcu_read_unlock();
return -ESRCH;
}
get_task_struct(tsk);
rcu_read_unlock();
if ((current->euid) && (current->euid != tsk->uid)
&& (current->euid != tsk->suid)) {
put_task_struct(tsk);
return -EACCES;
}
}
/*如果pid为0.表示是将当前进程添加进cgroup*/
else {
tsk = current;
get_task_struct(tsk);
}
/*将cgroup与task相关联*/
ret = cgroup_attach_task(cgrp, tsk);
/*操作完成,减少其引用计数*/
put_task_struct(tsk);
return ret;
}
如果写入的是一个不这0的数,表示的是进程的PID值.如果是写入0,表示是将当前进程.这个操作的核心操作是cgroup_attach_task().代码如下:
int cgroup_attach_task(struct cgroup *cgrp, struct task_struct *tsk)
{
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;
/*得到与cgroup关联的第一个subsys的序号*/
get_first_subsys(cgrp, NULL, &subsys_id);
/* Nothing to do if the task is already in that cgroup */
/*找到这个进程之前所属的cgroup*/
oldcgrp = task_cgroup(tsk, subsys_id);
/*如果已经在这个cgrp里面了.*/
if (cgrp == oldcgrp)
return 0;
/* 遍历与hierarchy关联的subsys
* 如果subsys定义了can_attach函数,就调用它
*/
for_each_subsys(root, ss) {
if (ss->can_attach) {
retval = ss->can_attach(ss, cgrp, tsk);
if (retval)
return retval;
}
}
/*
* Locate or allocate a new css_set for this task,
* based on its final set of cgroups
*/
/*找到这个task所关联的css_set.如果不存在,则新建一个*/
newcg = find_css_set(cg, cgrp);
if (!newcg)
return -ENOMEM;
task_lock(tsk);
/*如果task正在执行exit操作*/
if (tsk->flags & PF_EXITING) {
task_unlock(tsk);
put_css_set(newcg);
return -ESRCH;
}
/*将tak->cgroup指向这个css_set*/
rcu_assign_pointer(tsk->cgroups, newcg);
task_unlock(tsk);
/* Update the css_set linked lists if we're using them */
/*更改task->cg_list*/
write_lock(&css_set_lock);
if (!list_empty(&tsk->cg_list)) {
list_del(&tsk->cg_list);
list_add(&tsk->cg_list, &newcg->tasks);
}
write_unlock(&css_set_lock);
/* 遍历与hierarchy关联的subsys
* 如果subsys定义了attach 函数,就调用它
*/
for_each_subsys(root, ss) {
if (ss->attach)
ss->attach(ss, cgrp, oldcgrp, tsk);
}
set_bit(CGRP_RELEASABLE, &oldcgrp->flags);
synchronize_rcu();
/*减小旧指向的引用计数*/
put_css_set(cg);
return 0;
}
这个函数逻辑很清楚,它就是初始化task->cgroup.然后将它和subsys相关联.可自行参照代码中的注释进行分析.这里就不再赘述了.
在这里,详细分析一下find_css_set()函数,这个函数有点意思.代码如下:
static struct css_set *find_css_set(
struct css_set *oldcg, struct cgroup *cgrp)
{
struct css_set *res;
struct cgroup_subsys_state *template[CGROUP_SUBSYS_COUNT];
int i;
struct list_head tmp_cg_links;
struct cg_cgroup_link *link;
struct hlist_head *hhead;
/* First see if we already have a cgroup group that matches
* the desired set */
read_lock(&css_set_lock);
/*寻找从oldcg转换为cgrp的css_set.如果不存在,返回NULL */
res = find_existing_css_set(oldcg, cgrp, template);
/*如果css_set已经存在,增加其引用计数后退出*/
if (res)
get_css_set(res);
read_unlock(&css_set_lock);
if (res)
return res;
这一部份,先从哈希数组中搜索从oldcg转换cgrp的css_set.如果不存在,返回NULL.如果在哈希数组中存放,增加其引用计数返回即可.
Find_existing_css_set()的代码如下:
static struct css_set *find_existing_css_set(
struct css_set *oldcg,
struct cgroup *cgrp,
struct cgroup_subsys_state *template[])
{
int i;
struct cgroupfs_root *root = cgrp->root;
struct hlist_head *hhead;
struct hlist_node *node;
struct css_set *cg;
/* Built the set of subsystem state objects that we want to
* see in the new css_set */
for (i = 0; i < CGROUP_SUBSYS_COUNT; i++) {
if (root->subsys_bits & (1UL << i)) {
/* Subsystem is in this hierarchy. So we want
* the subsystem state from the new
* cgroup */
template[i] = cgrp->subsys[i];
} else {
/* Subsystem is not in this hierarchy, so we
* don't want to change the subsystem state */
template[i] = oldcg->subsys[i];
}
}
hhead = css_set_hash(template);
hlist_for_each_entry(cg, node, hhead, hlist) {
if (!memcmp(template, cg->subsys, sizeof(cg->subsys))) {
/* All subsystems matched */
return cg;
}
}
/* No existing cgroup group matched */
return NULL;
}
如果subsys与新的cgroup相关联,那么它指向新的cgroup->subsys[]中的对应项.否则指向旧的cgrop的对应项.这样做主要是因为,该进程可能还被关联在其它的hierarchy中.所以要保持它在其它hierarchy中的信息.
最后,在css_set_table[ ]中寻找看是否有与template相等的项.有的话返回该项.如果没有.返回NULL.
/*分配一个css_set*/
res = kmalloc(sizeof(*res), GFP_KERNEL);
if (!res)
return NULL;
/* Allocate all the cg_cgroup_link objects that we'll need */
/*分配root_count项cg_cgroup_link*/
if (allocate_cg_links(root_count, &tmp_cg_links) < 0) {
kfree(res);
return NULL;
}
/* 初始化刚分配的css_set */
atomic_set(&res->refcount, 1);
INIT_LIST_HEAD(&res->cg_links);
INIT_LIST_HEAD(&res->tasks);
INIT_HLIST_NODE(&res->hlist);
/* Copy the set of subsystem state objects generated in
* find_existing_css_set() */
/*设置css_set->subsys*/
memcpy(res->subsys, template, sizeof(res->subsys));
运行到这里的话.表示没有从css_set_table[ ]中找到相应项.因此需要分配并初始化一个css_set结构.并且设置css_set的subsys域.
write_lock(&css_set_lock);
/* Add reference counts and links from the new css_set. */
/*遍历所有的subsys以及css_set 中的subsys[ ].
*建立task所在的cgroup到css_set的引用
*/
for (i = 0; i < CGROUP_SUBSYS_COUNT; i++) {
struct cgroup *cgrp = res->subsys[i]->cgroup;
struct cgroup_subsys *ss = subsys[i];
atomic_inc(&cgrp->count);
/*
* We want to add a link once per cgroup, so we
* only do it for the first subsystem in each
* hierarchy
*/
if (ss->root->subsys_list.next == &ss->sibling) {
BUG_ON(list_empty(&tmp_cg_links));
link = list_entry(tmp_cg_links.next,
struct cg_cgroup_link,
cgrp_link_list);
list_del(&link->cgrp_link_list);
list_add(&link->cgrp_link_list, &cgrp->css_sets);
link->cg = res;
list_add(&link->cg_link_list, &res->cg_links);
}
}
/*似乎没有地方会更改rootnode.subsys_list.?这里的判断大部份情况是满足的*/
if (list_empty(&rootnode.subsys_list)) {
/*建立这个css_set到dumytop的引用*/
/* 这样做,是为了让新建的hierarchy能够关联到所有的进程*/
link = list_entry(tmp_cg_links.next,
struct cg_cgroup_link,
cgrp_link_list);
list_del(&link->cgrp_link_list);
list_add(&link->cgrp_link_list, &dummytop->css_sets);
link->cg = res;
list_add(&link->cg_link_list, &res->cg_links);
}
BUG_ON(!list_empty(&tmp_cg_links));
这一部份的关键操作都在代码中添加了相应的注释.如果系统中存在多个hierarchy.那么这个进程肯定也位于其它的hierarchy所对应的cgroup中.因此需要在新分配的css_set中保存这些信息,也就是建立从cgroup到css_set的引用.
另外,关于ist_empty(&rootnode.subsys_list)的操作.似乎没看到有什么地方会更改rootnode.subsys_list.不过,如果rootnode.subsys_list不为空的话,也会在它前面的for循环中检测出来.
总而言之.系统中有root_count个hierarchy.上述的引用保存过程就会进行root_count次.因此.到最后.tmp_cg_links肯定会空了.如果不为空.说明某处发生了错误.
/*增加css_set计数*/
css_set_count++;
/* Add this cgroup group to the hash table */
/*将其添加到全局哈希数组: css_set_table[ ]*/
hhead = css_set_hash(res->subsys);
hlist_add_head(&res->hlist, hhead);
write_unlock(&css_set_lock);
return res;
}
最后,将生成的css_set添加到哈希数组css_set_table[ ]中.
到这里,task文件的操作已经分析完了.
7.6:
notify_on_release文件操作
notify_on_release文件对应的cftype结构如下:
{
.name = "notify_on_release",
.read_u64 = cgroup_read_notify_on_release,
.write_u64 = cgroup_write_notify_on_release,
.private = FILE_NOTIFY_ON_RELEASE,
}
从此得知.文件的读操作接口为cgroup_read_notify_on_release().代码如下:
static u64 cgroup_read_notify_on_release(struct cgroup *cgrp,
struct cftype *cft)
{
return notify_on_release(cgrp);
}
继续跟进notify_on_release().如下示:
static int notify_on_release(const struct cgroup *cgrp)
{
return test_bit(CGRP_NOTIFY_ON_RELEASE, &cgrp->flags);
}
从此可以看到,如果当前cgroup设置了CGRP_NOTIFY_ON_RELEASE标志.就会返回1.否则.就是为0.
从当前系统中测试一下,如下:
[root@localhost cgroup]# cat notify_on_release
0
[root@localhost cgroup]#
文件内容为零.因为top_cgroup上没有设置CGRP_NOTIFY_ON_RELEASE的标志.
notify_on_release文件读操作接口为cgroup_write_notify_on_release().代码如下:
static int cgroup_write_notify_on_release(struct cgroup *cgrp,
struct cftype *cft,
u64 val)
{
clear_bit(CGRP_RELEASABLE, &cgrp->flags);
if (val)
set_bit(CGRP_NOTIFY_ON_RELEASE, &cgrp->flags);
else
clear_bit(CGRP_NOTIFY_ON_RELEASE, &cgrp->flags);
return 0;
}
从上面的代码可以看到.如果我们写入的是1.就会设置cgroup标志的CGRP_NOTIFY_ON_RELEASE位.否则.清除CGRP_NOTIFY_ON_RELEASE位.测试如下:
[root@localhost cgroup]# echo 1 > notify_on_release
[root@localhost cgroup]# cat notify_on_release
1
[root@localhost cgroup]# echo 0 > notify_on_release
[root@localhost cgroup]# cat notify_on_release
0
[root@localhost cgroup]#
7.7:
release_agent文件操作
release_agent只有在顶层目录才会有.它所代表的cftype结构如下:
static struct cftype cft_release_agent = {
.name = "release_agent",
.read_seq_string = cgroup_release_agent_show,
.write_string = cgroup_release_agent_write,
.max_write_len = PATH_MAX,
.private = FILE_RELEASE_AGENT,
};
由此可以看到.读文件的接口为cgroup_release_agent_show.代码如下:
static int cgroup_release_agent_show(struct cgroup *cgrp, struct cftype *cft,
struct seq_file *seq)
{
if (!cgroup_lock_live_group(cgrp))
return -ENODEV;
seq_puts(seq, cgrp->root->release_agent_path);
seq_putc(seq, '\n');
cgroup_unlock();
return 0;
}
从代码中可以看到.就是打印出root的release_agent_path.
写文件的接口为cgroup_release_agent_write().如下示:
static int cgroup_release_agent_write(struct cgroup *cgrp, struct cftype *cft,
const char *buffer)
{
BUILD_BUG_ON(sizeof(cgrp->root->release_agent_path) < PATH_MAX);
if (!cgroup_lock_live_group(cgrp))
return -ENODEV;
strcpy(cgrp->root->release_agent_path, buffer);
cgroup_unlock();
return 0;
}
由此得知.往这个文件中写内容,就是设置root的release_agent_path.如下做个测试:
[root@localhost cgroup]# cat release_agent
[root@localhost cgroup]# echo /bin/ls > release_agent
[root@localhost cgroup]# cat release_agent
/bin/ls
[root@localhost cgroup]#
7.8:debug创建的文件分析
下面分析一下debug subsys中的文件.由于我们挂载的时候没有带noprefix.因为.debug生成的文件都带了一个”debug_”前缀.由debug创建的文件如下示:
debug.cgroup_refcount debug.current_css_set_refcount debug.taskcount debug.current_css_set debug.releasable
挨个分析如下:
7.8.1: cgroup_refcount文件操作
Cgroup_refcount所代表的cftype结构如下示:
{
.name = "cgroup_refcount",
.read_u64 = cgroup_refcount_read,
},
可以看到,该文件不能写,只能读.读操作接口为cgroup_refcount_read().代码如下:
static u64 cgroup_refcount_read(struct cgroup *cont, struct cftype *cft)
{
return atomic_read(&cont->count);
}
它就是显示出当前cgroup的引用计数.
测试如下:
[root@localhost cgroup]# cat debug.cgroup_refcount
0
[root@localhost cgroup]#
顶层的cgroup是位于cgroupfs_root.top_cgroup.它的引用计数为0.
接下来,我们在下层创建一个子层cgroup.如下示:
[root@localhost cgroup]# mkdir /dev/cgroup/eric
[root@localhost cgroup]# cat /dev/cgroup/eric/debug.cgroup_refcount
0
[root@localhost cgroup]#
可见创建子层cgroup不会增加其引用计数.因为它只是与它的上一层cgroup构成指针指向关系.
现在我们让子层cgroup关联一个进程
[root@localhost cgroup]# echo 1673 > /dev/cgroup/eric/tasks
[root@localhost cgroup]# cat /dev/cgroup/eric/debug.cgroup_refcount
1
[root@localhost cgroup]#
可以看到.它的计数比为了1.这里在关联进程的css_set和所在的cgroup时增加的.
7.8.2:
current_css_set文件操作
current_css_set对应的cftype结构如下示:
{
.name = "current_css_set",
.read_u64 = current_css_set_read,
},
可看出.它也是一个只读的.读接口为current_css_set_read().代码如下:
static u64 current_css_set_read(struct cgroup *cont, struct cftype *cft)
{
return (u64)(long)current->cgroups;
}
它就是显示了当前进程关联的css_set的地址.
测试如下:
[root@localhost cgroup]# cat debug.current_css_set
18446744072645980768
7.8.3:
current_css_set_refcount文件操作
current_css_set_refcount文件对应的ctype结构如下:
{
.name = "current_css_set_refcount",
.read_u64 = current_css_set_refcount_read,
},
照例.它也是只读的.接口如下:
static u64 current_css_set_refcount_read(struct cgroup *cont,
struct cftype *cft)
{
u64 count;
rcu_read_lock();
count = atomic_read(¤t->cgroups->refcount);
rcu_read_unlock();
return count;
}
它就是显示出与当前进程关联的css_set的引用计数.
测试如下:
[root@localhost cgroup]# cat debug.current_css_set_refcount
56
表示已经有56个进程关联到这个css_set了.
7.8.3:
taskcount文件操作
Taskcount文件对应cftype结构如下:
{
.name = "taskcount",
.read_u64 = taskcount_read,
},
只读文件.接口如下:
static u64 taskcount_read(struct cgroup *cont, struct cftype *cft)
{
u64 count;
cgroup_lock();
count = cgroup_task_count(cont);
cgroup_unlock();
return count;
}
其中,子函数cgroup_task_count()我们在之前已经分析过了.它就是计算与当前cgroup关联的进程数目.这里就不再分析了.测试如下:
[root@localhost cgroup]# cat debug.taskcount
56
7.8.4:
releasable文件操作
Releasable文件对应的ctype结构如下示:
{
.name = "releasable",
.read_u64 = releasable_read,
},
只读,读接口代码如下:
static u64 releasable_read(struct cgroup *cgrp, struct cftype *cft)
{
return test_bit(CGRP_RELEASABLE, &cgrp->flags);
}
它用来查看当前cgroup是否有CGRP_RELEASABLE标志.如果有.显示为1.否则显示为0.
测试如下:
[root@localhost cgroup]# cat debug.releasable
0
经过上面的分析.可以知道.如果往cgroup中删除一个关联进程,就会将其设置CGRP_RELEASABLE标志.有下面测试:
[root@localhost cgroup]# mkdir eric
[root@localhost cgroup]# cat eric/debug.releasable
0
[root@localhost cgroup]# echo 1650 > eric/tasks
[root@localhost cgroup]# echo 1701 > eric/tasks
[root@localhost cgroup]# cat eric/debug.releasable
0
[root@localhost cgroup]# echo 1650 >tasks
[root@localhost cgroup]# cat eric/debug.releasable
1
到这里为止,各subsys共有的文件和debug中的文件操作就已经分析完了.其它的subsys远远比debug要复杂.之后再给出专题分析.详情请关注本站更新.*^_^*
九:
notify_on_release操作
下面我们来分析在之前一直在忽略的一个问题.也就是涉及到CGRP_NOTIFY_ON_RELEASE标志和root->
release_agent_path[]部份.
它的重用,就是在cgroup中最后的一个进程离开(包括进程退出.进程关联到其它同类型的cgroup),或者是在最后一个子层cgroup被移除的时候.就会调用用户空间的一个程序.这个程序的路径是在root->
release_agent_path[]中指定的.
下面我们从代码的角度来跟踪一下.
9.1:进程退出
我们在之前在分析父子进程之间的cgroup关系的时候.忽略掉了__put_css_set函数中的一个部份.现在是时候来剥开它了.
次__put_css_set()被忽略的代码片段列出,如下:
static void __put_css_set(struct css_set *cg, int taskexit)
{
......
......
for (i = 0; i < CGROUP_SUBSYS_COUNT; i++) {
struct cgroup *cgrp = cg->subsys[i]->cgroup;
if (atomic_dec_and_test(&cgrp->count) &&
notify_on_release(cgrp)) {
if (taskexit)
set_bit(CGRP_RELEASABLE, &cgrp->flags);
check_for_release(cgrp);
}
}
......
......
}
首先,进程退出时,调用__put_css_set时.taskexit参数是为1的,因此在这里,它会将cgroup的flag的CGRP_RELEASABLE位置1.
atomic_dec_and_test(&cgrp->count)返回为真的话,说明进程所属的cgroup中已经没有其它的进程了.因此即将要退出的子进程就是cgroup中的最后一个进程.
notify_on_release(cgrp)代码如下:
static int notify_on_release(const struct cgroup *cgrp)
{
return test_bit(CGRP_NOTIFY_ON_RELEASE, &cgrp->flags);
}
它用来判断cgroup有没有设定CGRP_NOTIFY_ON_RELEASE标志
综合上面的分析.如果cgroup中最后一个进程退出.且cgroup设定了CGRP_NOTIFY_ON_RELEASE标志.流程就会转到check_for_release()中.该函数代码如下:
static void check_for_release(struct cgroup *cgrp)
{
/* All of these checks rely on RCU to keep the cgroup
* structure alive */
if (cgroup_is_releasable(cgrp) && !atomic_read(&cgrp->count)
&& list_empty(&cgrp->children) && !cgroup_has_css_refs(cgrp)) {
/* Control Group is currently removeable. If it's not
* already queued for a userspace notification, queue
* it now */
int need_schedule_work = 0;
spin_lock(&release_list_lock);
if (!cgroup_is_removed(cgrp) &&
list_empty(&cgrp->release_list)) {
list_add(&cgrp->release_list, &release_list);
need_schedule_work = 1;
}
spin_unlock(&release_list_lock);
if (need_schedule_work)
schedule_work(&release_agent_work);
}
}
首先,在这里必须要满足以下四个条件才能继续下去:
1:cgroup_is_releasable()返回1.
代码如下:
static int cgroup_is_releasable(const struct cgroup *cgrp)
{
const int bits =
(1 << CGRP_RELEASABLE) |
(1 << CGRP_NOTIFY_ON_RELEASE);
return (cgrp->flags & bits) == bits;
}
它表示当前cgroup是含含有CGRP_RELEASABLE和CGRP_NOTIFY_ON_RELEASE标志.结合我们在上面分析的. CGRP_RELEASABLE标志是进程在退出是就会设置的.
2:cgroup的引用计数为0
3:cgroup没有子层cgroup
4:
cgroup_has_css_refs()返回0.代码如下:
static int cgroup_has_css_refs(struct cgroup *cgrp)
{
int i;
for (i = 0; i < CGROUP_SUBSYS_COUNT; i++) {
struct cgroup_subsys *ss = subsys[i];
struct cgroup_subsys_state *css;
/* Skip subsystems not in this hierarchy */
if (ss->root != cgrp->root)
continue;
css = cgrp->subsys[ss->subsys_id];
if (css && atomic_read(&css->refcnt))
return 1;
}
return 0;
}
也就是说,cgroup关联的css_set引用计数必须要为0
满足上面几个条件之后.就说明该cgroup是可以释放的.因此将cgroup链接到了release_list.接着调度了工作队列.在工作队列中会完成余下的工作.
下面跟踪看看这个工作队列是怎么处理余下任务的.
release_agent_work定义如下:
static DECLARE_WORK(release_agent_work, cgroup_release_agent);
该工作队列对应的处理函数为cgroup_release_agent().代码如下:
static void cgroup_release_agent(struct work_struct *work)
{
BUG_ON(work != &release_agent_work);
mutex_lock(&cgroup_mutex);
spin_lock(&release_list_lock);
/*遍历链表,直到其为空*/
while (!list_empty(&release_list)) {
char *argv[3], *envp[3];
int i;
char *pathbuf = NULL, *agentbuf = NULL;
/*取得链表项对应的cgroup*/
struct cgroup *cgrp = list_entry(release_list.next,
struct cgroup,
release_list);
/*将cgroup从release_list中断开*/
list_del_init(&cgrp->release_list);
spin_unlock(&release_list_lock);
/*将cgroup的路径存放到pathbuf中*/
pathbuf = kmalloc(PAGE_SIZE, GFP_KERNEL);
if (!pathbuf)
goto continue_free;
if (cgroup_path(cgrp, pathbuf, PAGE_SIZE) < 0)
goto continue_free;
/*agentbuf存放release_agent_path的内容*/
agentbuf = kstrdup(cgrp->root->release_agent_path, GFP_KERNEL);
if (!agentbuf)
goto continue_free;
/*初始化运行参数和环境变量*/
i = 0;
argv[i++] = agentbuf;
argv[i++] = pathbuf;
argv[i] = NULL;
i = 0;
/* minimal command environment */
envp[i++] = "HOME=/";
envp[i++] = "PATH=/sbin:/bin:/usr/sbin:/usr/bin";
envp[i] = NULL;
/* Drop the lock while we invoke the usermode helper,
* since the exec could involve hitting disk and hence
* be a slow process */
/*调用用户空间的进程*/
mutex_unlock(&cgroup_mutex);
call_usermodehelper(argv[0], argv, envp, UMH_WAIT_EXEC);
mutex_lock(&cgroup_mutex);
continue_free:
kfree(pathbuf);
kfree(agentbuf);
spin_lock(&release_list_lock);
}
spin_unlock(&release_list_lock);
mutex_unlock(&cgroup_mutex);
}
该函数遍历release_list中的cgroup.然后以其路径做为参数.调用root->release_agent_path对应的程序.
我们来做如下的实验:
为了配合这次实验.必须要写两个测试的程序.代码如下:
Test.c
#include <stdio.h>
#include <stdlib.h>
main()
{
int i = 30;
while(i){
i--;
sleep(1);
}
}
这个进程睡眠30s之后退出.编译成test
另外一个程序代码如下:
Main.c
#include <stdio.h>
#include <stdlib.h>
int main(int argc,char *argv[])
{
char buf[125] = "";
int i = 0;
sprintf(buf,"rm -f /var/eric_test");
system(buf);
while(i < argc){
sprintf(buf,"echo %s >> /var/eric_test",argv[i]);
system(buf);
i++;
}
}
它就是将调用参数输出到/var/eric_test下面.
下面就可以开始我们的测试了.挂载目录下已经有一个子层cgroup.如下示:
.
|-- debug.cgroup_refcount
|-- debug.current_css_set
|-- debug.current_css_set_refcount
|-- debug.releasable
|-- debug.taskcount
|-- eric
| |-- debug.cgroup_refcount
| |-- debug.current_css_set
| |-- debug.current_css_set_refcount
| |-- debug.releasable
| |-- debug.taskcount
| |-- notify_on_release
| `-- tasks
|-- notify_on_release
|-- release_agent
`-- tasks
接下来设置realesse_agent_path和CGRP_NOTIFY_ON_RELEASE标志,指令如下:
[root@localhost cgroup]# echo /root/main > release_agent
[root@localhost cgroup]# echo 1 > eric/notify_on_release
下面往子层cgroup中添加一个进程.指令如下:
[root@localhost cgroup]# /root/test &
[1] 4350
[root@localhost cgroup]# echo 4350 > eric/tasks
[root@localhost cgroup]#
[1]+ Done /root/test
等/root/test运行完之后.就会进行notify_on_release的操作了.印证一下:
[root@localhost cgroup]# cat /var/eric_test
/root/main
/eric
一切都如我们上面分析的一样
9.2:取消进程与cgroup的关联
当cgroup中的最后一个进程取消关联的时候,也会有notify_on_release过程.见下面的代码片段:
int cgroup_attach_task(struct cgroup *cgrp, struct task_struct *tsk)
{
int retval = 0;
struct cgroup_subsys *ss;
struct cgroup *oldcgrp;
struct css_set *cg = tsk->cgroups;
......
......
set_bit(CGRP_RELEASABLE, &oldcgrp->flags);
synchronize_rcu();
put_css_set(cg);
}
这个函数我们在之前分析过,不过也把notify_on_release的过程去掉了.现在也把它加上.
代码中的cg是指向进程原本所引用的css_set
Oldcgrp是过程之前所在的cgroup
在代码中,会将oldcgrp标志设为CGRP_RELEASABLE.之后也会调用put_css_set().put_css_set()就是我们在上面分析的过程了.如果cgroup为空的话,就会产生notify_on_release的操作.
同样做个测试:
接着上面的测试环境.我们先来看下环境下的相关文件内容:
[root@localhost cgroup]# cat release_agent
/root/main
[root@localhost cgroup]# cat eric/tasks
[root@localhost cgroup]# cat eric/notify_on_release
1
[root@localhost cgroup]# pwd
/dev/cgroup
好了,测试开始了:
[root@localhost cgroup]# rm -rf /var/eric_test
[root@localhost cgroup]# echo 1701 > eric/tasks
[root@localhost cgroup]# echo 1701 >tasks
[root@localhost cgroup]# cat /var/eric_test
/root/main
/eric
在上面的测试过程中.为了避免影响测试效果.先将/var/eric_test文件删了.然后将进程1701关联到eric所表示的cgroup.然后再把1701再加最上层cgroup.这样就会造成eric下关联进程为空.相应的会发生notify_on_release过程.上面的测试也印证了这一说话.
9.3:移除cgroup
当移除cgroup下的最后一个子层cgroup时.也会发生notify_on_release.
看一下移除cgroup时的代码片段:
static int cgroup_rmdir(struct inode *unused_dir, struct dentry *dentry)
{
......
......
set_bit(CGRP_RELEASABLE, &parent->flags);
check_for_release(parent);
......
}
代码中,parent表示cgroup的上一层.在移除cgroup时,会设置上一层的cgroup标志的CGRP_RELEASABLE位.然后流程同样会转入到check_for_release().这样,如果上一层cgroup是空的话.就会生notify_on_release操作了.
测试如下:
还是用上层的测试环境.先来看一下初始环境:
[root@localhost cgroup]# pwd
/dev/cgroup
[root@localhost cgroup]# cat release_agent
/root/main
[root@localhost cgroup]# cat eric/notify_on_release
1
在eric下面再加一层cgroup.
[root@localhost cgroup]# mkdir eric/test
[root@localhost cgroup]# tree
.
|-- debug.cgroup_refcount
|-- debug.current_css_set
|-- debug.current_css_set_refcount
|-- debug.releasable
|-- debug.taskcount
|-- eric
| |-- debug.cgroup_refcount
| |-- debug.current_css_set
| |-- debug.current_css_set_refcount
| |-- debug.releasable
| |-- debug.taskcount
| |-- notify_on_release
| |-- tasks
| `-- test
| |-- debug.cgroup_refcount
| |-- debug.current_css_set
| |-- debug.current_css_set_refcount
| |-- debug.releasable
| |-- debug.taskcount
| |-- notify_on_release
| `-- tasks
|-- notify_on_release
|-- release_agent
`-- tasks
2 directories, 22 files
接着运行如下指令:
[root@localhost cgroup]# rm -rf /var/eric_test
[root@localhost cgroup]# rmdir eric/test/
[root@localhost cgroup]# cat /var/eric_test
/root/main
/eric
如上所示.把eric下的唯一一个cgroup移除的时候.就发生了notity_on_release过程.
十:cgroup的proc节点
10.1:/proce/cgroups
在前面分析cgroup初始化的时候.在cgroup_init()中有下面代码片段:
int __init cgroup_init(void)
{
......
......
proc_create("cgroups", 0, NULL, &proc_cgroupstats_operations)
......
......
}
也就是说.会在proc根目录下创建一个名为cgroups的文件.如下示:
[root@localhost cgroup]# ls /proc/cgroups
/proc/cgroups
接下来就来分析这个文件的操作.
该文件对应的操作集为
proc_cgroupstats_operations.定义如下:
static struct file_operations proc_cgroupstats_operations = {
.open = cgroupstats_open,
.read = seq_read,
.llseek = seq_lseek,
.release = single_release,
}
从上面看到,这个文件是只读的.
先来看open时的操作,对应接口为cgroupstats_open.代码如下:
static int cgroupstats_open(struct inode *inode, struct file *file)
{
return single_open(file, proc_cgroupstats_show, NULL);
}
Single_open()函数十分简单.它也是sequences file中提供的一个接口.有关sequences file部份我们在上面已经分析过了. 这里就不再详细分析了.它将seq_file的show操作指向了proc_cgroupstats_show.
我们在上面的proc_cgroupstats_operations结构中可看到,它提供的read操作为seq_read().它就是调用seq_file中的相关操作.在open的时候,已经将seq_file的show接口指向了proc_cgroupstats_show().代码如下:
static int proc_cgroupstats_show(struct seq_file *m, void *v)
{
int i;
seq_puts(m, "#subsys_name\thierarchy\tnum_cgroups\tenabled\n");
mutex_lock(&cgroup_mutex);
for (i = 0; i < CGROUP_SUBSYS_COUNT; i++) {
struct cgroup_subsys *ss = subsys[i];
seq_printf(m, "%s\t%lu\t%d\t%d\n",
ss->name, ss->root->subsys_bits,
ss->root->number_of_cgroups, !ss->disabled);
}
mutex_unlock(&cgroup_mutex);
return 0;
}
从代码中看到,它就是将系统中每subsys名称.所在hierarchy的位码. Hierarchy下面的cgroup数目和subsys的启用状态.
测试如下:
[root@localhost cgroup]# cat /proc/cgroups
#subsys_name hierarchy num_cgroups enabled
cpuset 0 1 1
debug 2 2 1
ns 0 1 1
cpuacct 0 1 1
memory 0 1 1
devices 0 1 1
freezer 0 1 1
从这里可以看到所有的subsys和hierarchy的情况.在上面显示的debug和其它的subsys不同.是因为用的是之前测试notify_on_release的环境.如下示:
[root@localhost cgroup]# tree ../cgroup/
../cgroup/
|-- debug.cgroup_refcount
|-- debug.current_css_set
|-- debug.current_css_set_refcount
|-- debug.releasable
|-- debug.taskcount
|-- eric
| |-- debug.cgroup_refcount
| |-- debug.current_css_set
| |-- debug.current_css_set_refcount
| |-- debug.releasable
| |-- debug.taskcount
| |-- notify_on_release
| `-- tasks
|-- notify_on_release
|-- release_agent
`-- tasks
1 directory, 15 files
10.2:proc下进程镜像中的cgroup
除了在proc顶层目录创建cgroup外.另外在每个进程镜像下都有一个cgroup的文件.如下示:
[root@localhost cgroup]# ls /proc/648/cgroup
/proc/648/cgroup
来看一下这个文件对应的操作,如下示:
static const struct pid_entry tid_base_stuff[] = {
......
......
#ifdef CONFIG_CGROUPS
REG("cgroup", S_IRUGO, cgroup),
#endif
......
}
#define REG(NAME, MODE, OTYPE) \
NOD(NAME, (S_IFREG|(MODE)), NULL, \
&proc_##OTYPE##_operations, {})
从上面可以看到.Cgroup对应的操作为&proc_cgroup_operations
定义如下:
struct file_operations proc_cgroup_operations = {
.open = cgroup_open,
.read = seq_read,
.llseek = seq_lseek,
.release = single_release,
};
Open对应的操作为cgroup_open.定义如下:
static int cgroup_open(struct inode *inode, struct file *file)
{
struct pid *pid = PROC_I(inode)->pid;
return single_open(file, proc_cgroup_show, pid);
}
又见到single_open()了.如上面的分析一样,read操作的时候会转入到proc_cgroup_show().代码如下:
static int proc_cgroup_show(struct seq_file *m, void *v)
{
struct pid *pid;
struct task_struct *tsk;
char *buf;
int retval;
struct cgroupfs_root *root;
retval = -ENOMEM;
buf = kmalloc(PAGE_SIZE, GFP_KERNEL);
if (!buf)
goto out;
retval = -ESRCH;
pid = m->private;
tsk = get_pid_task(pid, PIDTYPE_PID);
if (!tsk)
goto out_free;
retval = 0;
mutex_lock(&cgroup_mutex);
/*遍历所有的cgroupfs_root*/
for_each_root(root) {
struct cgroup_subsys *ss;
struct cgroup *cgrp;
int subsys_id;
int count = 0;
/* Skip this hierarchy if it has no active subsystems */
/*如果hierarchy中没有subsys.就继续下一个rootnode就是这样的情况*/
if (!root->actual_subsys_bits)
continue;
/*打印hierarchy中的subsys位图*/
seq_printf(m, "%lu:", root->subsys_bits);
/*打印hierarchy中的subsys名称*/
for_each_subsys(root, ss)
seq_printf(m, "%s%s", count++ ? "," : "", ss->name);
seq_putc(m, ':');
/*进程所在cgroup的path*/
get_first_subsys(&root->top_cgroup, NULL, &subsys_id);
cgrp = task_cgroup(tsk, subsys_id);
retval = cgroup_path(cgrp, buf, PAGE_SIZE);
if (retval < 0)
goto out_unlock;
seq_puts(m, buf);
seq_putc(m, '\n');
}
out_unlock:
mutex_unlock(&cgroup_mutex);
put_task_struct(tsk);
out_free:
kfree(buf);
out:
return retval;
}
它的核心操作在这个for循环中,它的操作在注释中已经详细的说明了.在这里不做详细分析.
我将虚拟机重启了 *^_^*,所以现在的环境不是我们之前的测试环境了
测试一下:
[root@localhost ~]# cat /proc/646/cgroup
[root@localhost ~]#
说明当前系统中还没有hierarchy.
接下来挂载上一个:
[root@localhost ~]# mkdir /dev/cgroup
[root@localhost ~]# mount -t cgroup cgroup -o debug /dev/cgroup/
[root@localhost ~]# cat /proc/6
6/ 609/ 646/
[root@localhost ~]# cat /proc/646/cgroup
2:debug:/
[root@localhost ~]#
从上面可以看到.系统已经有一个hierarchy.且绑定的是debug subsys.当前进程是位于它的顶层.
继续测试:
[root@localhost ~]# mkdir /dev/cgroup/eric
[root@localhost ~]# echo 646 > /dev/cgroup/eric/tasks
[root@localhost ~]# cat /proc/646/cgroup
2:debug:/eric
[root@localhost ~]#
可以看到,当前进程是位于eric这个cgroup中.
十一:小结
在这一节里,用大篇幅详细的描述了整个cgroup的框架.cgroup框架并不复杂,只是其中的数据结构和大量的全局变量弄的头昏眼花.因此理顺这些数据结构和变量是阅读cgroup代码的关键.另外在cgroup中对于RCU和rw_mutex的使用也有值得推敲的地方.不过由于篇幅关系,就不再分析这一部份.在接下来专题里.以cgroup框架为基础来分析几个重要的subsys.