从著名的list_head看linux内核中OO

时间:2022-06-04 23:35:52

如果有人问我最欣赏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总是在人们争论不休的问题两端之间游走,有种行走于江湖的味道,这往往让一些人不知所措,谁让它的创始人长得那么滑稽呢?呵呵!