浅淡微内核地址空间是否独立问题

        现在操作系统主要有宏微内核两种设计方法。(当然不只这两种)
        大家经常接触到的Linux是宏内核的,而微内核并不是在Linux之后才出现的设计理念,而是在Linux之前就有微内核的操作系统了。
        那为什么Linux还要采用宏内核呢?最主要的原因应该是因为微内核的效率问题,这也是微内核很难解决的一个问题。
        微内核的好处也是显而易见的,用两个字能够很好地概括—“优雅”。
        而效率毕竟是实的东西,优雅只能拿来看,这也是微内核不太流行的原因。

        宏内核本质上就是一个可执行程序,那这个程序出了问题怎么办?结果就是整个系统都挂了,不能正常工作了。
        而微内核,内存管理,文件系统等都是一个个单独的可执行程序,就好比a.out, b.out, 分别运行,a.out挂了,不会影响b.out的运行,所以在微内核里某个模块出现问题,其他模块仍然可正常工作,不会让整个系统挂掉。(当然会有些影响)

        宏内核里要想申请内存,怎么办呢?直接申请就是了,都是在一个可执行程序里,直接调用函数就好了(陷入内核,然后直接找到相应的系统调用); 如malloc --> int 0x80 -> sys_malloc;
        而微内核呢?比如文件系统要申请内存,可没那么简单,因为文件系统里没有 sys_malloc这个函数(系统调用),这么写编译都通不过。而这个功能(申请内存)是由内存管理模块负责的,所以文件系统要申请内存,要向内存管理模块发条消息,表示自己要申请内存,然后自己阻塞,等内存管理模块回应,当内存模块调度上CPU运行时,发现有人申请内存,就分配一块内存,然后把这块内存的地址再通过消息传递发回文件系统(malloc的返回值),文件系统收到消息后,就可继续向下执行了。(本段描述可能不太准确)
        相信从上面两段描述的字数上也能看出来谁快谁慢了,宏内核简单粗暴,所以快,而微内核那么长一段,可能说的都不是很清楚。

        对于微内核的好处–“优雅”,这一条应该不用详述了。我开发a.out, 你开发b.out,通信由消息传递来负责,开发过程互不干扰,开发和维护也比较方便。

下面说一下微内核慢的原因:
注:send, receive两个消息传递机制工作于内核态。
        Linux系统调用过程:malloc -> int 0x80 -> sys_malloc, 需要陷入内核态一次(int 0x80), 陷入内核态要切换堆栈,保存和恢复寄存器(影响效率)。整个过程一气呵成。
在这里插入图片描述
        微内核过程上面已经详细说了,再说几点没提到的,要进行系统调用要先陷入内核态,因为消息传递机制是工作在内核态的。假设对方阻塞(在内核态中),收到消息后,执行相应功能,得到结果,再通过消息传递发送回来(陷入内核态)。
在这里插入图片描述
        上面关于微陷入内核态的次数,只计算主要过程,陷入内核态至少两次,比宏内核要慢。对于消息发送后等待对方这段时间,我认为没有必要作为效率损失,因为使用消息传递必须要等待对方回应。所以上面只比较了陷入内核态时的效率损失。

        还有一个进程地址空间的问题,在宏内核里各进程地址空间是独立的。如果微内核也要实现独立地址空间的话(比如使用页表)。那会带来更多问题。
        比较一个系统调用,把这个调用使用到的消息体定义为局部变量(在用户栈中),然后陷入内核态后,让该进程(Sender)pcb的消息指针指向它(挂上pcb), 然后阻塞,等待对方回应,当对方(Receiver)调度上CPU运行时,必然切换页表,(由Sender的页表,切换为Receiver的页表),也就是切换了地址空间,那么当对方(Receiver)处理这个消息时,想从Sender的消息体中取出数据(如消息的类型:我得知道你想要哪条系统调用吧)时,问题就来了,现在地址空间已经切换,而Sender的pcb中的消息指针指向的是用户栈中某块内存,说的准确一点,是当时消息体的虚拟地址(比如为:0x12345678), 但是现在地址空间发生变化,用户栈虚拟地址空间不再映射到Sender的用户栈的物理页了,而是Receiver的用户栈的物理页。(各进程用户栈虚拟地址空间是统一的)
在这里插入图片描述
在这里插入图片描述
        怎么解决这个问题呢?其实很简单,把消息体定义为全局变量呗,全局变量当然没有问题,不是在栈里的,地址空间切换一般不影响它们。但是不只有消息体本身有数据共享的问题,消息体传递的数据也有上面的共享问题,尤其对于字符串。比如我要传个数字给另一个进程,直接让消息体携带过去就好了,但是我要传字符串呢?这里的字符串准确说应为字符数组(在用户栈里)。
        比较快的方法是传过去指针(一个数字可以直接携带)。然后另一个进程跨进程使用。直接通过这个携带过来的指针来访问我的这个字符串。(这里不应该算是共享内存,应该算是跨进程使用内存,毕竟是种不太合法的方式),但是上面说了,局部变量是不行的,地址空间切换后就找不到了。要解决这个问题,可以进行地址空间临时改变映射的方式,暂时共享,但是听名字也觉得不好操作,增加设计复杂度,
        另一种传递字符串的方案是让消息体传递整个字符串,把整个字符串复制到消息体里,然后对方再从消息体里拷贝出来。过程大体为:要传递一个字符串了,进入内核态准备发消息,可以把字符串复制进pcb某个位置,然后对方再从这个pcb里复制出来。(对方复制的这个过程是在内核态里进行的,有访问权限),可以看到,需要进行多次的复制,影响效率。而且这个字符串可能很大,内核栈空间前面有用户栈挡着,如果字符数组太大,肯定也是用户栈先崩溃,但是如果是向内存模块申请的内存呢?这个字符串就可以很大的,放在哪?总不能分次复制吧,这可真是一点效率也不要了。
        上面说的主要是地址空间切换对数据共享的影响。宏内核就没有这个问题,因为宏内核里,系统调用虽然陷入内核态,但都是由一个进程完成的,不存在各进程需要共享数据的问题。
        而上面问题的根源就在于各进程地址空间需要独立。
       进程地址空间为什么需要独立?我认为应该有两个原因:1、物理地址空间不够 2、保护 (当然可能还有其他原因)
        虽然64位系统的普及,物理地址空间不够的问题迎刃而解。
        也就是说只要解决好各进程数据保护的问题,也没多大必要去让各进程地址空间独立。

        也就是让所有进程共享一个地址空间,这样,还省去了切换地址空间的开销。(切换地址空间本身也影响效率)
        上面所讲的数据共享的问题也自然解决了。
        但是要想共享一个地址空间,也就必须要解决上面的进程数据保护的问题,主要是内存。
        如果要解决保护的问题,个人认为这应该是由硬件来完成的,因为内存申请后,对块内存的读写都是直接进行的,能控制它的也就硬件了。

        注:以上讨论没有考虑局部描述符,毕竟现代操作系统很少用了。我也没用过,所以无法讨论。
               对系统调用陷入内核态的讨论,没有考虑返回过程
               以上问题都是在写一个微内核时,出现的有关地址空间的问题。发现想实现独立地址空间提高太多设计复杂度,所以放弃了。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值