一个操作系统的实现(10):进程间通信

如果一个进程要完成一个操作需要系统内核的帮助,那么这个系统就需要为进程提供一些可用的系统调用。为了让系统能够实现更多的功能,我们可以有两种选择:一种是内核处理所有的系统调用,所有的工作全部扔给内核;另一种则是将这些功能,如读取文件、创建进程等,从内核中剥离,单独形成一个或几个进程来管理这些事务,而内核只负责一些基本的操作如进程调度等。前者叫做宏内核,而后者叫做微内核。


就我个人看来,微内核更有优势,因为它的这种结构使得对内核功能的增加和删除都很方便。但是微内核的实现方法比较复杂,写起来不如宏内核简单。


宏内核的系统调用


比如Linux中有个系统调用叫做fork()。且不管这个调用是做什么用的,它的实现方法如下:


程序调用fork()
系统接受这个调用,在一个叫做“系统调用表”的结构里(类似中断向量表)查找相应的实现该功能的函数
调用该函数,实现功能
可以看到,宏内核实现系统调用是非常简单的。


微内核的系统调用


再来看一下Minix的系统调用,还是以fork()举例:


程序调用fork()
内核接到调用的请求,将其做成一个消息
内核将消息发送给MM(Memory Manager,内存管理器)这个进程
MM接收到消息,完成请求,实现功能
微内核的内核只是实现一个“中转站”的作用,其它的工作都交给各个进程去做。因此Minix建立了一套消息机制,利用这套消息机制来实现进程与进程之间的信息交流,简称“进程间通信”(Inter-process Communication,IPC)。


同步IPC和异步IPC


同步IPC的概念就是:发送者发送消息后,就开始等待;而接收者在决定接收消息后也开始等待,直到消息送达,发送者和接收者才能继续进行自己的操作。


异步IPC则是:发送者发送消息后就继续执行其它工作;而接收者拥有一个消息缓冲区,接收者只是定时检查一下缓冲区,有消息则处理,无消息则跳过。发送者不知道消息是否真正送达,而接收者也不知道消息何时发送。


对于系统调用来说,最好还是使用同步IPC,因为程序要等待内核返回结果之后才能继续。因此在发送消息的过程中,我们称这两个进程是“阻塞”(block)的。在进程调度时,如果一个进程处于阻塞状态,则不需要分配给它CPU时间,因为它们此时不能完成任何操作。


假设发送方为A,接收方为B。A发送消息的过程如下:


准备消息
调用系统调用,发送消息
系统判断是否发生死锁(死锁的意思就是,如果消息的发送链构成一个环,那么环上的所有进程都将接收不到消息而一直阻塞下去,造成程序停止响应。这种情况是要避免的)
判断B是否处于等待状态。若是,则将消息传给B,B解除阻塞状态;否则,A被阻塞,将消息放入A的消息队列
B接收消息的过程如下:


准备接收消息
调用系统调用,接收消息
若B有来自硬件或中断的消息,则将消息发送给B,并返回
若B要接收来自所有进程的消息,则从发送队列选取一条消息发送给B,并返回
若B要接收仅来自A的消息,并且A正在向B发送消息,则将消息发送给B,解除A的阻塞状态,并返回
若没有消息发送给B,则B进入阻塞状态
关于assert


assert的作用是,在调试阶段帮助找出代码的错误。比如某处代码应该保证变量x不为0,那就加入一个assert判断“x!=0”。一旦执行到这段代码就会判断x是否满足条件。当然,一般情况都是满足的,但是不排除代码在编写的时候漏掉了某些边缘值或没有预料到的情况,从而使得x为0,这时继续执行可能会引发严重的错误,所以assert函数会立即中止进程。要注意,assert只能在调试阶段使用,因为它本来就是用于调试代码、找出代码结构的不合理性的。

你可能感兴趣的:(一个操作系统的实现(10):进程间通信)