/*
* The bitmask of subsystems intended to be attached to this
* hierarchy
*/
//hierarchy相关联的subsys 位图
unsigned long subsys_bits;
/* The bitmask of subsystems currently attached to this hierarchy */
//当前hierarchy 中的subsys位图
unsigned long actual_subsys_bits;
/* A list running through the attached subsystems */
//hierarchy中的subsys链表
struct list_head subsys_list;
/* The root cgroup for this hierarchy */
//hierarchy中的顶层cgroup
struct cgroup top_cgroup;
/* Tracks how many cgroups are currently defined in hierarchy.*/
//hierarchy中cgroup的数目
int number_of_cgroups;
/* A list running through the mounted hierarchies */
//用来链入全局链表roots
struct list_head root_list;
/* Hierarchy-specific flags */
//hierarchy的标志
unsigned long flags;
/* The path to use for release notifications. */
char release_agent_path[PATH_MAX];
};
注意cgroupfs_root中有个struct cgroup结构的成员:top_cgroup.即在每个挂载点下面都会有一个总的cgroup.而通过mkdir创建的cgroup是它的子结点.
其中,release_agent_path[ ]的成员含义.我们在后面再来详细分析.
Struct cgroup的定义如下:
struct cgroup {
//cgroup的标志
unsigned long flags; /* "unsigned long" so bitops work */
/* count users of this cgroup. >0 means busy, but doesn't
* necessarily indicate the number of tasks in the
* cgroup */
//引用计数
atomic_t count;
/*
* We link our 'sibling' struct into our parent's 'children'.
* Our children link their 'sibling' into our 'children'.
*/
//用来链入父结点的children链表
struct list_head sibling; /* my parent's children */
//子结点链表
struct list_head children; /* my children */
//cgroup的父结点
struct cgroup *parent; /* my parent */
//cgroup所处的目录
struct dentry *dentry; /* cgroup fs entry */
/* Create the top cgroup state for this subsystem */
ss->root = &rootnode;
css = ss->create(ss, dummytop);
/* We don't handle early failures gracefully */
BUG_ON(IS_ERR(css));
init_cgroup_css(css, ss, dummytop);
/* Update the init_css_set to contain a subsys
* pointer to this state - since the subsystem is
* newly registered, all tasks and hence the
* init_css_set is in the subsystem's top cgroup. */
init_css_set.subsys[ss->subsys_id] = dummytop->subsys[ss->subsys_id];
/* At system boot, before all subsystems have been
* registered, no tasks have been forked, so we don't
* need to invoke fork callbacks here. */
BUG_ON(!list_empty(&init_task.tasks));
4.2: cgroup_init()
cgroup_init()是cgroup的第二阶段的初始化.代码如下:
int __init cgroup_init(void)
{
int err;
int i;
struct hlist_head *hhead;
err = bdi_init(&cgroup_backing_dev_info);
if (err)
return err;
//将剩下的(不需要在系统启动时初始化的subsys)的subsys进行初始化
for (i = 0; i
struct cgroup_subsys *ss = subsys;
if (!ss->early_init)
cgroup_init_subsys(ss);
}
/* Add init_css_set to the hash table */
//将init_css_set添加到css_set_table[ ]
hhead = css_set_hash(init_css_set.subsys);
hlist_add_head(&init_css_set.hlist, hhead);
//注册cgroup文件系统
err = register_filesystem(&cgroup_fs_type);
if (err
goto out;
//在proc文件系统的根目录下创建一个名为cgroups的文件
proc_create("cgroups", 0, NULL, &proc_cgroupstats_operations);
out:
if (err)
bdi_destroy(&cgroup_backing_dev_info);
5.2:子进程结束时的操作
子进程结束的时候,有:
Do_exit() à cgroup_exit().
Cgroup_exit()代码如下:
void cgroup_exit(struct task_struct *tsk, int run_callbacks)
{
int i;
struct css_set *cg;
if (run_callbacks && need_forkexit_callback) {
for (i = 0; i
struct cgroup_subsys *ss = subsys;
if (ss->exit)
ss->exit(ss, tsk);
}
}
/*
* Unlink from the css_set task list if necessary.
* Optimistically check cg_list before taking
* css_set_lock
*/
if (!list_empty(&tsk->cg_list)) {
write_lock(&css_set_lock);
if (!list_empty(&tsk->cg_list))
list_del(&tsk->cg_list);
write_unlock(&css_set_lock);
}
/* Reassign the task to the init_css_set. */
task_lock(tsk);
cg = tsk->cgroups;
tsk->cgroups = &init_css_set;
task_unlock(tsk);
if (cg)
put_css_set_taskexit(cg);
}
这个函数的代码逻辑比较清晰.首先,如果以1为调用参数(run_callbacks为1),且有定义了exit操作的subsys.就调用这个subsys的exit操作.
然后断开task->cg_list链表.将其从所指向的css_set->task链上断开.
最后,断开当前的cgroup指向.将其指向init_css_set.也就是将其回复到初始状态.最后,减少旧指向css_set的引用计数.
跟踪到__put_css_set()中:
static void __put_css_set(struct css_set *cg, int taskexit)
{
int i;
/*
* Ensure that the refcount doesn't hit zero while any readers
* can see it. Similar to atomic_dec_and_lock(), but for an
* rwlock
*/
if (atomic_add_unless(&cg->refcount, -1, 1))
return;
write_lock(&css_set_lock);
if (!atomic_dec_and_test(&cg->refcount)) {
write_unlock(&css_set_lock);
return;
}
unlink_css_set(cg);
write_unlock(&css_set_lock);
rcu_read_lock();
for (i = 0; i
struct cgroup *cgrp = cg->subsys->cgroup;
if (atomic_dec_and_test(&cgrp->count) &&
notify_on_release(cgrp)) {
if (taskexit)
set_bit(CGRP_RELEASABLE, &cgrp->flags);
check_for_release(cgrp);
}
}
rcu_read_unlock();
kfree(cg);
}
atomic_add_unless(v,a,u)表示如果v的值不为u就加a.返回1.如果v的值等于u就返回0
因此,这个函数首先减小css_set的引用计数.如果css_set的引用计数为1.就会将css_set释放掉了. 要释放css_set.首先要释放css_set上挂载的链表.再释放css_set结构本身所占空间.
释放css_set上的挂载链表是在unlink_css_set()中完成的.代码如下:
static void unlink_css_set(struct css_set *cg)
{
struct cg_cgroup_link *link;
struct cg_cgroup_link *saved_link;
/* First find the desired set of subsystems */
//解析挂载参数
ret = parse_cgroupfs_options(data, &opts);
if (ret) {
if (opts.release_agent)
kfree(opts.release_agent);
return ret;
}
在这一部份,解析挂载的参数,并将解析的结果存放到opts.opts-> subsys_bits表示指定关联的subsys位图,opts->flags:挂载的标志: opts->release_agent表示指定的release_agent路径.
//分配并初始化cgroufs_root
root = kzalloc(sizeof(*root), GFP_KERNEL);
if (!root) {
if (opts.release_agent)
kfree(opts.release_agent);
return -ENOMEM;
}
/*
* We're accessing css_set_count without locking
* css_set_lock here, but that's OK - it can only be
* increased by someone holding cgroup_lock, and
* that's us. The worst that can happen is that we
* have some link structures left over
*/
/*分配css_set_count个cg_cgroup_link并将它们链入到tmp_cg_links*/
ret = allocate_cg_links(css_set_count, &tmp_cg_links);
if (ret) {
mutex_unlock(&cgroup_mutex);
mutex_unlock(&inode->i_mutex);
goto drop_new_super;
}
/*bind subsys 到hierarchy*/
ret = rebind_subsystems(root, root->subsys_bits);
if (ret == -EBUSY) {
mutex_unlock(&cgroup_mutex);
mutex_unlock(&inode->i_mutex);
goto drop_new_super;
}
/* EBUSY should be the only error here */
BUG_ON(ret);
/*将root添加到roots链入.增加root_count计数*/
list_add(&root->root_list, &roots);
root_count++;
/* Link the top cgroup in this hierarchy into all
* the css_set objects */
/*将所有的css_set都和root->top_cgroup关联起来*/
write_lock(&css_set_lock);
for (i = 0; i
struct hlist_head *hhead = &css_set_table;
struct hlist_node *node;
struct css_set *cg;
/*各参数是以","分隔的*/
while ((token = strsep(&o, ",")) != NULL) {
if (!*token)
return -EINVAL;
/*如果为all.表示关联所有的subsys*/
if (!strcmp(token, "all")) {
/* Add all non-disabled subsystems */
int i;
opts->subsys_bits = 0;
for (i = 0; i
struct cgroup_subsys *ss = subsys;
if (!ss->disabled)
opts->subsys_bits |= 1ul
}
}
/*如果指定参数noprefix.设定ROOT_NOPREFIX标志*/
/*在指定noprefix的情况下.subsys创建的文件不会带subsys名称的前缀*/
else if (!strcmp(token, "noprefix")) {
set_bit(ROOT_NOPREFIX, &opts->flags);
}
/*如果指定了release_agent.分opt->release_agent分配内存,并将参数copy到里面*/
else if (!strncmp(token, "release_agent=", 14)) {
/* Specifying two release agents is forbidden */
if (opts->release_agent)
return -EINVAL;
opts->release_agent = kzalloc(PATH_MAX, GFP_KERNEL);
if (!opts->release_agent)
return -ENOMEM;
strncpy(opts->release_agent, token + 14, PATH_MAX - 1);
opts->release_agent[PATH_MAX - 1] = 0;
}
/*其它情况下,将所带参数做为一个susys名处理.到sussys[]找到
*对应的subsys.然后将opts->subsys_bits中的位置1
*/
else {
struct cgroup_subsys *ss;
int i;
for (i = 0; i
ss = subsys;
if (!strcmp(token, ss->name)) {
if (!ss->disabled)
set_bit(i, &opts->subsys_bits);
break;
}
}
if (i == CGROUP_SUBSYS_COUNT)
return -ENOENT;
}
}
/* We can't have an empty hierarchy */
/*如果没有关联到subsys.错误*/
if (!opts->subsys_bits)
return -EINVAL;
return 0;
}
对照代码中添加的注释应该很容易看懂.这里就不再做详细分析了.
6.2: rebind_subsystems()函数分析
rebind_subsystems()用来将cgroupfs_root和subsys绑定.代码如下:
static int rebind_subsystems(struct cgroupfs_root *root,
unsigned long final_bits)
{
unsigned long added_bits, removed_bits;
struct cgroup *cgrp = &root->top_cgroup;
int i;
/*root->actual_subsys_bits表示当进root中所关键的subsys位图*/
/*如果在root->actual_subsys_bits中.但没有在final_bits中.表示这是
*一次remonut的操作.需要将旧的subsys移除.如果在final_bits中
*存在,但没有在root->actual_subsys_bits中,表示是需要添加的.
*/
removed_bits = root->actual_subsys_bits & ~final_bits;
added_bits = final_bits & ~root->actual_subsys_bits;
/* Check that any added subsystems are currently free */
/*如果要关联的subsys已经在其它的hierarchy中了.失败.
*如果ss->root != &rootnode表示ss已经链入了其它的cgroupfs_root
*/
for (i = 0; i
unsigned long bit = 1UL
struct cgroup_subsys *ss = subsys;
if (!(bit & added_bits))
continue;
if (ss->root != &rootnode) {
/* Subsystem isn't free */
return -EBUSY;
}
}
/* Currently we don't handle adding/removing subsystems when
* any child cgroups exist. This is theoretically supportable
* but involves complex error handling, so it's being left until
* later */
/*如果root->top_cgroup->children不为空.表示该hierarchy还要其它的cgroup
*是不能被remount的.(新挂载的root->top_cgroup在初始化的时候将children置空了)
*/
if (!list_empty(&cgrp->children))
return -EBUSY;
/* Process each subsystem */
for (i = 0; i
struct cgroup_subsys *ss = subsys;
unsigned long bit = 1UL
/*添加subsys的情况*/
if (bit & added_bits) {
/* We're binding this subsystem to this hierarchy */
/* 添加情况下.将cgrp->subsys指向dummytop->subsys
* 并更新dummytop->subsys->root.将其指向要添加的root
* 最后调用subsys->bind()操作
*/
BUG_ON(cgrp->subsys);
BUG_ON(!dummytop->subsys);
BUG_ON(dummytop->subsys->cgroup != dummytop);
cgrp->subsys = dummytop->subsys;
cgrp->subsys->cgroup = cgrp;
list_add(&ss->sibling, &root->subsys_list);
rcu_assign_pointer(ss->root, root);
if (ss->bind)
ss->bind(ss, cgrp);
if (!dentry)
return -ENOENT;
if (dentry->d_inode)
return -EEXIST;
/*分配一个inode*/
inode = cgroup_new_inode(mode, sb);
if (!inode)
return -ENOMEM;
/*如果新建的是目录*/
if (S_ISDIR(mode)) {
inode->i_op = &cgroup_dir_inode_operations;
inode->i_fop = &simple_dir_operations;
/* start off with i_nlink == 2 (for "." entry) */
inc_nlink(inode);
/* start with the directory inode held, so that we can
* populate it without racing with another mkdir */
mutex_lock_nested(&inode->i_mutex, I_MUTEX_CHILD);
}
/*新建一般文件*/
else if (S_ISREG(mode)) {
inode->i_size = 0;
inode->i_fop = &cgroup_file_operations;
}
dentry->d_op = &cgroup_dops;
/*将dentry和inode关联起来*/
d_instantiate(dentry, inode);
dget(dentry); /* Extra count - pin the dentry in core */
return 0;
}
从这个函数我们可以看到.如果是目录的话,对应的操作集为simple_dir_operations和cgroup_dir_inode_operations.它与cgroup_get_rootdir()中对根目录对应的inode所设置的操作集是一样的.如果是一般文件,它的操作集为cgroup_file_operations.
在这里,先将cgroup中的文件操作放到一边,我们在之后再来详细分析这个过程.
现在.我们已经将cgroup文件系统的挂载分析完成.接下来看它下面子层cgroup的创建.
/* Grab a reference on the superblock so the hierarchy doesn't
* get deleted on unmount if there are child cgroups. This
* can be done outside cgroup_mutex, since the sb can't
* disappear while someone has an open control file on the
* fs */
atomic_inc(&sb->s_active);
/* The cgroup directory was pre-locked for us */
BUG_ON(!mutex_is_locked(&cgrp->dentry->d_inode->i_mutex));
/*在cgrp下创建几个交互文件*/
err = cgroup_populate_dir(cgrp);
/* If err
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);
在代码中,是通过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();
/* 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返回.
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
int mid = (index + end) / 2;
if (cgrp->tasks_pids[mid] == pid) {
index = mid;
break;
} else if (cgrp->tasks_pids[mid]
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.
/*
* 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;
}
}
/* Nothing to do if the task is already in that cgroup */
/*找到这个进程之前所属的cgroup*/
oldcgrp = task_cgroup(tsk, subsys_id);
/*如果已经在这个cgrp里面了.*/
if (cgrp == oldcgrp)
return 0;
/*
* 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;
/* 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);
/* 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);
/* Built the set of subsystem state objects that we want to
* see in the new css_set */
for (i = 0; i
if (root->subsys_bits & (1UL
/* Subsystem is in this hierarchy. So we want
* the subsystem state from the new
* cgroup */
template = cgrp->subsys;
} else {
/* Subsystem is not in this hierarchy, so we
* don't want to change the subsystem state */
template = oldcg->subsys;
}
}
/* 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)
kfree(res);
return NULL;
}
/* 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
struct cgroup *cgrp = res->subsys->cgroup;
struct cgroup_subsys *ss = subsys;
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);
}
}
/* 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
#include
main()
{
int i = 30;
while(i){
i--;
sleep(1);
}
}
这个进程睡眠30s之后退出.编译成test
另外一个程序代码如下:
Main.c
#include
#include
int main(int argc,char *argv[])
{
char buf[125] = "";
int i = 0;