Cypress 2020-01-25
进程通信
进程间的通信分为控制信息的传送和大量信息的传送两种,控制信息的传送称为低级通信,大批量数据的传送称为高级通信。
高级通信分为共享存储系统、消息传递系统和管道通信系统。
共享存储系器系统
Linux共享存储区通信的实现
1).共享存储区的建立:当进程要利用共享存储区与另一个进程进行通信时,必须先让系统调用shmget()建立一块共享存储区。
2).共享存储区的操纵:系统可以调用shmctl()对共享存储区的状态信息进行查询,如长度、所连接的进程数、创建者标识符等;也可设置或修改其属性,如共享存储区的许可权、当前连接的进程计数等;还可以对共享存储区进程加锁和解锁,以及修改共享存储区的标识符等。
3).共享存储区的附接与断开:当进程已经建立了共享存储区或已获得了去描述符后,还要利用系统调用shmat()将该共享存储区附接到用户给定的某个进程的虚地址shmaddr上,并指定该存储区的访问属性。此后,该共享存储区便成为该进程虚地址空间的一部分。当进程不再需要该共享存储区时,就利用shmdt()将该区与本进程断开。
消息传递系统
消息传递系统中,进程间的数据交换是以格式化的消息(Message)为单位的,程序员可直接利用系统提供的一组通信命令直接进行通信,操作系统隐藏了实现通信的细节。因实现方式的不同分为直接通信和间接通信。
直接通信方式
这种通信固定在一组进程间进行。如A只发生给B,B只接收A的信息。系统提供了两条原语send和receive用来发送和接收消息。
send(B,message); receive(A,message);
间接通信方式
间接通信又称为信箱通信,信箱是一种数据结构,分为信箱头和信箱体,信箱头包含了信箱体的结构信息,例如多进程共享信箱体时的同步互斥信息。信箱体由多个格子组成,它实际上就是一个有界缓冲池,它的同步-互斥方式与生产者-消费者模式类似。信箱模式一般是进程间的双向通信。信箱的创建者即拥有者。信箱分为三类:
(1)私有信箱:又用户进程创建,并且作为进程的一部分。用户有权从信箱读取信息,其他进程只能向该信箱发送消息。进程结束后,信箱也随之撤销。
(2)公有信箱:由操作系统创建,所有受到核准的进程均可向信箱中发送和读取信息。信箱在系统运行期间一直存在,通常采用双向通信链路的信箱实现。
(3)共享信箱:由某进程创建,创建时必须指出可以共享信箱的进程的用户名或进程标识符。信箱的创建者和共享者都有权从信箱中取信息。
消息缓冲队列通信机制
消息缓冲队列是为了接收进程同时接收多个发送进程发送的消息而设立的。
(1)消息缓冲队列通信机制所用的数据结构主要是消息缓冲区,描述如下:
struct message { char sender[]; //发送进程标识符id int size; //消息长度 char text[]; //消息正文 queue *next; //消息队列的指针 }
在设置消息缓冲队列时,还应添加用于对消息队列进行操作和实现同步的信号量到进程的PCB中:
struct PCB { queue *mq; //消息队列首指针 semaphore mutex; //互斥信号量 semaphore sm; //消息队列资源信号量 }
在一个发送进程发送信息时,并形成了一个Message,并发送给指定的接收进程。接收进程将所有的消息缓冲区链接成一个队列,队首由PCB中的队首指针mq指出。
(2)发送原语。发送进程在发送消息之前,会先在自己的内存设置一个发送区,把待发送的消息正文,发送进程标识符,消息长度等信息填入其中,再调用发送原语发送消息。发送原语会首先根据发送取设置的消息长度来申请一个缓冲区i,再把发送区的消息复制到缓冲区中。为了能够将缓冲区的消息挂在接收进程的消息队列中,应先获得接收进程的内部标识符j,再将缓冲区i挂在j.mq上。因为该队列属于临界资源,所以在执行挂接操作时,需要执行wait和signal操作。发送原语描述如下:
void send(receiver,m) { //m表示消息缓冲区,即message getbuf(m.size,i); //申请缓冲区,标为i i.sender = m.sender; i.size = m.size; i.text = m.text; i.next = 0; getid(PCB of receiver,j); //获得接收者进程id //消息队列是临界资源,需要进行申请 wait(j.mutex); insert(j.mq,i); //将缓冲区i插入j.mq中 signal(j.mutex); signal(j.sm); }
(3)接收原语。接收进程调用接收原语,从消息队列中选取第一个消息缓冲区,并将消息复制到指定的消息接收区内。描述如下:
void receive(m) { j = internal name; //获得本进程的内部标识符id wait(j.sm); wait(j.mutex); //这两个wait的顺序同样不能更换 remove(j.mq,i); signal(j.mutex); //将i中的数据复制到m中 m.sender = i.sender; m.size = i.size; m.text = i.text; releasebuf(i); //释放i }
(4)Linux系统关于消息传递的相关系统调用。使用msgget(key,flag)系统调用申请消息,获得一个消息的描述符,该描述符指定一个消息队列以便用于其他系统调用。使用msgsnd(id,msgp,size,flag)系统调用发送一条消息。使用msgrcv(id,msgp,size,type,flag)接收一条消息。使用msgctl(id,cmd,buf)查询一条消息描述符状态,设置状态及删除一个消息描述符。
管道通信系统
管道是指用于连接一个读进程和一个写进程,以实现它们之间的通信的一个共享文件,又称为pipe文件。向管道提供输入的发送进程(写进程),以字符流的形式将大量的数据通过管道传输给另一端的接收进程(读进程)。这种方式首创于UNIX系统,在Linux系统及其他系统中得到了广泛应用。管道机制必须提供以下三方面的协调能力:
(1)互斥:当一个进程在对pipe执行读/写操作时,其它进程必须等待。
(2)同步:当写进程把一定数量的数据输入到pipe后便去睡眠等待,直到读进程将数据读出才将它唤醒。当读进程读到一个空的pipe时,也进入睡眠等待,直到写进程写入新的数据才将它唤醒。这样能够确保数据的同步,不会发生错乱。
(3)读进程和写进程互相确定对方是否存在,只要两个都存在才能进行数据的传输。
信号的概念
每个信号对应一个正整数常量,称为signal number,即信号编号。它定义在系统头文件<signal.h>中,代表同一用户的各进程之间传送事先约定的信息类型,用于统治某进程发生了异常事件。每个进程运行时都会根据信号机制检查是否有信号到达,若有,则会中断当前程序的运行,转到与该信号对应的处理程序,以完成对该事件的处理,处理完后再返回先前的断点继续执行。进程对于信号可以屏蔽,所有的信号都是平等的,且进程对于信号的响应通常有比较长的延迟。