进程间通信(IPC,Interprocess communication)是一组编程接口,让程序员能够协调不同的程序进程,使之能在一个操作系统里同时运行。这使得一个程序能够在同一时间里处理许多用户的要求。因为即使只有一个用户发出要求,也可能导致一个操作系统中多个进程的运行,进程之间必须互相通话。IPC接口就提供了这种可能性。每个IPC方法均有它自己的优点和局限性,因此,对于单个程序而言使用所有的IPC方法是不常见的。
进程间通信主要包括管道, 系统IPC(Inter-Process Communication,进程间通信)(包括消息队列,信号,共享存储), 套接字(SOCKET),管道(PIPLE),信号量集
以下具体分析介绍每种分类:
一.信号机制
1.信号机制的基本概念
信号机制主要是作为在同一用户的诸进程之间通信的简单工具。信号本身是一个1~19中的某个整数,用来代表某一种事先约定好的简单消息。每个进程在执行时,都要通过信号机制来检查是否有信号到达。若有信号到达,表示某进程已发生了某种异常事件,便立即中断正在执行的进程,转向由该信号(某整数)所指示的处理程序,去完成对所发生的事件(事先约定)的处理。处理完毕,再返回到此前的断点处继续执行。可见,信号机制是对硬中断的一种模拟,故在早期的UNIX版本中又称其为软中断。
信号机制与中断机制之间的相似之处表现为:信号和中断都同样采用异步通信方式,在检测出有信号或有中断请求时,两者都是暂停正在执行的程序而转去执行相应的处理程序,处理完后都再返回到原来的断点;再有就是两者对信号或中断都可加以屏蔽。
信号与中断两机制之间的差异是:中断有优先级,而信号机制则没有,即所有的信号都是平等的;再者是信号处理程序是在用户态下运行的,而中断处理程序则是在核心态下运行;还有,中断响应是及时的,而对信号的响应通常都有较长的时间延迟。
2.信号机制的功能
1) 发送信号
这是指由发送进程把信号送至指定目标进程的proc结构中信号域的某一位上。如果目标进程正在一个可被中断的优先级上睡眠,核心便将目标进程唤醒,发送过程就此结束。一个进程可能在其信号域中有多个位被置位, 代表已有多种类型的信号到达, 但对于一类信号, 进程却只能记住其中的某一个。进程可利用系统调用kill向另一进程或一组进程发送一个信号。
2) 设置对信号的处理方式
在UNIX系统中,可利用系统调用signal(sig,func)来预置对信号的处理方式。其中, 参数sig为信号名,func用于预置处理方式,可分成三种情况:
(1) func=1时,进程对sig类信号不予理睬,亦即屏蔽了该信号。
(2) func=0,即为缺省值时,进程在收到sig信号后应自我终止。
(3) func为非0、非1类整数时,就把func的值作为指向某信号处理程序的指针。
3) 对信号的处理
当一个进程要进入或退出一个低优先级睡眠状态时,或一个进程即将从核心态返回到用户态时,核心都要检查该进程是否已收到信号。当进程处于核心态时,即使收到信号也不予理睬;仅当进程返回到用户态时,才处理信号。对信号的处理分三种情况进行:当func=1时,进程不做任何处理便返回;当func=0时,进程自我终止;当其值为非0、非1的整数时,系统从核心态转为用户态后,便转向相应信号的处理程序(因为该程序是用户程序,故应运行在用户态),处理完后,再返回到用户程序的断点。
二.管道机制
1.管道的类型
1) 无名管道(Unnamed Pipes)
在早期的UNIX版本中,只提供了无名管道。这是一个临时文件,是利用系统调用pipe( )建立起来的无名文件(指无路径名)。 只用该系统调用所返回的文件描述符来标识该文件, 因而,只有调用pipe的进程及其子孙进程才能识别此文件描述符,从而才能利用该文件(管道)进行通信。当这些进程不再需要此管道时,由核心收回其索引结点。
2) 有名管道(Named Pipes)
为了克服无名管道在使用上的局限性,以便让更多的进程能够利用管道进行通信,在UNIX系统中又增加了有名管道。有名管道是利用mknod系统调用建立的、可以在文件系统中长期存在的、具有路径名的文件,因而其它进程可以感知它的存在,并能利用该路径名来访问该文件。对有名管道的访问方式像访问其它文件一样,都需先用Open系统调用将它打开。不论是有名管道、还是无名管道,对它们的读写方式是相同的。
2.对无名管道的读写
1) 对pipe文件大小的限制
为了提高进程的运行效率,pipe文件只使用索引结点中的直接地址i-addr(0)~i-addr(9)。如果每个盘块的大小为4 KB, 则pipe文件的最大长度被限制在40 KB之内。 核心将索引结点中的直接地址项作为一个循环队列来管理,为它设置一个读指针和一个写指针, 按先进先出顺序读写。
2) 进程互斥
为使读、写进程互斥地访问pipe文件,只须使诸进程互斥地访问pipe文件索引结点中的直接地址项。因此,每逢进程在访问pipe文件前,都须先检查该索引结点是否已经上锁。 若已锁住,进程便睡眠等待;否则,将索引结点上锁,然后再执行读、写操作。操作结束后又将该索引结点解锁,并唤醒因该索引结点上锁而睡眠的进程。
3) 进程写管道
当进程向管道写数据时,可能有以下两种情况:如果管道中有足够的空间能存放要写的数据,在每写完一(盘)块后,核心便自动增加地址项的大小。当写完i-addr(9)地址项中所指示的盘块时,便又向i-addr(0)地址项所指示的盘块中写数据,全部写完后,核心修改索引结点中的写指针,并唤醒因该管道空而睡眠等待的进程。如果管道中无足够的空间来存放要写入的数据,核心将对该索引结点做出标志,然后让写进程睡眠等待,直到读进程将数据从管道中读出后,才唤醒等待写进程
4) 进程读管道
当进程从管道中读数据时,也同样会有两种可能:如果管道中已有足够的数据供进程读,读进程便根据读指针的初始值去读数据。每读出一块后,便增加地址项的大小。读完时,核心修改索引结点中的读指针,并唤醒所有等待的写进程。如果进程所要读的数据比管道中的数据多,则可令读进程把管道中已有数据读完后,暂时进入睡眠状态等待,直至写进程又将数据写入管道后,再将读进程唤醒。
三.消息机制
1) 消息(message)
消息是一个格式化的、可变长度的信息单元。消息机制允许进程发送一个消息给任何其它进程。由于消息的长度是可变的,因而为便于管理而把消息分为消息首部和消息数据区两部分。在消息首部中,记录了消息的类型和大小且指向消息数据区的指针以及消息队列的链接指针等是定长的。在图10-6 中示出了消息队列i中的三个消息首部msgh0、msgh3和msgh2。它们分别利用一个指向缓冲区(数据区)的指针指出消息的位置。
2) 消息队列
当一个进程收到由其它多个进程发来的消息时,可将这些消息排成一个消息队列,每个消息队列有一个称为关键字key的名称,它是由用户指定的。每个消息队列还有一个消息队列描述符,其作用与用户文件描述符一样,以方便用户和系统对消息队列的访问。在一个系统中可能有若干个消息队列,由所有的消息队列的头标组成一个头标数组。图10-6示出了消息和消息队列的数据结构。
2.消息队列的建立与操作
1) 消息队列的建立
在一个进程要利用消息机制与其它进程通信之前,应利用系统调用msgget( )先建立一个指名的消息队列。对于该系统调用,核心将搜索消息队列头标表,确定是否有指定名字的消息队列。若无,核心将分配一个新的消息队列头标,并对它进行初始化,然后给用户返回一个消息队列描述符;否则,它只是检查该消息队列的许可权后便返回。
2) 消息队列的操纵
用户可利用msgctl( )系统调用对指定的消息队列进行操纵。在该系统调用中包括三个参数,其中,msgid为消息队列描述符;cmd是规定的命令;buf是用户缓冲区地址,用户可在此缓冲区中存放控制参数和查询结果。命令可分为三类:(1) 用于查询有关消息队列的情况的命令,如查询队列中的消息数目、队列中的最大字节数、最后一个发送消息的进程的标识符、发送时间等。(2) 用于设置和改变有关消息队列的属性的命令,如改变消息队列的用户标识符、用户组标识符、消息队列的许可权等。 (3) 消除消息队列的标识符。
3.消息的发送和接收
1) 消息的发送
当进程要与其它进程通信时,可利用msgsnd( )系统调用来发送消息。对于msgsnd( )系统调用,核心检查消息队列描述符和许可权是否合法,消息长度是否超过系统规定的长度。 通过检查后,核心为消息分配消息数据区,并将消息从用户消息缓冲区拷贝到消息数据区。分配消息首部,将它链入消息队列的末尾;在消息首部中填写消息的类型、大小以及指向消息数据区的指针等;还要修改消息队列头标中的数据(如消息队列中的消息数、字节数等)。然后,唤醒在等待消息到来的睡眠进程。
2) 消息的接收
进程可利用msgrcv( )系统调用,从指定消息队列中读一个消息。对于msgrcv( )系统调用,先由核心检查消息队列标识符和许可权,继而根据用户指定的消息类型做相应的处理。消息类型msgtyp的参数可能有三种情况:当msgtyp=0时,核心寻找消息队列中的第一个消息,并将它返回给调用进程;当msgtyp为正整数时,核心返回指定类型的第一个消息;当msgtyp为负整数时,核心应在其类型值小于或等于msgtyp绝对值的所有消息中,选出类型值最低的第一个消息返回。如果所返回消息的大小等于或小于用户的请求,核心便将消息正文拷贝到用户区,再从队列中删除该消息,并唤醒睡眠的发送进程;如果消息长度比用户要求的大,则系统返回出错信息。用户也可忽略对消息大小的限制,此时,核心无需理会消息的大小而一概把消息内容拷贝到用户区。
四.共享存储区机制
1.共享存储区
共享存储区(Shared Memory)机制是UNIX系统中通信速度最高的一种通信机制。该机制一方面可使若干进程共享主存中的某个区域,且使该区域出现在多个进程的虚地址空间中。另一方面,在一个进程的虚地址空间中又可连接多个共享存储区,每个共享存储区都有自己的名字。当进程间欲利用共享存储区进行通信时,须首先在主存中建立一个共享存储区,然后将该区附接到自己的虚地址空间上。此后,进程之间便可通过对共享存储区中数据的读和写来实现直接通信。图 10-7 中示出了两个进程通过共享一个存储区进行通信的例子。其中,进程A将建立的共享存储区附到自己的AA′区域,进程B则将它附接到自己的BB' 区域。
2.共享存储区的建立与操纵
1) 共享存储区的建立
当进程要利用共享存储区与另一进程进行通信时,须先利用系统调用shmget( )建立一块共享存储区,并提供该共享存储区的名字key和共享存储区以字节为单位的长度size等参数。若系统中已经建立了指名的共享存储区,则该系统调用将返回该共享存储区的描述符shmid;若尚未建立,便为进程建立一个指定大小的共享存储区。
2) 共享存储区的操纵
如同消息机制一样,可以用shmctl( )系统调用对共享存储区的状态信息进行查询,如其长度、所连接的进程数、创建者标识符等;也可设置或修改其属性,如共享存储区的许可权、当前连接的进程计数等;还可用来对共享存储区加锁或解锁,以及修改共享存储区标识符等。
3.共享存储区的附接与断开
在进程已经建立了共享存储区或已获得了其描述符后,还须利用系统调用shmat( )将该共享存储区附接到用户给定的某个进程的虚地址shmaddr上,并指定该存储区的访问属性,即指明该区是只读,还是可读可写。此后,该共享存储区便成为该进程虚地址空间的一部分。进程可采取与对其它虚地址空间一样的存取方法来访问。当进程不再需要该共享存储区时,再利用系统调用shmdt( )把该区与进程断开。
五.信号量集机制
1.信号量与信号量集
1) 信号量
在UNIX系统中规定,每个信号量有一个可用来表示某类资源数目的信号量值和一个操作值,该操作值可为正整数、零或负整数三种情况之一。传统的信号量机构是对信号量施加wait及signal操作。而在UNIX系统中则并未采用wait及signal,而是利用semop( )系统调用对指定的信号量施加操作。此外,还可利用semget( )来建立信号量及利用semctl( )系统调用对信号量进行操纵。
2) 信号量集
在一个信号量集中,通常都包含有若干个信号量。对这组信号量的操作方式应当是原子操作方式,此即,把对这组信号量视为一个整体,要么全做,要么全不做。如果核心不能完成对这组所有信号量的操作,则核心应将已经操作过的信号量恢复到操作前的状态,这样便可实现要么全做、要么全不做的原子操作方式。
2.信号量集的数据结构
1) 信号量表
信号量表是信号量的结构数组。在系统Ⅴ中,每个信号量用一个信号量结构表示。其中,包括信号量值semval及最近一次对信号量进行操作的进程标识符sempid、等待该信号量值增加的进程数等。
2) 信号量集表
信号量集表是信号量集的索引结构数组,其中的每一个元素都对应于一个信号量集,其内容有:访问权限、指向信号量集中第一个信号量的指针、信号量集中信号量的数目、最近对信号量执行操作的时间。信号量表与信号量集表间的关系示于图10-8中。
3.系统调用
在信号量机制中,同样也提供了若干条系统调用,分别用于对信号量执行各种操作。
1) semget( )系统调用
用户可利用该系统调用来建立信号量集。用户应提供信号量的名字、信号量集中信号量的数目等。若信号量集的建立成功,将返回信号量集的描述符semid。
2) semop( )系统调用
该系统调用可用来对信号量集进行操作。用户需提供信号量集的描述符、信号量的编号,即信号量在信号量集中的序号,以及所要施加操作的操作数semop。内核根据semop来改变信号量的值。当semop为正值时,便将该正值加到信号量的值上。当semop为负值时, 若信号量的值大于semop的绝对值,应将该负值加到信号量值上;否则,操作失败,内核将已经操作过的信号量恢复到该系统调用开始执行时的值。