微内核将内核进程放入用户态的问题

     不论宏微内核,要进行系统调用,首先要陷入内核态,对于宏内核,陷入内核态后,调用内核态的函数来实现相应功能; 对于微内核,陷入内核态后,进行消息传递,然后等待结果。
     可以看到,不管宏微内核,都必须要陷入内核,也就是执行int 0x80这种指令,但是陷入内核这种操作是比较耗时的,要保存切换堆栈,还要保存寄存器,所以可不可以不陷入内核而直接进行系统调用呢?
     理论上是可行的,但是会带来很多设计问题。(本文讨论微内核,毕竟宏内核效率比微内核要好很多)
     首先好处显而易见,每次进行系统调用,不用陷入内核态,那效率自然上来了,相当于把系统调用转化为函数调用了,效率损失自然降到最小。至于发出消息,等待回应这段时间,不计算为损失,因为微内核要使用消息传递,必然需要等待回应。
     下面说一下不陷入内核态引起的设计问题,这也是本文主要要讨论的问题。
     原本的微内核设计,可以把内存管理模块,文件管理模块等移出内核,然后把内核和进程调度模块放在一起(工作于内核态)。
     要进行调用时通过int 0x80,陷入内核,等待结果,那这个int 0x80,有什么用呢?我觉得这就是相当于一个接口,把内核+进程模块(内部模块),与内存,文件管理(外部模块)(下文简称内、外部模块),从源码层分开。具体意思,比如,现在要进行一个系统调用,要做的就是(要执行的代码)就是int 0x80就好了(至于消息体参数设置就不说了),对于消息如何传递,进程什么阻塞等问题,外部进行系统调用的模块一概不用箮,陷入内核态后,由内部模块来负责,可以说,用这种方式,有很低的耦合度。下图可以看到,两部分其他并无过多干涉,外部模块也看不到内部模块的实现,也不需要关心,只用一个int 0x80即可。

微内核将内核进程放入用户态的问题
     如果要把内部模块放入用户态呢,此时各模块全部工作于用户态,也就不需要陷入内核了,可以直接进行消息传递,但是这会有什么 问题呢?
     直接传递消息,不需要内核来消息传递,也就是说要直接调用消息传递相关函数。也就是下面这样:
微内核将内核进程放入用户态的问题
     消息传递机制对所有模块都是可见的(可直接使用)。
     那消息传递机制又代表了什么呢?比如send函数,发送消息,对方运行中,自己要阻塞吧。也是说消息传递相关函数中包含了阻塞函数,阻塞函数又是怎么实现的呢?一般是通过影响进程调度相关数据结果(比如就绪队列)等来实现的。意思就是说把消息传递暴露出来,也就意味着几乎将整个进程调度模块也都暴露了出来,让各个外部模块包含了进程调度模块,这应该不符合微内核的思想了。
     继续想,各个模块包含了进程调度模块的后果还有什么呢?就绪队列及进程队列可能会让各个外部模块都有自己的队列或进程相关结构(比如内存模块有自己的进程队列,文件模块有自己的进程队列),而且这些队列很有可能不是统一的,而是各不相同,那以谁为准呢?当然可以规定进程相关结构统一起来,比如就在内存地址0x100处,所有进程调度时都从这里取数据。(比如在这放就绪队列)
     比较符合微内核设计思想的是把进程调度模块也独立出来,成为一个单独的进程(像内存,文件管理模块一样)。但如何从内核启动第一条进程呢?这就成了先有鸡还是先有蛋的问题了,最起码要先pcb等结构暴露给内核模块,让内核初始化这些数据,然后启动进程。(时间中断处理程序与进程也有关,应该也需要处理),或多或少破坏了一些封装性。
上面从设计的角度简要说了一些可能出现的问题。
     还有一些不可避免的问题,比如send函数中的阻塞函数,或与进程调度有关的其他操作,是否应该定义为原子操作,也就是前后开关中断,但是开关中断的指令(sti, cli), 又都特权指令,用户态肯定不能执行。难道要把所有进程都放在内核态呢?这又出现数据安全的问题(内存资源)。

     总之,效率和安全肯定不是能兼得的,但是我觉得设计阶段应该更关注效率问题,毕竟安全方面可以增加一些机制来保证,但是若设计导致的效率问题,要改动起来应该是挺麻烦的,动了骨头连着筯。

参考:[1]刘福岩, 尤晋元, 曾国荪.通过单地址空间提高微内核操作系统的效率[J].计算机工程,2000,26(9):49-128.

     注:论文中提到的用capability机制,给资源标识,来保证安全的问题,没有看太懂,对于内存安全,申请时,要经过内存,可以检查是否具有权限,但是申请完使用时,是直接读写的,如何进行标识和校验。这应该是需要硬件来支持吧。