<script type="text/javascript"> </script> 如果有人问我最欣赏linux 的什么,我会毫不犹豫地回答:list_head 。这个小小的结构向世人说明了用c 语言写成的linux 内核也在实现着OO ,下面我就具体来说一下下。先看list_head
struct list_head {
struct list_head *next, *prev;
};
就 这吗?就这!你别看它小,它却可以充当任何在内核中存在的东西,几乎所有的内核管理设施都用到了它,用OO 的语言讲就是它作为基类,几乎所有的内核设施都 继承了它,OO 中一直争论不休的就是用继承还是用包含,“ 遗憾” 的是,linux 内核利用list_head 将这两者集成在了一起,让人叹为观止!举几个 例子:
struct tast_struct{
...
list_head tasts;
...
};
struct sched_entity {
...
struct rb_node run_node;
struct list_head group_node;
};
仅此二例就可以说明list_head 可以作为task_struct 还可以作为sched_entity. 这仅仅是单向的转化,可以说sched_entity 继承了list_head ,也可以说tas_struct 继承了list_head ,那么到底是谁呢?需要的时候怎么转化过来呢?这个一会再说,先说说这 么做的意义,众所周知,继承的意义就是子类可以有父类的行为,还有自己的行为,那么list_head 的行为是什么呢?链表嘛,就是插入,删除,查找,再 问一个,内核数据结构作为子类为何要以拥有这些操作的list_head 作为它们的父类呢?原因就是提取公因子的结果,想想看,task_struct 是 管理进程的,net_device 是管理网卡的,这些能有什么一样的地方,要认识到这是在内核里面,内核的功能就是管理一切,正是需要被管理对象的注册, 注册的结构都要放到链表里面,于是几乎所有的数据结构都需要链表的操作,这样天经地义的继承list_head 就没有什么问题了,下面看看怎么取出原对 象,这也就是多态的概念范畴了,list_head 到底是什么样的对象呢?
#define list_entry(ptr, type, member) /
container_of(ptr, type, member)
#define container_of(ptr, type, member) ({ /
const typeof( ((type *)0)->member ) *__mptr = (ptr); /
(type *)( (char *)__mptr - offsetof(type,member) );})
上面的代码只要你懂c 语言,并且指针玩得好,一眼就看懂了,举个例子巴:
inode *inode ;
struct list_head * tmp = next;
inode = list_entry(tmp, struct inode, i_sb_list);
这里只有一个list_head ,但是我们需要的是一个inode ,那么调用list_entry 就可以取到inode 了,什么意思呢?就是说inode 结构的i_sb_list 字段表示一个list_head ,现在有了list_head ,那么此list_head 此的地址减去i_sb_list 相对于inode 结构的偏移就是inode 结构体了,其实现代的面向对象语言底层实现机制也是这样的,只不过偏移相对固定,比如c++ 语言中的类的对象在 内存中的布局就是把基类放到这个对象固定偏移的地方,根据编译器不同这个位置也不同,很一般的就是基类放到最前面,以下才是子类私有的数据,linux 内 核和现代c++ 语言如出一辙,至于java ,本质上也是这样,只不过没有c++ 那么直观罢了,java 的对象由jvm 创建并由jvm 管理,看看jvm 的源 代码就知道了,我看过一些,jvm 管理对象的方式和linux 内核的实现更加相似一些。但是这里的list_entry 并没有进行类型检查,这实际上是不 必要的,它可是内核啊,出了问题崩溃掉自然比强行恢复要好得多,再者说一般人接触不到内核,再内核的调试阶段问题几乎就都解决了,如果还有问题只好听老天 安排了,内核中少了类型检查会节省很大一笔开销的,另外一个更有意思并且更有意义的原因就是保持最简单最天真的形式,这样黑客就不会想方设法去避开类型检 查了,因为压根就没有检查,如果你有问题,那么很简单,直接崩溃,不给黑客任何可乘之机,这印证了一条哲理:只要有检查,总能避开,最简单的方式就是最好 的方式,最危险的反而最安全!
内核中不光实体设施用了list_head ,就连另外的管理设施也用,比如klist
truct klist {
spinlock_t k_lock;
struct list_head k_list;
void (*get)(struct klist_node *);
void (*put)(struct klist_node *);
};
struct klist_node {
struct klist * n_klist;
struct list_head n_node;
struct kref n_ref;
struct completion n_removed;
};
如果说刚才的论述令你不知所云的话,这两个应该比较简单了,毕竟它们都是list ,一个klist 继承list_head 从字面意义上讲要比一个task_struct 继承list_head 好理解得多。
最后谈一下继承和包含,就以klist 为例吧,你说klist 继承一个list_head 呢还是包含一个list_head 呢?实际上都是,为什么这么说呢?要明确list_head 可以代表klist 自身,因为可以用list_entry 转化,我们说list“ 是” 一个list_head ,这个意义上就是继承,但是另一方面,一个klist 确实“ 有” 一个list_head ,就像结构中写的那样,list_head 确实是klist 的一个字段,这里list_head 不是作为一个klist 存在,而是作为一个真实的链表存在,这个意义上讲,klist 包含一个list_head ,有点文字游戏了,呵呵!linux 总是在人们争论不休的问题两端之间游走,有种行走于江湖的味道,这往往让一些人不知所措,谁让它的创始人长得那么滑稽呢?呵呵!