五、进程间通信

进程间通信(进程间消息传递)

一、微内核 or 宏内核

1.1 为什么进程间通信要先讨论微内核和宏内核?

经过多年灌输式的应试教育,我们读书学习的时候,往往只是被动地接受作者或者是老师所传达的知识和方法,但是并不会先问为什么。为什么书的作者要这样编排,为什么解决这个问题要用这种方法,为什么这个系统要这样设计,为什么不那样设计…除了少部分真理,这世间万事万物的出现和兴衰都有着它背后的道理。我们学习的知识也都有着它特定的适用条件的优缺点,只有在明白了“为什么”后,那些神秘深奥的知识才能在你的脑海中变得清晰,原来很多难以理解和记忆的内容也会变得不那么可怕了。

而这也是我喜欢这本书作者的写作风格的原因,作者在每次推进到一个新的概念或者设计之前,总是先说明白为什么要这样做,有时候甚至会花上很大的篇幅,这样的写作风格在有些人看来太过啰嗦,不着要点,但我却甚是喜欢,因为我相信,当我们能做到每提到一个知识点脑海中都总是先浮现“为什么是这样”的时候,我们的所学的某个领域的零散知识就会形成体系,这时我们才能够高屋建瓴地站到这整个体系的外面,从外部的宏观角度来观察这整个知识体系,了解它的结构、它的运作方式、它成功或是失败的原因以及对于做研究很重要的一点:它是否还有可以突破或者提升的点。


又扯远了…下面回到正题。

前面已经提到,当一个进程需要操作系统的帮助时,可以通过系统调用来让内核来代替它完成一些工作,而且实际上,我们的用户进程有很多事情都得依赖内核,这是现实所需,我们没有办法改变。正因如此,我们就需要不断地扩充操作系统提供的功能, 这必然会让操作系统变得庞大,但这是没有办法的事情。不过,在一件事上我们有操作的余地,那就是这些功能是加在内核里还是额外用系统进程来实现两种方法的取舍。第一种就是顺着之前的思路,每次用户进程有自己无法实现的需求要拜托操作系统,就在内核里增加相应的系统调用,把工作交给内核完成。而第二种就是,保持内核的相对精简,只让它完成那些必须地做的工作,比如中断处理、进程管理、以及我们本章要讲的消息传递(进程间通信)。(至于为什么这些工作是内核必须做的,很快就会知道)至于其他的那些需求则交给一些额外的系统进程来完成。
这两种设计也因为以上特征分别被称为宏内核微内核

下面来总结一下这两种方案的优缺点:

  • 微内核:逻辑虽然相对复杂一些,但是很严谨,结构上也优雅精致,程序实现起来也更容易模块化,移植性更好。随着操作系统功能的逐渐丰富,微内核结构会变得更加清晰一些,因为各个系统进程之间可以做到相对独立。 但是,这种结构清晰模块化也是有代价的,那就是需要一个健全且强大的进程间通信的机制,来相互传递消息,合作的对象多了,它们之间的交流也就变得更多且必不可少起来,而且往往这个通信机制的效率会决定这种分工模式整体的效率。这和我们现实生活中的分工合作模式是高度一致的,所以其实很多技术上的设计理念也都是来源于生活,因此我们也可以多多从生活中找找灵感,没准可以在架构设计上迸发出不一样的火花。在这种架构下,内核基本就值充当“邮局”或者说“中介”的职能,只要把用户进程的需求传给能够解决相应需求的系统进程,然后把 处理结果传回给用户进程即可。

  • 宏内核:逻辑简单、直接,实现起来也容易,而且也因为它的直接,避免了微内核在进行消息传递时所必须要占用的资源。缺点是随着系统功能的逐渐完善,内核会变得很臃肿,结构变得复杂,各种功能都在内核中实现,函数之间的调用关系会复杂很多。

这样看来,似乎微内核的主要优点是在开发层面的,而在系统运行效率的这个用户在意的层面上,微内核似乎没有优势,还因为进程间通信所带来的负担遭到效率上的质疑。但是

