学习路线-后端-操作系统-进程间通信

进程间常见的通信方式:

  • 管道pipe
  • 命名管道FIFO
  • 消息队列MessageQueue
  • 共享内存ShareMemory
  • 信号量Semaphore
  • 套接字Socket
  • 信号sinal

管道

管道是一种半双工的通信方式,数据只能单向流动,而且之恶能在具有亲缘关系的进程间使用。进程的亲缘关系通常指的是父子进程关系。

管道允许在进程之间按先进先出的方式传送数据,是进程间通信的一种常见方式。
管道,通常指无名管道,是UNIX系统IPC最古老的形式。它具有如下特点:

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

当一个管道建立时,它会创建两个文件描述符:fd[0]为读而打开,fd[1]为写而打开。如下图:
学习路线-后端-操作系统-进程间通信
要关闭管道只需将这两个文件描述符关闭即可。
注:单个进程中的管道几乎没有任何用处。所以,通常调用 pipe 的进程接着调用 fork,这样就创建了父进程与子进程之间的 IPC 通道。如下图所示:

学习路线-后端-操作系统-进程间通信
若要数据流从父进程流向子进程,则关闭父进程的读端(fd[0])与子进程的写端(fd[1]);反之,则可以使数据流从子进程流向父进程。

命名管道

有名管道也是半双工的通信方式,但是他允许无亲缘关系的进程间的通信。

管道是命令管道(FIFO),除了建立、打开、删除的方式不同外,这两种管道几乎是一样的。命名管道的特点如下:
它是半双工的(即数据只能在一个方向上流动),具有固定的读端和写端。

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

消息队列

消息队列是由消息的链表,存放在内核中并由消息队列标识符标识。消息队列克服了信号传递信息少、管道只能承载无格式字节流以及缓冲区大小受限等缺点。

特点:

  • 消息队列是面向记录的,其中的消息具有特定的格式以及特定的优先级。
  • 消息队列独立于发送与接受进程。进程停止时,消息队列及其内容并不会删除。
  • 消息队列可以实现消息的随即查询,消息不一定要以先进先出的次序读写,也可以按消息的类型读取。

消息队列与管道通信相比,其优势就是对每个消息指定特定的消息类型,接受的时候不需要按照队列次序,而是可以根据自定义条件接受特定类型的消息。

可以把消息看作一个记录,具有特定格式以及特定的优先级。对消息队列有写权限的进程可以向消息队列中按照一定的规则添加新消息,对消息队列有读权限的进程可以从消息队列中读取消息。

函数 功能
fork 由于文件路径工程ID生成标准key
msgget 创建或打开消息队列
msgsnd 添加消息
msgrcv 读取消息
msgctl 控制消息队列

 

进程间通过消息队列通信,主要是:创建或打开消息队列,添加消息,读取消息和控制消息队列。

例如:用函数msgget创建消息队列,调用msgsnd函数,把输入的字符串添加到消息队列中,然年后调用msgrcv函数,读取消息队列中的消息并打印输出,最后在调用msgctl函数,删除系统内核中的消息队列。

共享内存

共享内存就是饮食一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问。共享内存是最快的IPC方式,他是针对其他进程间通信方式运行效率低而专门设计的。他往往与其它通信机制,如信号量配合使用,来实现进程间的同步和通信。

特点:

  • 共享内存是最快的IPC,因为进程是直接对内存进程存取
  • 因为多个进程可以同时操作,所以需要进行同步
  • 信号量+共享内存通常结合在一起使用,信号量用来同步对共享内存的访问

共享内存允许两个或多个进程共享一个给定的存储区,这一段存储区可以被两个或两个以上的进程映射到自身的地址空间中,一个进程写入共享内存的信息,可以被其他使用这个共享内存的进程,通过一个简单的内存读取读出,从而实现了进程间的通信。

采用共享内存仅从通信的一个主要的好处就是效率高,因为进程可以直接读写内存,而不需要任何数据的拷贝,对于像管道和消息队列里等通信方式,则需要在内核和用户空间进行四次的数据拷贝,而共享内存则只拷贝两次:一次从输入文件到共享内存区,另一个从共享内存到输出文件。

学习路线-后端-操作系统-进程间通信

一般而言,进程之间在共享内存时,并不总是读少量数据后就解除映射,有新的通信时再重新建立共享内存区域;而是保持功能共享,指导通信完毕为止,这样,数据内容一直保存在共享内存中,并没有写回文件。共享内存中的内容往往是在解除映射时才写回文件,英雌,采用共享内存的通信方式效率非常高。

函数 功能
mmap 建立共享内存映射
munmap 解除共享内存映射
shmget 获取共享内存区域的ID
shmat 建立映射共享内存
shmdt 解除共享内存映射

信号量

信号量是一各计数器,可以用来控制多个进程多共享资源的访问,它通常作为一种锁机制,防止某种进程正在访问共享资源时,其他进程也访问该资源。因此,主要作为进程间以及同一进程内不同线程之间的同步手段。

特点:

  • 信号量用于进程间同步,若要在进程间传递数据需要结合共享内存
  • 信号量基于操作系统的PV操作,程序对信号量的操作都是原子操作
  • 每次对信号量的PV操作不仅限于对信号量值+1或-1,而且可以加减任意正整数

套接字

套接字也是一种进程间通信机制,与其他通信方式不同的是,它可用于不同及其间的进程通信。

套接字通信的方式非常多,有Unix域套接字、TCP套接字、UDP套接字、链路层套接字等等。但最常用的肯定是TCP套接字。所以,这里介绍下TCP Socket通信方式。

TCP Socket用于客户端、服务端的基于TCP协议的通信,所以在客户端和服务端均需要创建一个套接字。创建TCP套接字时会返回这个套接字的文件描述符,可通过这个文件描述符对套接字进行读和写操作。

对比一下,当一个程序需要对一个磁盘文件同时进行读写操作(在命令行下似乎没有找到这种命令,但通过编程方式是很容易实现的)时,由于只通过单个文件描述符同时负责读和写,很可能需要通过不断移动文件指针的方式来改变读写的位置,否则数据很容易错乱。

而TCP套接字也是通过单个文件描述符进行读写套接字的,为了保证读和写的位置不错乱,操作系统在内核空间为每个TCP套接字维护了两个buffer空间,一个buffer用于写、一个buffer用于读。提供读的buffer空间称为recv buffer,提供写的buffer空间称为send buffer,它们统称为socket buffer。

所以,服务端和客户端通过两个套接字通信就简单了,一端向send buffer写数据,该buffer的数据会通过已经建立好的TCP连接发送到另一端的recv buffer,于是另一端只需从recv buffer中读数据即可实现不同计算机上的进程间通信。过程如图。

学习路线-后端-操作系统-进程间通信

信号

信号是一种比较复杂的通信方式,用于通知接受进程某个事件已经发生。

除了用于进程间通信之外,进程还可以发送信号给进程本身。除了系统内核和root之外,只有具备相同id的进程才可以信号进行通信。