IPC概述

  • 进程间实现数据共享容易吗?

如果进程空间之间有可以共享的交叠空间的话,进程间可以通过这个交叠的空间,很容易的就能实现数据共享。但是实际情况是,每个进程的进程空间是完全独立的,进程空间没有任何的交叠,所以实现数据共享的难度很高。

  • 为什么进程空间是完全独立的?

每个进程的虚拟地址范围虽然都是相同的(由0~2^32)但是各个进程通过内存映射机制,映射一段独立的物理内存。所以进程间互相独立。

  • 让每个进程拥有独立进程空间的好处?

最关键的好处就是防止别人攻击(程序互相干扰)。如果病毒、木马被做成应用程序,那么它只能运行在OS所提供的独立的进程空间里面,在这种情况下,它还真是没办法去攻击别人,所以现在根本就不存在应用级的木马和病毒(应用程序形式的木马和病毒)。现在的木马和病毒如果真想搞事的话,只能是系统级的木马和病毒,就是木马和病毒能够直接攻击OS,以实现攻击的目的。

  • 独立进程空间的缺点?

数据共享困难。

  • 进程间通信的原理

OS作为所有进程共享的第三方,会提供相关的机制,以实现进程间数据的转发,达到数据共享的目的。

  • Linux提供的进程间通信方式有哪些
  1. 信号(非精确通信)
  2. 管道(有名、匿名)
  3. system V IPC(消息队列、共享内存、信号量)
  4. socket

匿名管道

  • 通信原理

在内核中开辟一段缓冲区,通信的进程通过共享这个缓冲区实现通信。

  • 为什么叫无名管道?

作为共享介质的内核缓冲区没有名字,可以看做是一个匿名的文件。

  • 如果管道是匿名的,那还如何被open?

由于没有文件名,因此进程没办法使用open打开管道文件,所以就只能让父进程先调用pipe创建管道,并得到读写管道的文件描述符,然后再fork出子进程,让子进程通过继承父进程打开的文件描述符,父子进程就能操作同一个管道,从而实现通信。

  • 使用步骤
  1. 父进程在fork之前先调用pipe创建无名管道,并获取读、写文件描述符;
  2. fork创建出子进程,子进程继承无名管道读、写文件描述符;
  3. 父子进程使用各自管道的读写文件描述符进行读写操作,即可实现通信;
  4. 为了避免干扰,我们通常会把没有使用的文件描述关闭。
  • SIGPIPE信号

写管道时,如果管道所有的读端的fd都被close的话,向管道write数据的进程会被内核发送一个SIGPIPE信号,发这个信号的目的就是想告知“管道所有的read通道都被关闭了”。由于这个信号的默认动作是终止,所以收到这个信号的进程会被终止。

  • 单个无名管道为什么无法实现双向通信?

因为自己发送给对方的数据,会被自己给抢读到。比如生产-消费者模型,如果通信双方既是生产者也是消费者,那么就无法判断缓冲区的数据该由谁来消费。

  • 如何使用无名管道实现双向通信?

一个不行,就用俩。一个无名管道用于读,一个无名管道用于写。

  • 匿名管道的两个主要缺点:
  1. 近亲。无法用于非亲缘关系的进程
  2. 单向。无法实现多对多的通信

命名管道(FIFO)

  • 本质是一个普通文件,在文件系统中可见。
  • 同样也会收到SIGPIPE,当一方收到SIGPIPE后,应当关闭FIFO文件。
  • 使用步骤:
  1. 进程调用mkfifo创建命名管道
  2. open打开有名管道
  3. read/write读写管道进行通信
    注意:(为保证管道一定被创建,两个进程最好都创建管道,谁先运行就谁先创建,后运行的发现管道已存在,则直接open打开使用)

System V IPC

  • 什么是System V IPC

前面讲的无名管道和有名管道,都是UNIX系统早期提供的比较原始的一种进程间通信(IPC)方式,早到Unix系统设计之初就有了。在Unix系统升级到第5版本时,提供了三种新的IPC通信方式,分别是:

  1. 消息队列
  2. 信号量
  3. 共享内存

后来的Linux也继承了unix的这三个通信方式。

System V IPC不再以文件的形式存在,因此没有文件描述符这个东西(故不能read/write),但是它有自己专用的“描述符”。

  • System V IPC对象的特点:
  1. 独立于进程而存在,即进程关闭后,IPC对象仍存在。
  2. 如何查看IPC对象?使用ipcs命令即可查看。
  3. 如何删除IPC对象?①重启OS、②系统API、③ipcs命令。
  4. 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的操作顺序,绝对不能出现这操作顺序以外的顺序。

  • 思考:既然信号量是一种加锁机制,那为什么会被归到了进程间通信里面?

资源保护时,某个进程的操作没有完全完成之前,别人是不能操作的,那么进程间必须相互知道对方的操作状态,必须会涉及到通信过程。所以信号量实现资源保护的本质就是,通过通信让各个进程了解到操作状态以判断自己是否能够进行操作。