操作系统6————进程间通信IPC

操作系统6————进程间通信IPC

一.目录

二.进程通信概述

进程通信是指进程间的信息交换。在进程的互斥与同步中,也在进程之间交换了一些信息,因此不少学者也将他们归为低级进程通信。他们低级的原因在与:① 效率低 ② 通信对与用户不透明.

在进程间要传递大量数据是,应当利用OS提供的的高级通信工具,目前,常见的高级通信机制可归为四类:共享存储器,管道通信系统,消息传递系统,客户机-服务机系统.

三.共享存储器

1. 概述

相互通信的进程间共享某些数据结构或者共享存储去,进程之间通过他们进行通信,据此把他们分为两种类型:基于共享数据结构,基于共享存储区。

2. 共享某些数据结构

在这种通信方式下,要就诸进程公用某些数据结构,借此来实现诸进程间的信息交换。数据结构的设置和同步处理都是由用户完成的吗,不透明,效率低,仅用于传递少量数据.

3. 共享存储区

这周通信方式下,进程向系统申请共享存储区的一个分区,并指定该分区的关键字,并将该分区连接到本进程对其进行写。另一进程可根据关键字将该分区连接到自己之上对其进行读写,通过同一分区的读写,实现两进程间的信息交换。

四.管道通信

1.概述

管道也称共享文件方式,基于文件系统,利用打开的共享文件连接两个相互通信的进程,文件作为缓冲传输介质。

管道就是用于连接读进程和写进程的共享文件,写进程想管道发送字符流。读进程从管道中接受字符流。
这里写图片描述

2. 管道机制必须提供的协调能力

  • 互斥:当一个进程正在等待pipe进行读写操作时,另一个进程必须等待。
  • 同步:当写进程把一定数量的数据写入pipe后,便去睡眠等待,直到读进程取走数据后,再把它唤醒。当读进程读到一空pipe时,也应睡眠等待,直至写进程将数据写入管道后,才将它唤醒。
  • 对方是否存在:只有确定对方存在是,才能进行通信。

3. Linux中的无名管道

a.特点:

  • 他是半双工 (即数据只能在一个方向上流动),具有固定的写端和读端
  • 它只能用于具有亲缘关系(父子进程或者兄弟进程)的进程之间的通信
  • 它可以看成是一种特殊的文件,对于它的读写也可以使用普通的read、write 等函数。但是它不是普通的文件,并不属于其他任何文件系统,并且只存在于内存中

b.原型:

  • int pipe(int fd[2]); //// 返回值:若成功返回0,失败返回-1
  • 当一个管道建立时,它会创建两个文件描述符:fd[0]为读而打开,fd[1]为写而打开。如下图:
  • 这里写图片描述
  • 关闭管道只需要关闭两个文件描述符即可。

c.例子

  • 如果想要数据从父进程流向子进程,则关闭父进程的读端(fd[0])于子进程的写端反之可以使数据从子进程流向父进程
#include<stdio.h>
#include<unistd.h>

int main()
{
    int fd[2];  // 两个文件描述符
    pid_t pid;
    char buff[20];

    if(pipe(fd) < 0)  // 创建管道
        printf("Create Pipe Error!\n");

    if((pid = fork()) < 0)  // 创建子进程
        printf("Fork Error!\n");
    else if(pid > 0)  // 父进程
    {
        close(fd[0]); // 关闭读端
        write(fd[1], "hello world\n", 12);
    }
    else
    {
        close(fd[1]); // 关闭写端
        read(fd[0], buff, 20);
        printf("%s", buff);
    }

    return 0;
}

4. Linux中的命名管道(FIFO)

a.特点:

  • FIFO可以在无关进程直接交换数据
  • FIFO有路径名与之相关联,它以一种特殊设备文件形式存在于文件系统。

b.原型:

  • int mkfifo( const char * pathname,mode_t mode);//成功返回0,出错返回-1
  • 其中的mode参数与open函数中的mode相同。一但创建一个FIFO,就可以用一般文件的I/O函数操作它。
  • 如果open一个FIFO时,没有指定O_NONBLOCK(默认),只读open要阻塞到某个其他进程为写打开次FIFO,类似的,只写open要阻塞到其他进程为读而打开它。
  • 如果open一个FIFO时,指定了O_NONELOCK,则只读open立即返回。而只写open将出错返回-1。如果没有进程为读而打开该FIFO,其errno置ENXIO。

