进程通信IPC
进程通信
IPC
IPC(Inner-Process Communication 进程间通信)
进程之间协作完成任务,必然涉及到其进程间的通信
为什么要进程间通信?
多进程之间的空间是独立的,如果需要操作共享数据,就涉及到了通信;进程通信就是为了协作操作共享数据的(和进程同步的目的也差不多)
下面我们先从为什么需要通信开始讲起
进程的空间是完全独立的
先引入一个问题
OS为什么要将进程设计为独立的?
进程空间
1、虚拟内存
就是程序运行时提供的内存空间,进程运行的空间都是虚拟内存空间(此虚拟内存空间指,OS在物理内存上营造出来的空间)
2、虚拟地址
虚拟空间有自己的虚拟地址,并不和真实的物理地址一一对应
注意:CPU从PC取指令运行,PC存放的地址为虚拟地址,而不是真实地址
(之后会进行虚拟地址与真实地址的转换,将数据从真实地址中取出)
3、 进程空间中虚拟地址的编码范围
注意:每个进程空间中的虚拟地址范围都是一样的!
对于32位OS来说,虚拟内存的虚拟地址的编码范围为:
1 | 4G-1: 1111 1111...1111 1111 |
注意:虚拟地址范围如此大,但是对应到物理内存,只有几M而已!
实际上,OS对进程的虚拟地址做了限制,只使用了其中一部分
4、虚拟内存都一样,进程间会不会相互干扰?
不会,类似于一楼编号1-20、二楼编号1-20,,1楼对1楼的操作,不会影响到二楼(这也是虚拟内存的意义)
5、进程间相互独立空间的好处与坏处
好处:
可以保证安全,防止病毒的侵入(病毒也是一个进程,他和我的其他进程不共享数据,也就无法入侵)
缺点:
增加了共享信息的难度,所以提出了IPC
6、广义与狭义
广义上来说,只要能够实现进程间通信的交换方式就是进程间通信
比如:A进程 -> 文件 -> B进程
AB进程通过文件来交换数据、再比如AB进程通过数据库来进行数据交换
但是我们研究的都是OS来实现的狭义上的进程通信
管道
管道分为两种:无名管道、有名管道
无名管道
内核会开辟一个“管道”,通信的进程可以共享这个管道,从而实现通信
什么是管道?
其实管道就是OS内核在自己的物理内存空间开辟的一段缓存空间,并且提供对这块缓存区域对应的API,方便进程进行读写
如何操作管道?
通过文件描述符及相关write
、read
等IO函数对文件进行操作
(这里我们也能知道,其实管道也是一个文件,因为有文件描述符)
为什么要叫无名管道?
管道其实也是一个文件,但是这个文件比较特殊,因为它没有文件名,所以叫无名管道
无名管道的实现原理
函数原型:
1 |
|
功能就是创建一个用于有亲缘关系的进程通信的管道
(注意:管道只能满足亲缘关系进程通信,例如父子、父孙进程之间的通信!)
有两个参数:
- 元素
[0]
:放读管道的读文件描述符 - 元素
[1]
:放写管道的写文件描述符
(这里的读写文件描述符是独立的,即并不是open
函数得到的,创建无名管道时就会有它的读写文件描述符)
为什么无名管道只能用于亲缘进程间通信?
因为无名管道没有文件名,因此没有办法通过open
打开文件,得到文件描述符,所以只有一种办法:
- 就是父进程先调用
pipe
函数创建出管道,得到无名管道的文件描述符 - 然后
fork
出子进程,让子进程通过继承父进程打开文件描述符,从而父子间操作同一个管道,进行通信
注意:
- 管道创建失败返回 -1,成功返回一个大于0的数
- 读管道时,如果管道没有数据,那么读操作会休眠(即阻塞)
1 |
|
运行结果如下:
1 | [root@master learnIPC]# ./a.out |
无名管道是单向还是双向通信?
是单向通信的,只有一个写段,一个读端;
如果你用两个线程分别去读写,那么写线程会立即读到自己的写的内容
如何实现双向通信?
可以使用两个无名管道
无名管道的缺点
- 无法用于无亲缘关系的进程
- 不适合用于网状通信(因为会导致文件描述符变的异常复杂)(只适合单向双方通信)
有名管道
什么是有名管道
对比无名管道就是有了名字,即有了文件名的管道文件
这意味着我们可以通过名字open
来打开这个文件,得到其文件描述符
有名管道的特点
- 能够用于非亲缘进程之间的通信
- 读管道时,管道无数据,读操作会阻塞
- 当进程写一个所有读端都被关闭的管道,进程会被返回SIGPIPE信号(如果不想管道被该信号终止,要忽略这个信号)
有名管道的创建步骤
- 使用
mkfifo
创建有名管道 open
打开有名管道read/write
读写管道进行通信
函数原型
1 |
|
传入两个参数:
- pathname:文件路径
- mode:被创建的原始权限(一般为0664)必须包含读写权限
注意!有名管道也是单向通信,如果要实现双向通信,也需要使用两个有名管道
System V IPC提供了新的通信方式
什么是System V IPC?
管道是Unix提供的很原始的一种进程间通信方式,后来升级到版本5后,引入了新的进程通信方式:
- 消息队列
- 信号量
- 共享内存
所以System V IPC指的就是这三种通信方式
System V 的通信原理
与管道的通信方式不同(管道的通信方式类似于文件操作)
System V IPC不再以文件的形式存在,而是通过一个标识符(完全可以认为这个标识符就是文件描述符的替代品)
消息队列
消息队列是什么?
消息队列就是一个用于存放消息的双向链表
通信的进程通过共享操作同一个消息队列,就能实现通信
消息是如何存放在消息队列中?
链表的每一个结点就是一个消息,结点应该有两部分内容:
- 消息编号
- 消息正文
1 | struct msgbuf{ |
消息队列的作用
消息队列很好的实现了网状通信!
(具体的创建过程等内容,过于硬件,不再介绍)
共享内存
什么是共享内存?
共享内存就是OS在内存区域开辟的一大段内存空间
适合于什么场景?
适合于传输大量的数据(如果用管道或者消息队列,将会很慢)是最快的IPC方式
如果要让很多个进程共享同一个空间,要加保护措施
实现方式
- 调用API,让OS内核在物理内存开辟出一大段空间
- 让进程与开辟的缓存建立映射关系
共享内存与管道的区别:
- 管道小,共享内存大
- 管道慢(管道每次调用都要进行系统调用),共享内存快
- 管道不需要自己管理,其会自己阻塞;而共享内存需要我们自己管理
信号量
信号量是一个锁机制,为什么会归类到IPC内?
因为其进行锁的原理,就是通知各个进程什么时候该做什么,所以也算是一种通信方式(只不过其通信的内容仅仅只是事情发生了没有,而不是事情是什么)
Socket
socket也可以实现进程间通信(Socket另外单独来讲)
信号
什么是信号?
信号是一条小的消息,由内核或者其它进程生成并发送至目标进程,目标进程可以根据该信号来做出响应。
信号可以由进程或者内核发出,例如:
- 用户在Bash界面通过键盘对正在执行的进程输入
Ctrl+C
、Ctrl+\
等信号命令,或者执行kill
命令发送信号。 - 进程执行出错,例如访问了一个非法的地址、除0运算,或者硬件发生故障,就会由内核向进程发送一个信号。
- 进程执行
kill
命令向目标进程发送信号。
信号的传递步骤:
传送一个信号到目的进程主要有两个步骤:
- 发送信号:内核通过更新目标进程上下文的某个状态,传递一个信号给目标进程
- 接收信号:目标进程会被内核强制以某种方式对信号的发送做出反应,它就会接收到信号。(如果程序没有针对这种信号指定其处理方式,就会采用默认的处理策略,例如中止进程、忽略)
一个发出但没有接收的信号称之为待处理信号,在任何时刻,同一种类型的信号最多只会有一个待处理的信号
发送信号的方式
- 通过操作系统的
bin/kill
程序,向程序发送信号
例如:kill -9 pid
向进程发送信号值为9的信号
- 用键盘发出信号
在命令行界面中,能有一个前台任务和多个后台任务
kill
函数发送信号
kill函数的定义位于头文件signal.h
中:
1 | int kill(pid_t pid, int sig); |
alarm
函数发送SIGALRM
信号
进程可以通过alarm
函数向自己发送一个SIGALRM
信号,该函数定义于头文件unistd.h
中:
1 | unsigned int alarm(unsigned int secs); |