IPC概述
- 进程间实现数据共享容易吗?
如果进程空间之间有可以共享的交叠空间的话,进程间可以通过这个交叠的空间,很容易的就能实现数据共享。但是实际情况是,每个进程的进程空间是完全独立的,进程空间没有任何的交叠,所以实现数据共享的难度很高。
- 为什么进程空间是完全独立的?
每个进程的虚拟地址范围虽然都是相同的(由0~2^32)但是各个进程通过内存映射机制,映射一段独立的物理内存。所以进程间互相独立。
- 让每个进程拥有独立进程空间的好处?
最关键的好处就是防止别人攻击(程序互相干扰)。如果病毒、木马被做成应用程序,那么它只能运行在OS所提供的独立的进程空间里面,在这种情况下,它还真是没办法去攻击别人,所以现在根本就不存在应用级的木马和病毒(应用程序形式的木马和病毒)。现在的木马和病毒如果真想搞事的话,只能是系统级的木马和病毒,就是木马和病毒能够直接攻击OS,以实现攻击的目的。
- 独立进程空间的缺点?
数据共享困难。
- 进程间通信的原理
OS作为所有进程共享的第三方,会提供相关的机制,以实现进程间数据的转发,达到数据共享的目的。
- Linux提供的进程间通信方式有哪些
- 信号(非精确通信)
- 管道(有名、匿名)
- system V IPC(消息队列、共享内存、信号量)
- socket
匿名管道
- 通信原理
在内核中开辟一段缓冲区,通信的进程通过共享这个缓冲区实现通信。
- 为什么叫无名管道?
作为共享介质的内核缓冲区没有名字,可以看做是一个匿名的文件。
- 如果管道是匿名的,那还如何被open?
由于没有文件名,因此进程没办法使用open打开管道文件,所以就只能让父进程先调用pipe创建管道,并得到读写管道的文件描述符,然后再fork出子进程,让子进程通过继承父进程打开的文件描述符,父子进程就能操作同一个管道,从而实现通信。
- 使用步骤
- 父进程在fork之前先调用pipe创建无名管道,并获取读、写文件描述符;
- fork创建出子进程,子进程继承无名管道读、写文件描述符;
- 父子进程使用各自管道的读写文件描述符进行读写操作,即可实现通信;
- 为了避免干扰,我们通常会把没有使用的文件描述关闭。
- SIGPIPE信号
写管道时,如果管道所有的读端的fd都被close的话,向管道write数据的进程会被内核发送一个SIGPIPE信号,发这个信号的目的就是想告知“管道所有的read通道都被关闭了”。由于这个信号的默认动作是终止,所以收到这个信号的进程会被终止。
- 单个无名管道为什么无法实现双向通信?
因为自己发送给对方的数据,会被自己给抢读到。比如生产-消费者模型,如果通信双方既是生产者也是消费者,那么就无法判断缓冲区的数据该由谁来消费。
- 如何使用无名管道实现双向通信?
一个不行,就用俩。一个无名管道用于读,一个无名管道用于写。
- 匿名管道的两个主要缺点:
- 近亲。无法用于非亲缘关系的进程
- 单向。无法实现多对多的通信
命名管道(FIFO)
- 本质是一个普通文件,在文件系统中可见。
- 同样也会收到SIGPIPE,当一方收到SIGPIPE后,应当关闭FIFO文件。
- 使用步骤:
- 进程调用mkfifo创建命名管道
- open打开有名管道
- read/write读写管道进行通信
注意:(为保证管道一定被创建,两个进程最好都创建管道,谁先运行就谁先创建,后运行的发现管道已存在,则直接open打开使用)
System V IPC
- 什么是System V IPC
前面讲的无名管道和有名管道,都是UNIX系统早期提供的比较原始的一种进程间通信(IPC)方式,早到Unix系统设计之初就有了。在Unix系统升级到第5版本时,提供了三种新的IPC通信方式,分别是:
- 消息队列
- 信号量
- 共享内存
后来的Linux也继承了unix的这三个通信方式。
System V IPC不再以文件的形式存在,因此没有文件描述符这个东西(故不能read/write),但是它有自己专用的“描述符”。
- System V IPC对象的特点:
- 独立于进程而存在,即进程关闭后,IPC对象仍存在。
- 如何查看IPC对象?使用ipcs命令即可查看。
- 如何删除IPC对象?①重启OS、②系统API、③ipcs命令。
- IPC对象的标识符皆可用ftok()来生成。
消息队列
- 本质
由内核创建的用于存放消息的链表,由于是存放消息的,所以就把这个链表称为了消息队列。
- 收发数据的过程
a)发送消息
1° 封装一个消息包。这个消息包其实就是如下类型的一个结构体变量,封包时将消息编号和消息正文写到结构体的成员中。
struct msgbuf { long mtype; /* 消息编号,必须>0*/ char mtext[msgsz]; /* 消息内容*/ };
2° 调用相应的API发送消息。调用API时通过消息队列的“标识符”找到对应的消息队列,然后将消息包发送给消息队列,消息包(存放消息的结构体变量)会被作为一个链表节点插入链表。
b)接收消息,通过“消息队列标识符”+“消息的编号”确定一条消息的身份。“消息队列”有点像信息公告牌,发送信息的人把某编号的消息挂到公告牌上,接收消息的人自己到公告牌上去取对应编号的消息,如此,发送者和接受者之间就实现了通信。
- 优点
易于实现网状交叉通信。异步化、解耦、削峰。
共享内存
- 原理:
让OS在物理内存上开辟出一大段缓存空间。让各自进程空间与开辟出的缓存空间建立映射关系。
- 与管道、消息队列的比较
对于小数据量的通信来说,使用管道和消息队列这种使用API读写的通信方式很合适,但是如果进程涉及到超大量的数据通信时,必须使用“共享内存”这种直接使用地址操作的通信方式,因为如果频繁使用系统API来读写的话,效率会非常的低。而共享内存仅在建立共享内存时需要系统调用,建立完成后,所有的访问都被处理为常规的内存访问,不再需要内核的帮助,故效率最高。
- 两个特有API:
shmat()将共享内存连接到进程的虚拟空间中。
shmdt()将共享内存从进程的虚拟空间中分离。
信号量
- 互斥
对于互斥操作来说,多进程共享操作时,多个进程间不关心操作的先后顺序,它们只希望自己在操作时别人不能操作。就算当前正在操作的进程它的时间片到了,切换到了其它进程上,但是当该进程检测到上一个进程还没有操作完时,该进程在当前的时间片内会休眠,直到再次切换会上一个进程,将操作完成后再切换回来,此时才能进行操作。
- 同步
同步其实本身就包含了互斥,不过同步不仅仅只互斥,同步对于谁先操作、谁后操作的先后顺序有要求,比如规定A->B->C的操作顺序,绝对不能出现这操作顺序以外的顺序。
- 思考:既然信号量是一种加锁机制,那为什么会被归到了进程间通信里面?
资源保护时,某个进程的操作没有完全完成之前,别人是不能操作的,那么进程间必须相互知道对方的操作状态,必须会涉及到通信过程。所以信号量实现资源保护的本质就是,通过通信让各个进程了解到操作状态以判断自己是否能够进行操作。