c.例子
FIFO的通信方式类似于在进程中使用文件来传输数据,只不过FIFO类型文件同时具有管道的特性。在数据读出时,FIFO管道中同时清除数据,并且“先进先出”。下面的例子演示了使用 FIFO 进行 IPC 的过程:
write_fifo.c

#include<stdio.h>
#include<stdlib.h>   // exit
#include<fcntl.h>    // O_WRONLY
#include<sys/stat.h>
#include<time.h>     // time

int main()
{
    int fd;
    int n, i;
    char buf[1024];
    time_t tp;

    printf("I am %d process.\n", getpid()); // 说明进程ID

    if((fd = open("fifo1", O_WRONLY)) < 0) // 以写打开一个FIFO 
    {
        perror("Open FIFO Failed");
        exit(1);
    }

    for(i=0; i<10; ++i)
    {
        time(&tp);  // 取系统当前时间
        n=sprintf(buf,"Process %d's time is %s",getpid(),ctime(&tp));
        printf("Send message: %s", buf); // 打印
        if(write(fd, buf, n+1) < 0)  // 写入到FIFO中
        {
            perror("Write FIFO Failed");
            close(fd);
            exit(1);
        }
        sleep(1);  // 休眠1秒
    }

    close(fd);  // 关闭FIFO文件
    return 0;
}

read_fifo.c

#include<stdio.h>
#include<stdlib.h>
#include<errno.h>
#include<fcntl.h>
#include<sys/stat.h>

int main()
{
    int fd;
    int len;
    char buf[1024];

    if(mkfifo("fifo1", 0666) < 0 && errno!=EEXIST) // 创建FIFO管道
        perror("Create FIFO Failed");

    if((fd = open("fifo1", O_RDONLY)) < 0)  // 以读打开FIFO
    {
        perror("Open FIFO Failed");
        exit(1);
    }

    while((len = read(fd, buf, 1024)) > 0) // 读取FIFO管道
        printf("Read message: %s", buf);

    close(fd);  // 关闭FIFO文件
    return 0;
}

五.消息传递机制

1. 概述

在该机制中,以格式化的消息(message)为通信单位;利用系统为进程提供的两个高级通信原语send和received进行通信,隐藏看通讯的实现细节,对用户是透明的,使用非常方便,是使用最广泛的进程通信机制。

send: 但要进行消息传递时,执行send
receive:当接收者要接收消息是执行receive。

根据实现方式不同,可进一步将他们分为两种:

  • 直接通信方式:是指发送进程利用OS提供的发送原语,直接把消息发送给目标进程
  • 间接通信方式:是指发送和接收进程,都通过共享中间实体(邮箱)的方式进行消息的发送和接收,完成进程间的通信。

2.直接消息传递系统的实现方式

a.直接通信原语
直接信息传递系统分为两种。

对称寻址方式,这种方式下,发送进程和进程都要求以显式方式提供对方的的标识符,通常,系统提供的原语如下
send(receiver, message);发送一个消息给接受进程
receive(sender,message);接受发送进程的消息

非对称寻址方式,这种方式中,接受进程的原语中,并不需要命名发送进程。只填写表示源进程参数。原语如下:
send(P,message);
recrive(id,message);
b.消息的格式
消息的格式常用的就是较短的定长消息格式,和较长的可变的消息格式。定长的消息格式处理简单,存储空间开销少。可变的消息格式方便用户的使用,但是处理和存储空间需要更多的更大的开销.
c.通路链路
为了在发送进程和接受进程自己能进行通信,必须在两者之间建立一条通信链路。

有两种方式建立通信链路:
第一种:由发送进程在通信之前用显示的”通信连接”命令请求系统为之建立一条通信链路。在链路使用完后拆除链路。(主要用于计算机网络)
第二种:发送进程无需明确提出建立链路的请求,只需要利用系统提供的发送命令原因,系统会自动为之建立一条链路。

3.信箱通信的实现方式

信箱通信属于间接的通信方式,即进程之间的通信,通过某种实体来完成。接受进程可以从该实体中取出发送进程的消息,通常我们把这份实体成为信箱(邮箱),每一个信箱都有唯一一个标识符。
a.信箱的结构
信箱定义为一种数据结构,通常从逻辑上分为两部分。

信息头:存放有关信箱的描述信息,如信箱标识符,信箱的拥有者,信箱的口令,信箱的空格数。
信箱体:由若干个可以存放消息(或者消息头)的信箱个。

