(一)进程间通信的基础知识
之前在Linux&Apue(0.4.0)中举到过个例子,就是供水厂供水的情况。
假如某地区停水的,我们难道大老远跑过去堵供水厂要解决方案吗?当然不啊,我们需要通过渠道(IPC方法)联系供水厂的有关部门进行交流(父子进程通信),我们也可以向临近地区的进行交流(兄弟进程通信)。
现在,问题又来了。供水厂设备故障了,无法向各地方供水了,怎么办呢?通过渠道(IPC方法)联系隔壁供水厂进行支援了(互不相关的两个进程通信)。
当然,上面举例只是把大致的进行描述。如果进行细分还有许多要补充的。
(1) 进程间通信定义
进程间通信:是一组编程接口,让程序员能够协调不同的进程,使之能在一个操作系统里同时运行(至少两个进程或线程间传送数据或信号),并相互传递、交换信息。
进程间通信目的:
①数据传输:一个进程需要将它的数据发送给另一个进程。
②共享数据:多个进程想要操作共享数据,一个进程对共享数据的修改,别的进程应该立刻看到。
③通知事件:一个进程需要向另一个或一组进程发送消息,通知它(它们)发生了某种事件(如进程终止时要通知父进程)。
④资源共享:多个进程之间共享同样的资源。(需要内核的锁&同步机制)
⑤进程控制:有些进程希望完全控制另一个进程的执行(如Debug进程),此时控制进程希望能够拦截另一个进程的所有陷入和异常,并能够及时知道它的状态改变。
(2) 进程间通信(IPC方法)主要分类
前言:每个IPC方法均有它自己的优点和局限性。
主要分类:管道(无名,有名), 套接字(SOCKET),信号,系统IPC(包括信号量,享内存,消息队列)。
2.1 管道(两种)
管道:一个固定大小的缓冲区。
2.1.1 无名管道
无名管道:相当于一个队列结构,fd[1]为写入端(入队),fd[0]为读出端(出队)。其中信息读出后即删除,再次读取时即为下一个信息。
特点:
①只能在父子进程或是兄弟进程之间通信。
②半双工的通信模式(即数据只能在一个方向流动),具有固定的读端和写端。
③是Linux系统内核的特殊文件。
2.1.2 有名管道(FIFO管道)
有名管道:无名管道的改进,是一种文件类型,在文件系统中可以看到。
特点:
①实现不相关的两个进程彼此通信。
②提供一个路径与之关联,以FIFO的文件形式存在于系统中。
③遵循先进先出规则。对管道及FIFO的读总是开始处返回数据,对它们的写时把数据添加到末尾。
怎么实现不相关的两个进程彼此通信?
①FIFO文件形式会存在于系统中,并且在相应的磁盘上存有节点,没有对应的数据块(拥有名字和相应的访问权限),通过mknode()系统调用或者mkfifo()函数来建立。建立后,任何进程都有通过文件名将其打开和进行读写,而不局限于父子进程或兄弟进程之中。
②当FIFO管道不使用时,FIFO会在内存中释放,但磁盘节点仍会存在。
2.2 命名socket(或:Unix Domain Socket)
命名socket:实现同一主机的不同进程间的双向通信。
实现方式: 通过Unix域协议提供的进程间通信的方式实现。(在单个主机上,执行客户/服务通信的速度:Unix域协议>TCP/IP协议)
2.3 信号(或:软中断信号)
信号:是进程间通信的一种有限制的方式,是异步通知机制,用来提醒进程一个事件已经发生。当一个信号发送给一个进程,操作系统中断了进程正常的控制流程,此时,任何非原子操作都将被中断。如果进程定义了信号的处理函数,那么它将被执行,否则就执行默认的处理函数。(只做通知,不会给进程传递任何数据。)
关系:
①进程之间可以互相通过系统调用kill发送软中断信号。
②内核也可以因为内部事件而给进程发送信号,通知进程发生了某个事件。
进程处理接收信号的方法:
①类似中断的处理程序,对于需要处理的信号,进程可以指定处理函数,由该函数来处理。
②忽略某个信号,对该信号不做任何处理。
③对该信号的处理保留系统的默认值(缺省操作),对大部分的信号的缺省操作是使得进程终止。
拓展:
①原子操作:指不会被线程调度机制打断的操作,这种操作一旦开始,就一直运行到结束,中间不会有任何切换线程的操作(context switch)。
2.4 系统IPC
系统IPC:信号量、消息队列、共享内存都是用了内核里的标识符来识别。
无名管道与系统IPC比较:
对象 | 优点 | 缺点 |
---|---|---|
无名管道 | 所有的UNIX实现都支持, 并且在最后一个访问管道的进程终止后,管道就被完全删除; | 管道只允许单向传输或者用于父子进程之间. |
系统IPC | 功能强大,能在毫不相关进程之间进行通讯。 | 关键字KEY_T使用了内核标识,占用了内核资源,而且只能被显式删除,而且不能使用SOCKET的一些机制,例如select,epoll等。 |
2.4.1 信号量
信号量:是包含一个非负整数型的变量(本质:计数器),并且带有两个原子操作wait(P,lock)和signal/post(V,unlock),也叫P/V操作),用来记录对某个资源(如共享内存)的存取状况。
实现方式:
①P(wait操作):如果信号量的非负整形变量S>0,wait就将S减1。如果S=0,wait就将调用线程阻塞。
②V(post操作):如果线程在信号量上阻塞(S=0),post就会解除对某个等待线程的阻塞,使其从wait中返回。如果线程没有阻塞在信号量上,则post就将S加1。
在POSIX中两种信号量的实现机制:
①无名信号:只可以在共享内存的情况下(基于内存的信号量),比如实现进程中各个线程之间的互斥和同步(互斥信号量,条件信号量)。
②命名信号量:通常用于不共享内存的情况下,比如进程间通信。
例如:
①可以用来解决父子进程的运行顺序。
②解决线程出现临界资源问题。
2.4.2 共享内存
共享内存:两个或多个进程都可以访问的同一块内存空间,一个进程对这块空间内容的修改可为其他参与通信的进程所看到的。(因为进程间数据传递不涉及内核,所以共享内存在各种进程间通信方式中具有最高的效率。)
实现方式:
①在内存划出一块区域来作为共享区。
②把这个区域映射到参与通信的各个进程空间。(虚拟地址←页表→物理地址)
拓展:
①进程间共享信息的三种方式:
2.4.3 消息队列(Message Queue)
消息队列:在消息的传输过程中保存消息的容器(本质上是位于内核空间的链表,链表的每个节点都是一条消息。消息类型为 0 的链表记录了所有消息加入队列的顺序,除此之外每一条消息都有自己的消息类型,消息类型用整数来表示,而且必须大于 0)。
限制:
①每个消息的最大长度是有上限的(MSGMAX)。
②每个消息队列的总的字节数是有上限的(MSGMNB)。
③系统上消息队列的总数也有一个上限(MSGMNI)。
拓展:
①什么是消息?
消息:是在两台计算机间传送的数据单位,也可以理解为有边界信息的字节流。下至包含文本字符串,上至包含嵌入对象(不同于字节流,字符流)。
②管道与消息队列的区别:
对象 | 功能 |
---|---|
管道(无名管道&有名管道) | 随进程持续IPC,只能传送无格式的字节流。受限于缓冲区大小 |
消息队列 | 随内核持续IPC,能传送消息。虽然有限制,但没有管道限制大。 |