`
xiaohui_p
  • 浏览: 16971 次
  • 性别: Icon_minigender_1
  • 来自: 杭州
社区版块
存档分类
最新评论

控制组实现初步分析

阅读更多
控制组实现初步分析
1. 控制组简介
当前的主流linux内核提供了控制组(control group)的功能,利用该功能允许管理员对系统中运行的任务进行分组,然后为每个组分配资源,比如CPU时间,系统内存,网络带宽或者资源的组合.利用控制组系统管理员可更具体的控制系统资源的分配、优先顺序、拒绝、管理和监控,可以更好的根据用户和资源的情况合理的分配资源,提高系统总体效率.内核为了便于管理资源使用子系统表示单一资源,比如CPU时间,系统内存,网络带宽等,然后每个控制组都会关联一个或者多个子系统,进而实现资源管理和配置。当前的内核支持如下可用的子系统:
blkio:设定系统块设备的I/O限制.
cpu:对系统中CPU资源进行管理.
cpuacct:该子系统自动生成控制组使用CPU资源的报告.
cpuset:该系统为控制组分配独立CPU和内存节点.
devices:提供设备访问白名单机制.
freezer:该系统提供挂起或者恢复控制组的机制.
memory:提供控制组对内存使用的限制以及自动生成这些任务内存资源使用报告.
net_cls:允许内核流量控制器识别属于特定控制组的网络包.
ns:名称空间子系统.
此外,在2.6.35及其上内核中允许子系统编译成独立的模块,动态的安装和卸载,当前系统中支持子系统的最大个数为32(unsigned long类型占用的比特数).

2. 基本数据结构与组织
2.1 控制组文件系统
linux内核将控制组组织成若干树形的层次结构,每一个层次结构以控制组文件系统的形式进行管理和操作。用户通过操作控制组文件系统中的控制文件来对控制组进行管理.控制组根文件系统结构定义如下:
struct cgroupfs_root{
	struct super_block *sb;//根文件系统超级块指针
	unsigned long subsys_bits;//可能连接到层次结构的子系统位图
	int hierarchy_id;//该层级结构id
	unsigned long actual_subsys_bits;//实际连接到层次结构的子系统位图
	struct list_head subsys_list;//连接到该层次结构的子系统链表头
	struct cgroup top_cgroup;//层次结构的根控制组
	int number_of_cgroups;//层次结构中控制组的总数
	struct list_head root_list;//该层次结构在层次机构链表中的节点
	unsigned long flags;//层次结构标记
	char release_agent_path[PATH_MAX];//释放通知器的路径.
	char name[MAX_CGORUP_ROOT_NAMELEN];//层次结构名称
};

在系统中可同时存在若干个独立的层次结构(hierarchy),每个层次结构至少包含一个控制组(根控制组,top_cgroup字段),控制组之间按照父-子关系组织成树形结构,每个子系统只能连接到一个层次结构,一个层次结构可以链接若干子系统,显然可通过子系统来唯一的确定其关联到的层次结构.系统定义了默认的层次结构rootnode,该层次结构用于关联没有连接到其他层次结构的子系统,并且rootnode只拥有一个控制组rootnode.top_cgroup,系统启动后,系统中所有的进程均属于该控制组.

下面我们看看控制组数据结构:
struct cgroup{
	unsigned long flags;//控制组标志
	atomic_t count;//控制组的用户数,该值>0表示控制组空闲
	struct list_head sibling;//控制组在兄弟控制组链表中的节点
	struct list_head children;//控制组孩子链表头
	struct cgroup *parent;//父控制组指针
	struct dentry __rcu *dentry;//控制组在控制组文件系统中的目录项指针
	//指向注册到控制组的子系统的指针数组
	struct cgroup_subsys_state *subsys[CGROUP_SUBSYS_COUNT];
	struct cgroupfs_root *root;//指向控制组文件系统根指针
	struct cgroup *top_cgroup;//指向根控制组指针
	struct list_head css_sets;//连接到该控制组中的cg_cgroup_link链表头指针
	struct list_head release_list;//被release agent回收的链表中节点
	struct list_head pidlists;//pidlist链表,用于名称空间
	struct mutex pidlist_mutex;//pidlist互斥锁
	struct list_head event_list;//用户空间希望接收到的事件列表
	spinlock_t event_list_lock;//事件列表自旋锁
};

从上面的结构可知道控制组通过sibling,children,parent组织称树形层次结构.为了快速定位控制组层次结构中控制组文件系统根和根控制组,cgroup结构中增加了root和top_cgroup项,以提高系统的性能.此外,需额外注意的是dentry数据项,它指定了控制组在控制组文件系统中目录项,在该目录下内核会创建控制文件来允许用户空间对控制组的访问与控制。关于其他的数据项现不做进一步的介绍,在后面遇到时再详细介绍.

2.2 任务与控制组的关联
内核将任务分配到不同的控制组中以对其进行管理和控制.也许你会发现上面的cgroup结构中并没有数据项显示的指定属于该控制组的任务,唯一有些相关的就是pidlist,虽然可通过进程pid来确定属于该控制组的所有任务,但是通过pid确定进程的任务结构却不是很容易的事情.为了将任务和控制组关联起来,task_struct结构增加了以下两个数据项:
struct css_set *cgroup;
struct list_head cg_list;
从上面两个数据项中依旧没有发现task_struct和cgroup的任何直接关联,而是遇到了一个新的数据结构css_set,从直觉上说这两项肯定会和cgroup有着什么的瓜葛.我们不应该作主观臆测,通过数据结构的定义才能发现其中的原委。该数据结构的定义并不复杂,其定义如下:
struct css_set{
	atomoc_t refcount;//应用计数
	struct list_node hlist;//控制组hash链表入口
	struct list_head tasks;//和该结构关联的所有任务双向链表头
	struct list_head cg_links;//连接到该结构的所有cg_cgroup_link对象的双向链表头
	//子系统状态结构指针数组
	struct cgroup_subsys_state *subsys[CGROUP_SUBSYS_COUNT];
	struct rcu_head rcu_head;//用于删除时保护
};

即便是知道了css_set的定义,我们仍没有看到task_struct和cgroup的任何瓜葛,反而又遇到了一个还没有搞清楚的结构struct cgroup_subsys_state,以及若干不知道具体作用的双向链表.也许你会有些的抱怨,但是我们还是要顺藤摸瓜,内核这样的设计必然有其的道理,说不准在后面的研究中还会发现更多有趣的事情。关于subsys子系统的数组现在不是主要关注的东西,它会在下一节详细的讲述。

前面提到一个运行中的任务可同时属于不同层次结构中的控制组,也许cg_links就是关联控制组的至关重要的纽带. 从上面的注释中可以知道该数据项表示了cg_cgroup_link结构的双向链表头,揭开该结构的面纱也许可以使得事情变得清晰.和css_set结构一样cg_cgroup_link的设计也是非常的简单,但它却可以扫清眼前的阴霾,找到任务和控制组结构的关联.
struct cg_cgroup_link{
	struct list_head cgrp_link_list;
	struct cgroup *cgrp;
	struct list_head cg_link_list;
	struct css_set *cg;
};

在cg_cgroup_link结构我们终于发现了cgroup的影子,cgrp指针指向了一个cgroup结构,它应该就是和任务关联的控制组.现在似乎已经知道了任务和控制组关联的方法,但是还有一个没有解决的疑点:每个任务可能同时属于多个层次结构不同控制组,显然不可能让cgrp指针同时指向多个控制组结构,也许你会说可以用cgrp指向和任务关联的所有控制组的数组,这当然是一个解决方案,但是这样存在一个问题就是我们不可能对每个任务都这样处理(在最坏情况下我们要为每个任务维护控制组数据,想想这是为什么呢?),因为这样不仅浪费空间,而且通过控制组反过来找关联的任务就不是一件简单的事情了,还有就是不能方便的将任务添加其他控制组中了,以及数据的一致性问题等.

那么cg_cgroup_link到底是如何和cgroup关联起来的呢?为了解决这个疑问,需要回头看看cgroup结构,在该结构中存在一个css_sets数据项,它表示了和cgroup关联的cg_cgrop_link结构的双向链表头节点,结合cg_cgroup_link中的cgrp_link_list数据项,就可将所有和cgroup关联的cg_cgroup_link结构插入到该双向链表中,到此时,你应该知道cgrp的作用了,它就是指向了关联该结构实例的控制组,这样当知道cg_cgrop_link结构时就可以快速定位到关联的cgroup结构了.

如果你明白了上面的表述,那么你可以推测出cg_link_list和cg的作用了,cg指向了和cg_cgroup_link结构关联的css_set结构,而cg_link_list则是添加到和css_set关联的cg_cgroup_link结构的双向链表中的节点,该双向链表头为css_set结构中的cg_links数据项.

到现在终于明白了内核怎样将任务和控制组关联起来了:
利用task_struct结构中的cgroup数据项找到该任务关联的css_set结构,然后通过css_set结构找到和css_set关联的所有cg_link_list结构,最后根据cg_link_list结构中的cgrp指针找到任务关联的控制组,这个查找过程看起来没有直接关联起来的方便,但是这样的设计可减少task_struct和cgroup的耦合度,并且还可减少很多的数据冗余.不知道你是否还记得数据库中的范式理论,cg_link_list的实际就是为了解除数据冗余而设计的(好长时间没看数据库了,到底是第几范式,记不清楚了,如果你知道的话,请你告诉我......^_^).

反过来通过cgroup查找相应的task_struct也不再是困难的事情,根据cgroup中的css_sets数据项,可以找到所有关联到控制组的cg_link_list结构,通过cg_link_list结构中的cg可找到关联的css_set结构,最后根据css_set结构的tasks数据项找到关联到控制组的所有任务(交织在一起的双向链表,你可以理清楚的..).

需要提一下的是css_set结构中的tasks为所有关联到css_set结构的任务链表头,想必task_struct中cg_list的作用你应该也知道了,它就是连接到css_set中tasks链表的节点.


2.3 控制组与子系统的关联
将任务关联到控制组后,就可以通过连接到控制组的子系统对任务进行控制了.从数据结构上说找到和控制组关联的子系统是一件很容易的事情,现在回头看看cgroup结构中的subsys结构,它保存了cgroup_subsys_state结构的指针数组,根据cgroup_subsys_state的名称可以推测它表示了控制组子系统的状态信息,也许通过它我们可以知道很多关于子系统的信息,在源码面前所有的秘密都不再是秘密,来看看cgroup_subsys_state结构的定义吧!
struct cgrouop_subsys_state{
	struct cgroup *cgroup;//子系统关联到的控制组指针
	atomic_t refcnt;//引用计数>0表示忙碌
	unsigned long flags;//标记信息
	struct css_id __rcu *id;//css ID
};

这个数据结构也许会让你失望,通过它并没有看到什么子系统的有效信息.该结构似乎真有些名不副实,没有提供什么有用的信息,除了该结构中包含了指向控制组的指针和一些无关痛痒的数据项.

好吧,打起精神,不在这里故弄玄虚了,还是看看cgroup_subsys结构的真正定义吧!
struct cgroup_subsys{
	struct cgroup_subsys_state *(*create)(struct cgroup_subsys *ss,struct cgroup *cgrp);
	int (*pre_destroy)(struct cgroup_subsys *ss,struct cgroup *cgrp);
	void (*destroy)(struct cgroup_subsys *ss,struct cgroup *cgrp);
	int (*can_attach)(struct cgroup_subsys *ss,struct cgroup *cgrp);
	int (*can_attach_task)(struct cgroup_subsys *ss,struct cgroup *cgrp);
	void (*cancel_attach)(struct cgroup_subsys *ss,struct cgroup *cgrp);
	void (*pre_attach)(struct cgroup *cgrp);
	void (*attach_task)(struct cgroup *cgrp,struct task_struct *tsk);
	void (*attach)(struct cgroup_subsys *ss,struct cgroup *cgrp,struct cgroup *old_group,struct task_struct *tsk);
	void (*fork)(struct cgroup_subsys *ss,struct task_struct *task);
	void (*exit)(struct cgroup_subsys *ss,struct cgroup *cgrp,struct cgroup *old_cgrp,struct task_struct *task);
	int (*populate)(struct cgroup_subsys *ss,struct cgroup *cgrp);
	void (*bind)(struct cgroup_subsys *ss,struct cgroup *root);
	int subsys_id;
	int active;
	int disabled;
	int early_init;
	bool use_id;
#define MAX_CGROUP_TYPE_NAMELEN 32
	const char *name;
	struct mutex hierarchy_mutex;
	struct lock_class_key subsys_key;
	struct cgroupfs_root *root;
	struct list_head sibling;
	struct idr idr;
	spinlock_t id_lock;
	struct module *module;
};

密密麻麻的一个结构体,如果你是第一次看到该结构的话,我想你会和我一样头都大了(^_^!),这是正常的事情.开始的时候没有必要搞清楚所有的事情,只需要知道当前所需的东西就可以了.后面遇到的时候还可以回头仔细的研究,至少这样不会打击自信心,而且回头看时会感觉比开始的时候容易了很多.在当前需要关注的只有create,destroy,populate和subsys_id.从名字上我们可以知道create和destroy一定是创造和销毁了什么东西,从create函数指针的返回值可以知道它返回了一个struct cgroup_subsys_state结构的指针,而对于cgroup_subsys_state结构已经清楚了解,通过它可找到关联控制组.到这里你也许已经看出了cgroup_subsys_state结构的作用,子系统可以调用create指向的函数,该函数会创建一个cgroup_subsys_state对象,该对象将控制组和子系统关联起来.由于该对象是由子系统创建的,显然子系统可对其进行很好的控制.现在可确信的说cgroup_subsys_state扮演了将每个控制组和子系统关联起来的纽带,为什么说是每个控制组呢?在后面介绍子系统实现实例的时候进行介绍.populate函数指针是做什么的呢?一般情况下我们使用该函数来创建控制文件,你也许会问什么是控制文件呢?好问题,我会在下面进行一些的分析解答.subsys_id很容易理解,该项表示了子系统的id,借助它就可以很方便的管理子系统了.

2.4 任务和子系统的关联
为了方便的将任务和子系统关联起来,内核在css_set结构中定义了subsys指针数组来指向css_set关联的子系统,因此task_struct可以借助于css_set结构来快速的定位关联的子系统,内核之所有没有直接把subsys指针数组放在task_struct结构中是为了节省空间和便于管理,因为如果每个task_struct均保持subsys的备份,显然在控制组和子系统链接情况发生变化的时候,可能需要修改所有的task_struct结构,并且很容易发生多个副本不一致的情况.

2.5 控制文件
内核采用控制文件和用户空间交换控制信息,而不是提供操作API.用户通过读写控制文件对控制组信息进行管理.从用户的观点来看,控制文件和其他的文件没有任何的区别,可采用cat或者echo命令对其进行读写.在结构上控制文件的定义不算复杂,主要定义了用户对控制文件进行读写操作的接口以及文件名等信息.
struct cftype{
	char name[MAX_CFTYPE_NAME];//控制文件名,需要主要的是导出的控制文件明以子系统名和点号开头
	int private;//私有数据
	mode_t mode;//文件权限
	size_t max_write_len;//定义允许写入的最大长度,默认为64
	int (*open)(struct inode *inode,struct file *file);//打开文件处理例程
	//读操作
	ssize_t (*read)(struct cgroup *cgrp,struct cftype *cft,struct file *file,char __user *buf,size_t nbytes, loff_t *ppos);
	//读取64位无符号数
	u64 (*read_u64)(struct cgroup *cgrp,struct cftype *cft);
	//读取64位有符号数
	s64 (*read_s64)(struct cgroup *cgrp,struct cftype *cft);
	//定义一个key/value对
	int (*read_map)(struct *cont,struct cftype *cft,struct cgroup_map_cb *cb);
	//从序列文件中读取一行
	int (*read_seq_string)(struct cgroup *cont,struct cftype *cft,struct seq_file *m);
	//写文件操作
	ssize_t (*write)(struct cgroup *cgrp,struct cftype *cft,struct file *file,const char __user *buf,size_t nbytes,loff_t *ppos);
	//写64位无符号整数
	int (*write_u64)(struct cgroup *cgrp,struct cftype *cft, u64 val);
	//写64位有符号整数
	int (*write_s64)(struct cgroup *cgrp,struct cftype *cft, s64 val);
	//写入一个字符串
	int (*write_string)(struct cgroup *cgrp,struct cftype *cft,const char *buffer);
	int (*trigger)(struct cgroup *cgrp, unsigned int event);//暂时忽略
	int (*release)(struct inode *indoe, struct file *file);//暂时忽略
	int (*register_event)(struct cgroup *cgrp,struct cftype *cft,struct eventfd_ctx *eventfd,const char * args);//暂时忽略
	void (*unregister_event)(struct cgroup *cgrp,struct cftype *cft,struct eventfd_ctx *eventfd);//暂时忽略	
};

根据上面的结构可知道该结构主要定义了控制文件名、访问权限以及读写操作等接口,现只对控制文件名以及控制文件的读写接口进行简要的介绍,其他数据项在遇到的时候再详细介绍(我不想一开始就把事情弄的很复杂).控制组文件系统导出的控制文件名采用:子系统名称+.+name的方式导出,比如子系统sample定义了名称为hello的控制文件,那么该控制文件在控制组文件系统中的文件名称为:"sample.hello".有一点需要注意的是cftype定义的读写操作接口均有优先级,系统会根据优先级调用相应的读写函数,因此在实现子系统控制文件读写操作时只需实现需要的读写方式即可.
读接口的优先级为:read>read_u64>read_s64
写接口的优先级为:write>write_u64>write_s64>write_string.
比如在某个子系统实现中你同时实现了read和read_u64方法,那么当你在读取控制文件时,系统只会调用read而忽略read_u64的实现,读操作亦然.一般在子系统接口populate中使用来创建控制文件,在后面分析实现实例时进行介绍.


3. 子系统实现实例
为使大家对子系统有个直观的认识,下面对内核中devices subsys的实现进行简单剖析,然后根据剖析的结果,编写一个简单的没有任何实际意义的子系统模块来加深大家对子系统的认识.看了前面的描述大家应该对控制组有了初步的认识,虽然内核对控制组文件系统和子系统的管理有些复杂(至少要比我所描述的要复杂很多),但是编写子系统模块的确是一件比较容易的事情.当然如果想要设计出有实际使用意义的子系统模仍旧是一件很有挑战性的工作,这其中主要的难点在于怎样合理的设计以及在内核中增加hook来实现设计的功能.也许你现在已经跃跃欲试设计自己的子系统,但是在此之前还是耐心的把下面的文字看完.devices subsys通过对内核进行很小的修改实现了设备白名单机制(块设备和字符设备),如果你希望你的子系统可以加入到最新的内核,那你就朝着devices的设计努力吧.请注意在这里我并不会对所有代码进行详细细致的介绍,只对模块的结构和在内核中增加hook的代码进行介绍,详细信息可参考内核源代码.
根据前面的介绍,要实现devices subsys包含如下几个方面。
1) 首先需要做的是定义设备子系统实例:
extern int devices_subsys_id;//子系统ID
struct cgroup_subsys devices_subsys={
	.name = "devices",
	.can_attach = devcgroup_can_attach,
	.create = devcgroup_create,
	.destroy = devcgroup_destroy,
	.populate = devcgroup_populate,
	.subsys_id = devices_subsys_id,
};

从上面的定义可知道devices子系统系统实现了接口can_attach,create,populate,create和destroy.

2) 定义描述设备白名单的数据结构和子系统状态实例数据结构
//白名单条目结构
struct devices_whitelist_item{
	u32 major,minor;//设备主从设备号
	short type;//设备类型(块设备或者字符设备)
	short access;//允许的权限(读,写和mknod)
	struct list_head list;//设备白名单条目链表节点
	struct rcu_head rcu;//rcu锁,暂时忽略
};
//设备白名单子系统状态实例数据结构
struct dev_cgroup{
	struct cgroup_subsys_state css;
	struct list_head whitelist;
};

从上面的两个数据结构可知道子系统实例数据结构中包含了控制组子系统状态项和白名单条目链表头指针,通过将css结构定义在dev_cgroup中,利用container_of宏可以很容易的根据task_struct或者cgroup结构计算出dev_cgroup结构实例.container_of的具体实现见附录.转化函数如下:
static inline struct dev_cgroup *css_to_devcgroup(struct cgroup_subsys_state *s){
	return container_of(s,struce dev_cgroup,css);
}
static inline struct dev_cgroup *cgroup_to_devcgroup(struct cgroup *cgrp){
	return css_to_devcgroup(cgroup_subsys_state(cgroup,devices_subsys_id));
}
static inline struct dev_cgroup *task_to_devcgroup(struct task_struct *task){
	return css_to_devcgroup(task_subsys_state(task,devices_subsys_id));
}

3) 编写代码实现devcgroup_can_attach,devcgroup_create,devcgroup_destroy,dev_cgroup_populate函数

cgroup_subsys中接口can_attach在将一个任务移动到控制组之前调用,如果该接口返回错误则退出链接操作,devcgroup_can_attach主要进行权限检查,只有任务自身或者具有管理权限的任务才具有将任务移动到控制组.
static int devcgroup_can_attach(struct cgroup_subsys *ss,struct cgroup *new_cgroup,struct task_struct *task){
	if(current != task && !capable(CAP_SYS_ADMIN))
		return -EPERM;
	return 0;
}


cgroup_subsys中接口create在创建控制组时被调用,用于为控制组分配一个cgroup_subsys_state对象以便于将控制组和子系统联系起来.其实现代码主要包括:分配一个dev_cgroup结构,并且初始化设备白名单,如果控制组为根控制组,则将设备白名单初始化为对所有的设备具有所有的权限;否则继承父控制组的设备白名单.
static struct cgroup_subsys_state *devcgroup_create(struct cgroup_subsys *ss,struct cgroup *cgroup){
	struct dev_cgroup *dev_cgroup,*parent_dev_cgroup;
	struct cgroup *parent_cgroup;
	int ret;
	dev_cgroup = kmalloc(sizeof(*dev_cgroup),GFP_KERNEL);
	if(!dev_cgroup)
		return ERR_PTR(_ENOMEM);
	INIT_LIST_HEAD(&dev_cgroup->whitelist);
	parent_cgroup = cgroup->parent;
	if(!parent_cgroup){//根控制组,允许所有设备的所有权限
		struct dev_whitelist_item *wh;
		wh = kmalloc(sizeof(*wh,GFP_KERNEL);
		if(!wh){
			kfree(dev_cgroup);
			return ERR_PTR(-ENOMEM);
		}
		wh->minor = wh->major = ~0;//0xFFFFFFFF表示所有设备号
		wh->access = ACC_MASK;//给你所有的权限,挂根的东西总会有额外的照顾,o(∩∩)o...
		wh->type = DEV_ALL;//所有类型的设备
		//添加到白名单中
		list_add(&wh->list,&dev_cgroup->whitelist);
	} else {
		//获取父控制组白名单子系统结构实例
		parent_dev_cgroup = cgroup_to_devcgroup(parent_cgroup);
		mutex_lock(&devcgroup_mutex);//获得锁定
		//复制白名单
		ret = dev_whitelist_copy(&dev_cgroup->whitelist,&parent_dev_cgroup->whitelist);
		mutex_unlock(&devcgroup_mutex);//释放锁
		if(ret){
			kfrer(dev_cgroup);
			return ERR_PTR(ret);
		}
	}
	return &dev_cgroup->css;//返回和控制组cgroup关联的cgroup_subsys_state对象
}


cgroup_subsys中接口destroy用户在删除控制组时调用进行子系统相关的清理工作,devcgroup_destroy主要进行释放申请的dev_cgroup空间以及白名单列表.
static void devcgroup_destroy(struct cgroup_subsys *ss,struct cgroup *cgroup){
	struct dev_cgroup *cgroup ;
	struct dev_whitelist_item *wh,*tmp;
	dev_cgroup = cgroup_to_devcgroup(cgroup);//获取和控制组cgroup关联的dev_cgroup对象指针
	//释放白名单条目
	list_for_each_entry_safe(wh,tmp,&dev_cgroup->whitelist,list){
		list_del(&wh->list);
		kfree(wh);
	}
	kfree(dev_cgroup);//释放dev_cgroup结构
}


cgroup_subsys中接口populate主要在控制组创建完成后调用,主要用于在控制组目录中添加需要的控制文件.
static int devcgroup_populate(struct cgroup_subsys *ss,struct cgroup *cgroup){
	return cgroup_add_files(cgroup,ss,dev_cgroup_files,ARRAY_SIZE(dev_cgroup_files));//添加控制文件
}


4) 定义控制文件结构用于在控制组文件夹中创建控制文件.
static cftype dev_cgroup_files[]={
	{
		.name = "allow",
		.write_string = devcgroup_access_write,
		.private = DEVCG_ALLOW,
	},
	{
		.name = "deny",
		.write_string = devcgroup_access_write,
		.private = DEVCG_DENY,
	},
	{
		.name = "list",
		.read_seq_string = devcgroup_seq_read,
		.private = DEVCG_LIST,
	},
};

devices子系统定义了三个控制文件devices.allow,devices.deny和devices.list以及相关读写接口,接口的实现不做论述,可参考内核代码.

5) 定义hook供其在其他内核模块调用.
devices定义了两个hook函数用于检查控制组是否对给定的设备具有相应的权限,这两个函数的实现均通过检索白名单列表来确定是否具有相应的权限,故在此就不给出代码,仅给出这两个函数的声明以及在内核其他模块的调用点.
权限检查函数:
//检查是否具有设备dev的权限mod
int devcgroup_inode_mknod(int mod, dev_t dev);
//检查inode节点是否具有权限mod
int devcgroup_inode_permission(struct inode *inode,int mask);
这两个hook分别在文件系统中创建或者访问的时候调用进行权限检查,其调用点为:
int inode_permission(struct inode *inode,int mask);//fs/namei.c
static int __blkdev_get(struct block_device *bdev,fmode_t mode,int for_part);//fs/block_dev.c
int vfs_mknod(struct inode *dir,struct dentry *dentry,int mode,dev_t dev);//fs/namei.c
该子系统只在内核其他模块中增加了3行代码就实现了设备白名单,感慨一下内核设计者的独到设计~~~~.


4. 编写简单子系统模块
根据上一节对devices子系统实现的分析,我们可以按照其格式实现一个简单的子系统,在下面的子系统中不能作任何有意的事情,仅仅用于对子系统的认识,详情参考hello.c

5. 附录
在第3节中遇到了contailer_of宏用于通过数据结构中某一数据项的地址来计算数据项所在数据结构的地址,其定义如下:
#define container_of(ptr, type, member) ({                      \
        const typeof( ((type *)0)->member ) *__mptr = (ptr);    \
        (type *)( (char *)__mptr - offsetof(type,member) );})


结束语
本文简要介绍了控制组相关的主要数据结构以及这些数据结构的相互关系.内核将控制组组织成一个或者多个树形层次结构,并通过控制组文件系统对控制组进行管理。为使用户空间可与控制组交互通信,控制组文件系统提供了控制文件机制,用户可通过读写控制文件来实现对控制组的操作.此外,本文还简要介绍了子系统的初步实现框架,并剖析了子系统中比较容易理解的设备白名单子系统.最后给出了一个简单的子系统模块实现.本文只是浅显的介绍控制组相关知识,没有对控制组文件系统进行详细的介绍,在后续的工作中还会对其进行进一步的分析.最后,上面的文字可能会存在错误或者不当的分析,希望大家可以批评指正.

冗长的论述有时会让人厌烦,希望您可以忍受我杂乱无章的表述,good luck!

0
2
分享到:
评论

相关推荐

    基于MATLAB/SIMULINK的临近空间飞行器气动力/直接力组合控制系统分析.pdf

    本文针对临近空间飞行器的建模和控制方法进行研究,目的是提出一种简化流程的分析方法,通过设计气动力和直接力组合控制系统来实现对临近空间飞行器的控制。 为了简化临近空间飞行器的建模过程,本文采用了小扰动...

    数据质控功能初步分析1

    数据质控功能初步分析1涉及的关键知识点主要集中在数据质量管理、软件功能模块设计和系统性能要求等方面。以下是对这些知识点的详细说明: 1. **数据源管理**:此功能支持多种类型的数据源,如关系数据库(MySQL、...

    第八章 现代控制理论初步.rar

    综上所述,"第八章 现代控制理论初步"可能会详细讨论这些概念,通过实例和数学模型帮助读者理解并掌握现代控制系统的设计与分析方法。对于学习和研究自动控制的人员来说,这是一份非常有价值的参考资料。

    安卓智能家居,android app源码,初步是实现开关、led灯亮度,电机pwm等,定时

    本项目提供的【android app源码】是一个基础实现,涵盖了对开关、LED灯亮度调整、电机PWM控制以及定时功能的编程。 首先,让我们深入理解一下这些功能的实现原理: 1. **开关控制**:在智能家居系统中,开关通常指...

    高通量测序数据的组装及后续生物信息学分析

    - **高杂合基因组的组装策略**:采用多阶段组装方法,先进行初步组装,再利用额外的文库数据来解决杂合性问题。 - **高重复基因组的组装策略**:采用不同大小的文库构建策略,结合长读测序技术来减少重复区域的影响...

    某汽车总装项目总装工艺初步分析报告.docx

    综上所述,某汽车总装项目的初步分析报告不仅仅是一个技术文件,它更是一份全面考虑了生产效率、成本控制、未来发展等多重要素的综合规划蓝图。通过这份报告,汽车制造企业能够更加清晰地认识到在项目规划中所应遵循...

    JMP 数据分析软件

    2. **利用图形化界面进行初步探索**:在深入分析之前,先使用 GUI 对数据进行快速预览和初步探索,确定分析方向。 3. **创建和使用宏**:JMP 提供了一系列实用的宏命令,可以简化常见任务的操作,如数据导入、数据...

    新集煤矿1#煤开采太灰突水危险性的初步分析

    本篇文章通过对淮南煤田和新集煤矿的水文地质条件的分析,对新集煤矿太灰岩的富水性、底板岩层的隔水能力以及构造控水特征进行了初步研究,旨在判断在开采1号煤层过程中太灰水突出的危险性,并分析了可能的防治措施...

    宫内铅暴露脐带血DNA甲基化450K芯片检测结果的初步分析.pdf

    从给定的文件信息中,我们可以提取以下IT及生物信息学相关的知识点: ...这些领域不仅需要高度专业的知识储备,还需要先进技术和精密设备的支持,才能实现复杂生物数据的获取和分析,从而推动科学研究和技术进步。

    精煤族组分制备泡沫炭的初步研究

    标题中的知识点为“精煤族组分制备泡沫炭的初步研究”,这表明文章主要探讨了如何使用精煤族组分这种煤炭副产品来制备泡沫炭。描述中提到“采用一步三段控温法制备泡沫炭”,说明研究中使用了一种特殊的温度控制工艺...

    一种芯片式血流变分析系统的初步研究.pdf

    根据给定的文件信息,本文将围绕芯片式血流变分析系统的初步研究展开详细的知识点说明。该研究主要涉及以下几个方面:流路驱动装置、微流控芯片、双光路图像采集系统、实验过程和结论,以及讨论。 首先,流路驱动...

    数字电路基础组合电路的分析与设计PPT学习教案.pptx

    在初步得到表达式后,为了简化后续的分析与设计工作,我们通常需要对表达式进行化简。布尔代数的定律,如德摩根定律、分配律等,在化简过程中起到了关键作用。化简后的逻辑表达式应能以最简洁的方式描述整个电路的...

    软件实践第十组初步项目设计1

    根据给定的信息,我们可以了解到这是一个软件实践项目的初步设计文档,主要由软件实践第十组负责。该项目的核心是构建一个基于Django框架的数据库应用,旨在整合其他小组提供的数据和算法,并通过ORM(Object ...

    xiangguanfenxi.zip_WFW_汇总_相关分析法_相关分析法脉冲响应_脉冲响应分析

    5. "Lab 1" - 可能是某个系列实验的第一部分,专注于相关分析法和脉冲响应的初步学习。 6. "例子" - 这可能包含了一些实际案例,用于展示如何应用相关分析法和脉冲响应分析来解决实际问题。 脉冲响应分析在系统辨识...

    大数据技术分享 Spark技术讲座 Azure Databricks如何帮助实现物联网分析 共17页.pdf

    通过使用Azure Databricks等先进的大数据处理工具和技术,Lennox成功地解决了物联网分析中的挑战,实现了物联网数据的有效分析。这一案例展示了大数据技术在解决实际问题中的强大能力,特别是对于大规模数据处理的...

    小型企业客户信息管理系统设计与实现的初步设想.doc

    在设计和实现小型企业客户信息管理系统时,首要任务是对需求进行深入分析。传统的客户信息管理方式,如纸质记录,存在信息共享困难、数据分析速度慢等问题。随着计算机技术的进步和网络的普及,企业需要处理的信息量...

    基于单片机的温度控制系统设计与实现 (2).pdf

    对系统进行长时间运行的稳定性测试,分析温度控制精度,评估系统整体性能。 第 5章 结论 总结整个项目的设计与实现过程,讨论可能存在的问题和改进方向,对未来进一步研究提供启示。 关键词:单片机系统;传感器...

Global site tag (gtag.js) - Google Analytics