【Linux】进程间通信
一、进程间通信介绍
什么是进程间通信
进程间通信目的
如何实现进程间通信
进程间通信分类
管道
System V IPC
POSIX IPC
二、管道
什么是管道
匿名管道
匿名管道的概念
匿名管道的接口
匿名管道的创建
匿名管道读写规则
匿名管道的应用场景
命名管道
创建一个命名管道
匿名管道与命名管道的区别
命名管道的打开规则
使用命名管道实现server&&client通信
System V共享内存
共享内存的原理
共享内存的数据结构
共享内存函数
ftok函数
创建共享内存—shmget函数
共享内存的释放—指令或shmctl函数
关联共享内存—shmat函数
共享内存的去关联—shmdt函数
使用共享内存实现server&&client通信
system V消息队列
system V信号量
一、进程间通信介绍
什么是进程间通信
进程间通信(IPC,InterProcess Communication)是两个或者多个进程实现数据层面的交互。日常生活中,一个大型的应用系统往往需要众多进程协作进行,进程通过与内核及其他进程之间的互相通信来协调它们的行为。但是进程间通信是有成本的,因为进程独立性的存在,导致进程通信的成本较高。
进程间通信目的
- 数据传输:一个进程需要将它的数据发送给另一个进程
- 资源共享:多个进程之间共享同样的资源。
- 通知事件:一个进程需要向另一个或一组进程发送消息,通知它(它们)发生了某种事件(如进程终止时要通知父进程)。
- 进程控制:有些进程希望完全控制另一个进程的执行(如Debug进程),此时控制进程希望能够拦截另一个进程的所有陷入和异常,并能够及时知道它的状态改变。
如何实现进程间通信
a.进程间通信的本质:必须让不同的进程看到同一份"资源"。
- 进程间的独立性确实使得它们之间的数据交互变得相对困难。其中独立性主要体现在数据层面,即使是我们的父子进程,他们之间虽然代码是共享的但是数据还是私有的。每个进程都有自己的地址空间和资源,因此一个进程无法直接访问另一个进程的数据或资源。
- 因为进程是具有独立性的,那么两个进程要实现互相通信就必须得先看到一份公共的资源!!
b."资源":特定形式的内存空间
这里的资源其实就是一段内存。它可能以文件方式提供,也可能以队列的方式也有可能提供的就是原始的内存块。这也就是进程通信方式有很多种的原因。
c. 这个"资源"谁提供? 一般是操作系统
为什么不是我们两个进程中的一个呢? 假设一个进程提供,这个资源属于谁? 这个进程独有,破坏进程独立性。第三方空间。
d. 我们进程访问这个空间,进行通信,本质就是访问操作系统!
进程代表的就是用户,“资源”从创建,使用(一般), 释放都要使用系统调用接口!!
- 从底层设计,从接口设计,都要由操作系统独立设计
- 一般操作系统,会有一个独立的通信模块 —— 隶属于文件系统 —— IPC通信模块
- 定制标准进程间通信是有标准的 —— system V && posix
进程间通信分类
管道
- 匿名管道pipe
- 命名管道
System V IPC
- System V 消息队列
- System V 共享内存
- System V 信号量
POSIX IPC
- 消息队列
- 共享内存
- 信号量
- 互斥量
- 条件变量
- 读写锁
二、管道
什么是管道
- 管道是Unix中最古老的进程间通信的形式。
- 我们把从一个进程连接到另一个进程的一个数据流称为一个“管道”。
举例:who | wc -l
说明:其中who和wc是我们的两个进程,who进程通过标准输出将数据放到管道中,wc通过标准输入从管道中读取数据,如此一来便实现了进程间通信。
注意:who命令用于查看当前云服务器的登录用户(一行显示一个用户),wc-l用于统计当前的行数。
匿名管道
匿名管道的概念
- 我们在上面说过,要想让两个进程之间进行通信,前提是必须得让他们看到一份公共的资源。而我们这里的匿名管道就是其中的一种公共资源。 匿名管道用于进程间通信,使用匿名管道进行通信的进程之间必须具有亲缘关系(如父子,兄弟,爷孙关系),常用于父子之间通信。
- 匿名管道是一种单向的通信通道。通过匿名管道进行通信时,一个进程可以将数据写入管道的一端,另一个进程可以从管道的另一端读取数据。这种通信方式非常适合于父子进程之间的通信,其中父进程可以创建一个子进程,并将数据通过匿名管道传递给子进程。
匿名管道的接口
我们常用pipe函数来创建匿名管道,我们用man来看一下pipe函数:
pipe函数的用法如下:
#include 功能:创建一无名管道 原型 int pipe(int fd[2]); 参数 fd:文件描述符数组,其中fd[0]表示读端, fd[1]表示写端 返回值:成功返回0,失败返回错误代码
匿名管道的创建
匿名管道的创建原理如下图所示:
学习文件描述符的时候,我们知道一个进程的0,1,2文件描述符是会分配给标准输入、标准输出与标准错误的。
所以按照文件描述符的分配规则,操作系统为管道文件分配的两个文件描述符应该是3和4。
我们用一段代码来验证一下:
可以看到OS为管道文件分配的两个文件描述符确实是3和4。
了解了上面的匿名管道的创建原理之后,下面我们来看父子进程通过匿名管道通信的原理
在创建匿名管道实现父子进程间通信的过程中,需要pipe函数和fork函数搭配使用,用fork来共享管道原理。具体步骤如下(这里以父进程读,子进程写为例):
1. 父进程通过pipe函数创建管道
2.父进程通过调用fork函数创建子进程
3.fork之后父子进程各自关掉不用的文件描述符
如此一来父子进程便可以通过匿名管道建立了通信的通道。
下面我们再站在文件描述符角度-深度理解管道
父进程写,子进程读:
下面我们来用代码来实现一下父子进程通过匿名管道实现进程间通信:
#include #include #include #include #include //stdlib.h #include #include #include #define N 2 #define NUM 1024 using namespace std; // child void Writer(int wfd) { string s = "hello, I am child"; pid_t self = getpid(); int number = 0; char buffer[NUM]; while (true) { // 构建发送字符串 buffer[0] = 0; // 字符串清空, 只是为了提醒阅读代码的人,我把这个数组当做字符串了 snprintf(buffer, sizeof(buffer), "%s-%d-%d", s.c_str(), self, number++); cout