《ORANGE’S:一个操作系统的实现》读书笔记(二十三)进程间通信(一)

本文讨论了微内核和宏内核在操作系统中如何通过系统调用进行进程间的交互,特别强调了微内核将内核功能简化,通过消息机制协调任务,而宏内核则更直接。作者最终倾向于微内核,认为其有利于长期维护和学习编程的视角。
摘要由CSDN通过智能技术生成

我们提到过,当一个进程需要操作系统的帮助,它可以通过系统调用让内核替它完成一些工作。迄今为止,我们已经熟悉了系统调用的工作机制,并且已经实现了不止一个系统调用。接下来你会发现,用户进程将会有更多事情依赖于内核。比如我们想实现一个文件系统,最起码读写硬盘的工作要求助于内核。这里我们可以逐渐地增加系统进程来完成,让内核只负责它必须负责的工作,比如进程调度。这种将内核工作简单化的思想,便是微内核的基本思想。而所有工作通过系统调用扔给内核的做法,被称为宏内核。

在基于宏内核的操作系统中,完成具体任务时,用户进程通过系统调用让内核来做事情,直来直去,我们之前已经很熟悉了。在基于微内核的操作系统中,这个过程就稍微复杂一些。在完成具体任务时,内核的角色很像是中介。就比如我们将要实现文件系统吧,设想用户进程P读取一个文件,首先通过内核告诉进程FS,然后FS再通过内核告诉驱动程序(也是一个独立的进程),驱动程序读取硬盘,返回结果。这样一来,一项工作的完成就变得有些曲折,需要多个进程协同工作。于是,进程间通信也就变得至关重要了。

现在,我们的操作系统慢慢变大,接下来我们要用它管理磁盘和磁盘上的文件并管理内存等,这些都要向应用程序提供接口,到了必须决定是用微内核还是宏内核的时候了。我们不妨先找两个具体的例子看一下,了解一下它们分别是什么意思,等明白了,我们再进行选择。这篇文章就来记录下微内核和宏内核的相关内容。

微内核还是宏内核

微内核和宏内核的例子都是非常好找的。Minix是微内核,Linux是宏内核。

Linux 的系统调用

为了简单起见,书上是拿Linux0.01作为Linux的说明,因为最新的Linux内核代码量太大了,不利于短时间弄懂。其中的系统调用不止一个,我们以fork()为例进行分析。书上为了读者可以一下理清这个系统调用的脉络,对于代码细节没有进行详细说明,给了我们一个程序流程图(下图所示)。相关的具体代码可以在Linux0.01的以下文件中找到:

  • init/main.c
  • include/unistd.h
  • kernel/sched.c
  • include/linux/sys.h
  • kernel/system_call.s

从上图中我们可以看出,调用fork()实际上是调用了中断0x80,通过事先初始化好的IDT,程序转移到了_system_call,最终通过一个函数指针数组sys_call_table[]转化成了调用sys_fork()。这跟我们实现过的系统调用是很相似的,此处不再赘述。

Minix 的系统调用

Linux的fork系统调用很容易理解,但Minix的就不这么简单了,它刚开始甚至可能让你感到迷惑。我们来看一下Minix函数sys_call()的开头,代码如下所示。

PUBLIC int sys_call(function, src_dest, m_ptr)
int function;            /* SEND, RECEIVE, or BOTH */
int src_dest;            /* source to receive from or dest to send to */
message *m_ptr;            /* pointer to message */
{
/* The only system calls that exist in MINIX are sending and receiving
 * messages.  These are done by trapping to the kernel with an INT instruction.
 * The trap is caught and sys_call() is called to send or receive a message
 * (or both). The caller is always given by proc_ptr.
 */
...
}

开头这段注释非常重要,一个“only”道破天机:在Minix中,不再像Linux那样有许许多多的系统调用(sys_call_table[]中列出的有几十个),而是仅有发送和接收消息的系统调用。通过sys_call的参数function的注释我们可以知道,系统调用的种类总共有三个,那就是SEND、RECEIVE以及BOTH。