操作系统发展至今,已经有足够的证据表明微内核可以像宏内核一样快(比如Rick Rashid已经发表的的Mach 3.0和红内核系统的比较报告…(后面找来看看)

所以,在解决了效率问题的质疑后,微内核显然更胜一筹。

最后,看看宏内核的代表Linux以及微内核的代表Minix的系统调用是怎么样的

  • Linux的系统调用就像我们前面实现的那样,是同样的道理和结构:通过调用软件中断转移到sys_call中断程序,在里面通过system_call_table[]的函数指针数组转移到相应的系统调用函数。
  • Minx的系统调用则很不同,因为minix只有一类系统调用:负责收发消息的系统调用,即进程间通信IPC的系统调用。
    因为通信涉及收发操作,所以实际有三个,那就是SEND\RECEIVE以及BOTH。
1.2 我们的选择

鉴于前面一节的讨论,结论已经很明显了,从我们通过写自己的操作系统来学习知识的角度出发,架构设计更加优雅,且模块化的微内核显然更符合我们的需求。

二、IPC

IPC就是进程间通信的缩写。上一节把IPC比作邮局,其实不全对。因为IPC其实分为同步IPC和异步IPC(不愧是通信机制,必然涉及同步和异步两种分类)

  • 异步IPC:这个像邮局,发完信(把信丢进邮箱)就去干别的了,收信者也是,看看邮箱有没有信,有信就取走,没信也不会傻等,先去干别的,有信了再来拿。
  • 同步IPC:这个就看起来比较反人类了,发信的时候要一直等,等到确定对方收到信再走,接收方呢则是一旦决定要收信,就守在信箱前,直到收到信。

这样看起来异步通信更符合常理,也更省时间,似乎一般人都会选异步通信。但是这里我们却选了同步IPC,原因好处如下:

  • OS无需另外维护缓冲区来维护正在传递的消息
  • OS无需保留一份消息副本(等接收方来接收)
  • OS无需维护接收队列(发送队列还需要)
  • 收发双方都可以在任何时候清晰地直到消息是否送达
  • 从实现系统调用的角度,同步IPC也更加合理:当我们使用系统调用的时候我们的确需要等待内核返回结果之后再继续。(设想我们在程序中使用系统调用,当然是想函数返回后再继续执行后面的)

因为从算法角度来讲,时间复杂度和空间复杂度通常是相互矛盾的,同步和异步IPC的实现恰好体现了这一点,异步IP提供了时间上的方便,但是就得付出空间上的代价。这里面的取舍要根据具体应用需求进行,不能一概而论。

三、实现IPC

确立了微内核的设计原则,下面开始实现微内核中最重要的系统调用——IPC

因为前面说过,IPC模块是几个系统调用(也是微内核仅有的几个),所以还是用前面的步骤添加一个名为sendrec()的系统调用,添加过程参考前面的内容。实际上这里形式上这里并没有分开实现,而是用一个总的系统调用sendrec()来负责SEND、RECEIVE和BOTH三种操作,只不过具体想要进进行的操作以参数形式传入,在sendrec内部再根据参数调用对应的处理函数,这样形式上就统一成只有一个系统调用了。

  • sendrec的对应的内核版本sys_sendrec()函数的内容为:把SEND消息交给msg_send()处理,把RECEIVE消息交给msg_receive()处理。

由于在实现这些系统调用函数的过程中,用到了新的调试方法——实际是assert()和panic函数,就先介绍下它们的作用:它们用处主要是方便调试,我们编程的时候写在代码中容易出错(运行时错误)的位置,比如作为参数传入的指针,这两个函数的参数就是判断可能出错条件的表达式,一旦表达式为真,就会打印出错误的类型和位置,然后调用abort()终止程序。通常可以利用宏定义比如_DEBUG宏来控制它们是否起作用,因为它们的定义放在了条件编译指令中。具体参考这里:assert()函数详解

另外,注意,assert()使用了一个不一样的printl打印函数,其实现是最终调用了printx的系统调用(定义在tty.c中),这个系统调用能够区分(通过叫magic char 的字符来标识)调用它的是用户进程还是系统进程。如果是系统进程就会让系统停转,如果是用户进程则只是让该用户进程停转。

支持IPC的那个唯一的系统调用sendrec()

system.asm中,通过寄存器传递参数,然后int INT_VECTOR_SYS_CALL引发中断

!这里注意:因为有3个参数了,加了dx来传,但是save中用到了dx,所以需要保护dx,save中进行相应修改

中断处理程序sys_call -> sys_sendrec()

中断处理程序sys_call将寄存器传入的参数入栈,然后通过函数指针调用对应的系统调用函数sys_sendrec()

sys_sendrec()根据调用对应的消息收发函数

分别是msg_send()和msg_receive()
这两个函数的实现涉及到很多的知识,值得好好研究探讨。。。

为了实现BOTH添加一层封装send_recv()

因为一次调用sendrev()只能执行msg_send()或者msg_recv()其中一个,而实际上还有第三种消息类型BOTH,msg_send()再msg_receive()所以需要将系统调用再添加一层封装来支持BOTH。每次使用IPC机制的时候都调用send_recv()而不是行msg_send()或者msg_recv()

增加了消息机制之后的进程调度

因为消息机制的加入,我们也对进程引入了阻塞的概念,体现在进程表的p_flags属性。被阻塞的进程是不会被调度器调度的,因此在调度函数schedule()里要在调度时加一个对于p_flags的判断。

使用IPC来替换系统调用get_ticks

使用IPC+系统进程来实现原来系统调用的功能的流程:
用户进程调用get_ticks()->get-ticks()调用send_recv()这个系统调用(IPC)

->传递消息给负责的系统进程task_sys()->task_sys()通过IPC机制收发给它的消息

->根据消息,执行相应任务,获取结果后返回(这种要获取系统进程返回的消息的,通常在get_ticks中使用BOTH类型的消息)

->用户进程收到消息

注意要记得在进程表中添加系统进程task_sys()的表项,以及对表项的初始化。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值