b.信箱通信原语
系统为信箱通信提供了若干个原语:

信箱的创建和取消。进程可以利用信箱创建原语来建立一个新信箱,创建者进程应该给出信箱的名字,信箱的属性;对于共享信箱还应该给出共享者。当进程不再需要信箱时,可以利用撤销原语撤销。

消息的发送和接受
send(mainbox,message); //将一个信息发送到指定的信箱中
receive(mainbox,message);//从指定的信箱中取出信息
c.信箱的类型
信箱可由操作系统创建,也可以由用户进程创建,创建者是信箱的拥有者。据此,可以将信箱分为以下三类:

  • 私用信箱.用户进程为自己创建一个新信箱,并作为该进程的一部分,信箱的拥有者有权从信箱中读取消息,其他用户则只能将自己构建的信息发送到该信箱。当拥有该信箱的进程结束时,信箱也将消失。
  • 公用信箱。由操作系统创建,并提供给系统中所以核准进程使用,核准进程既可以把信息发送到该信箱中,也可以从信箱中读取发送给自己的信息。
  • 共享信箱。由某进程创建,在创建时指明它可以共享的。同时指出共享者的名字。邮箱的拥有者和共享者都可以从信箱中取走发送给自己的信息

4.直接消息传递系统实例

a.数据结构
在消息缓冲队列通信方式中,主要利用的数据结构是消息缓冲区
描述如下:

typedef struct message_buffer{
    int sender;  //发送者进程标识符
    int size;  //消息长度
    char *text; /消息正文
    struct message_buffer *next; //指向下一个消息缓冲区的指针
}

在PCB有关通信的数据项中。加入以下项

typedef struct processcontrol_block{
    ···
    struct message_buffer *mq;//消息队列首指针.
    semaphore muter; //消息队列互斥量
    semaohore sm;   //消息队列资源信号量 
    ···
}PCB

b.发送原语
发送原语描述如下:

void send(receiver,a){  //receiver为接受进程的标识符,a为发送区间首地址
    getbuf(a.size,i); //根据a.size申请缓冲区。
    i.sender = a.sender;
    i.size = a.size;
    copy(i.text,a.text);//将发送区a的信息复制到消息缓冲区i中
    i.next=0;
    getid(PCBset,receiver.j);//获得接受进程内部的标识符;
    wait(j.mutex);
    insert(&j.mq,i);//将小修缓冲区插入消息队列中
    signal(j.mutex);
    a=signal(j.sm);
}

c.接收原语
接受原语描述如下:

void receive(b){
    j = internal name;//j为接收进程的标识符。
    wait(j.sm);
    wait(j.mutex);
    removce(j.mq,i); //将消息队列中第一个消息移出。
    signel(j.mutex);
    b.sender = i.sender;
    b.size = i.size;
    cope(b.tezt,i.text); //将消息缓存区i中的信息复制到b中
    releasebuf(i);
}

六.客户机-服务机系统

前面所述的各种技术,虽然也实现了不同计算机间进程的通信,但客户机——服务器的通信机制,在网络环境的各种应用已成为当前主流,其主要的实现方式分为三类:套接字(Scoket),远程过程调用和远程方法调用。

1. 套接字Scoket

一个套接字就是有关通信标识类型的数据结构,包含了通信目的地,通信使用的端口号,通信网络的传输层协议,进程所在的网络地址,以及针对客户或者服务器所提供的不同的系统调用。通常套接字包括两类:

  • 基于文件性:通信进程都运行在一台机器的环境中,套接字是基于本地文件系统的支持,一个套接字关联到一个特殊的文件,双方文件基于这个特殊文件进行读写。
  • 基于网络型:这种类型通常采用非对称方法通信,即发送者需要提供接收者的命名。通信双方的进程运行的在不同主机的网络环境下,被分配了一对套接字。

套接字的优势在于,它不仅适用于同一台计算机内部的进程通信,也适用于网络环境中不同计算机间的进程通信。

2. 远程过程调用和远程方法调用

远程过程调用,是一个通信协议,用于通过网络连接的系统。该协议允许运行一台主机系统上的进程调用另一台主机系统上的进程,而对程序员表现为常规的进程调用,无需为此额外编程。

七.参考资料

《操作系统 第四版》
进程间通信(IPC)介绍

猜你喜欢

转载自blog.csdn.net/qq_38499859/article/details/80425044