系统调用虽少,但实现的功能可不能少,那么Minix是怎样通过仅仅三个系统调用就实现与以Linux为代表的宏内核OS一样多的功能呢?我们仍以fork()为例,来看一下Minix是怎么做的。

相对于Linux,Minix的机制显得有点复杂,我们还是直接来看图。

跟Linux不同,这里多出来一个内存管理器(MM),fork()所要做的工作是由它来负责的(如果是另外的系统调用,那么具体工作可能就不是由MM来负责,比如系统调用read()就是由FS来负责的,跟MM类似,FS是单独运行的另一个进程),那么MM是如何得到用户进程的通知的呢?正确消息机制在进程之间起到了重要的作用,它类似于邮政系统,在信封(或包裹单)上写明目的地,消息就送达了。

图中使用了三种箭头,实线表示消息的发送过程,点线表示消息的获取过程,虚线表示发送和接收消息都会经历的过程。

用户进程对fork()的调用将最终转化成调用内核态的函数sys_call(),消息(即图中的m)的地址这时已经作为参数被传递进来,sys_call()可以据此得知m的内容,并在适当的时候将内容传递给MM。MM的工作其实说起来很简单,它不断地获取并处理消息,所以它能够得到用户进程发送的m,并将其存放在mm_in中。当MM通过获得mm_in得知了消息的内容是要进行fork操作,它就进一步调用其do_fork()完成整个过程。

消息的一送一收之间,fork()的大致过程我们就已经基本了解了。其实我们也完全可以猜测出其它系统调用的情况。不外乎是通过调用_syscall()转换成发送消息,将来会有相应的进程取出消息并进行处理。

说到这里,有一个情况需要说明,就是我们拿Linux中的fork()和Minix中的fork()来比较是有些不公平的。因为你也一定已经看到,实现方式真正与Linux的fork()相同的是Minix的_sendrec()和_receive(),它们都是通过中断进入内核,在内核中完成任务。而Minix中的fork()是通过两个进程分别调用_sendrec()和_recevie()这两个系统调用来实现的,从这个意义上来说,Minix中的fork()其实不算系统调用(这也是函数sys_call()的开头注释中说Minix的系统调用只有三个的原因),它只是在完成一个紧密依赖系统调用的工作罢了。不过从用户的角度,这种差别是看不见的,而且只要调用fork()时能实现需要的功能,这种差别就无关紧要。因此用户完全可以称fork()为一个系统调用。

实现方法的差别源于设计思路的不同。在Minix中,真正的系统调用只有三个,这意味着内核不必事无巨细地处理用户进程要求的所有工作,只需要做好其“邮局”的职能,将消息按照需求来回传送就够了。在Linux中内核所做的工作,在Minix中被交给专门的进程来完成。你可能一下子就明白了,原来微内核的“微”字是让内核功能最简化的意思。

我们的选择

到了这里,微内核和宏内核各自的工作原理大家估计已经有了一个大概的理解。同时它们的优缺点也基本上清楚了。宏内核的优势在于其逻辑简单,直截了当,实现起来也容易,而且也因为直接,避免了像微内核那样在消息传递时占用资源。而微内核的优势在于,它的逻辑虽相对复杂但非常严谨,结构上显得优雅和精致,而且程序更容易模块化,从而更容易移植。

从编程的难易程度上看,宏内核看上去具有一定优势,因为它很直接,不需要绕弯子,但从长期来看,当内核逐渐变大,微内核的结构会更加清晰。虽然选择微内核意味着有调试起来有些困难的消息机制摆在面前,但从设计理念上来看,微内核更加“摩登”。而且,从学习编程的角度看,搞一个微内核可以为将来架构其它东西作为很有益的参考。基于这些原因,我们选择微内核。

公众号

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值