从著名的list_head看linux内核中OO && 从Unix分层内核栈以及中断处理看Linux内核的另类

如果有人问我最欣赏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;

     ...

};

扫描二维码关注公众号,回复: 5042236 查看本文章

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

如果你一开始就读linux内核,那么你的思路会变得非常开阔,如果你读过unix内核,那么你会惊叹于linux内核的巧妙,如果你连NT的内核也了解,那么你会发现linux内核真是个另类,本文从内核栈的角度来简要说明linux为何另类。

 
了解操作系统原理和计算机体系结构的都知道在应用程序陷入内核的时候要将用户栈切换到内核栈,

而引起陷入的有三种方式:

1.系统调用;

2.硬件中断;

3.异常。

但是如果执行续本来就在内核而发生了中断,那么怎么办呢?unix,nt,linux的处理是不同的: 


1.unix的方式: 
unix中的进程的内核上下文有静态部分和动态部分,其中静态部分就是区表,进程表项和U区,本文我们不考虑,动态部分最重要的就是内核栈,unix按照 系统支持的中断级别种类将进程的内核栈分为多个层,每个中断级别对应一个层,这个中断级别的概念和nt的irql有些类似,其实它们的作用还真的一样,用 来屏蔽同优先级或下优先级的中断,由此来保证多层的中断栈不会使上下文乱掉,栈的层次是单向提高或者降低的。具体过程就是:用户空间的进程进行系统调用或者被中断,那么系统切换到第0层内核栈,开始执行处理程序代码,此时有一个更高级别的中断发生,那么系统切换到第1层内核栈,....系统切换到第n层内核栈,如果采用硬件屏蔽中断的方式,那么第n层以及第n层以下对应级别的中断根本就不会发生了,如果不是硬件屏蔽,那么可能还会发生,只不过不会执行中断处理代码,而只是将低优先级的中断pending,等到以后中断级别降低后再延迟执行,和linux软中断类似,只不过unix对待中断的方式更加统一 了,每个中断级别对应一个栈; 


2.NT的方式: 
在NT内核,系统定义了irql的概念,和unix的类似,只是没有分层的内核栈,中断完全借用当前线程的内核栈,因此nt的中断都是在任意上下文执行的,unix是同步的,意思是说虽然用了中断级别的概念,但是也就对“真正”的中断有用,而nt将很多可能的操作都往irql框架填,以“假”中断--软 中断的方式执行。某种意义上,nt也是和unix一样以一种统一的方式处理中断,不同的是unix对待当前进程和中断的方式更加仁慈,多层内核栈保证了一 切,nt就不那么仁慈了,直接借用了当前进程的内核栈,造成中断和当前进程鱼龙混杂的局面; 


3.Linux的方式: 
Linux的方式对于高手是轻松的,对于思维僵化的人是沉重的,它的实现相当灵活,毅然抛弃了硬件都建议的中断优先级的概念,从而也就抛弃了处理器中断级别的概念,所有中断一视同仁,完全可嵌套;另外linux没有那么多所谓的“层次”,效率绝对有优势,对于中断上下文,和nt一样,借用当前进程上下文, 采用硬件中断发布软中断的方式,这个也和nt一样,但是却没有出现混乱,linux内核尽量减少硬件中断处理时间,而且也不过分执行软中断,过多的软中断在ksoftirqd的进程上下文执行,它有自己的内核栈。你不能说linux没有内核栈是它的弱点,linux内核是高度可配置的,linux内核也可以配置独立的内核中断栈,反正不管你怎么说,linux都是可以做到unix或nt能做到的事情,linux内核是典型的“两面三刀”,呵呵,不过我喜欢。 

谈完上面的三点,我想linux为何另类的原因就不用多说了,linux没有规则,没有规则就是灵活,而且总是毫不留情的抛弃硬件的建议,按照自己的方式 进行处理,其强大的内核机制保证不会发生混乱,而强大的内核机制竟然出自一个个小巧的数据结构,其一眼望去不是那么华丽,但是却是深不可测,毫无定法。

 
说linux不规则,仅仅从系统结构的角度来说,它处理问题的方式却比unix和windows更规则,比如对于陷入内核的进程,和没有陷入的一样接受同样的调度,linux只是简单的区分交互进程来使得睡眠过久的进程提升优先级而不会像unix的sysyem v那样将唤醒优先级和睡眠原因硬性联系起来也不会像nt那样规定不允许这也不允许那。 

综上,如果你仅读过linux,那么你会想当然认为所有内核都那样,有这种想法的原因就在于linux内核设计原则就是按照现实世界的原则设计的,它的开 发人员极端复杂,本来就是三教九流,就难怪内核的两面三刀了。别的内核可能就不是那么“现实”了,它们都是为了开发“内核”而开发内核的,和现实的联系没有linux内核那么大,开发过程很规则,要经过总体构思和设计。

这个世界,只要经过组织全盘设计过的东西,十有八九没有独侠的东西更具杀伤力,看过金庸的小说吗?里面那个武林高手是属于军队的!?招无定法才是武林至尊,不要看外表多华丽,不要看内在多合逻辑,那还看什么,什么也不要看,只要默默品味就可 以了。

猜你喜欢

转载自blog.csdn.net/armlinuxww/article/details/86495768
今日推荐