问:进程间通信方式有哪些?

答:进程间通信的7中方式:

  • 管道/匿名管道(pipe)
  • 有名/命名管道(FIFO)
  • 信号(Signal)
  • 消息(Message)队列
  • 共享内存(share memory)
  • 信号量(semaphore)
  • 套接字(socket)

**

问:进程间通信的概念

答:每个进程各自有不同的用户地址空间,任何一个进程的全局变量在另一个进程中都看不到,所以进程之间要交换数据必须通过内核,在内核中开辟一块缓冲区,进程1把数据从用户空间拷到内核缓冲,进程2再从内核缓冲区把数据读走,内核提供的这种机制称为进程间通信(IPC,InterProcess Communication)。
image.png

管道

  • 管道是半双工的,数据只能向一个方向流动;需要双方通信时,需要建立起两个管道;
  • 一个进程向管道中写的内容被管道另一端的进程读出。写入的内容每次都添加在管道缓冲区的末尾,并且每次都是从缓冲区的头部读出数据;
  • 只能用于父子进程或者兄弟进程之间(具有亲缘关系的进程)

image.png
管道的实质:管道的实质是一个内核缓冲器,该缓冲区使用循环队列的数据结构实现,进程以 “先进先出”的方式从缓冲区存取数据。当缓冲区读空或者写满时,有一定的规则控制读进程或者写进程进入等待队列。

管道特点:

  • 只支持单向数据流;
  • 只能用于具有亲缘关系的进程之间;
  • 没有名字;
  • 管道的缓冲区存在内存中,是有限的;
  • 管道所传送的是无格式的字节流。

有名管道

匿名管道,由于没有名字,只能用于亲缘关系的进程间通信。为了克服这个缺点,提出了有名管道(FIFO)。
有名管道不同于匿名管道之处在于它提供了一个路径名与之关联,以有名管道的文件形式存在于文件系统中,这样,即使与有名管道的创建进程不存在亲缘关系的进程,只要可以访问该路径,就能够彼此通过有名管道相互通信,因此,通过有名管道不相关的进程也能交换数据。值的注意的是,有名管道严格遵循先进先出(first in first out),对匿名管道及有名管道的读总是从开始处返回数据,对它们的写则把数据添加到末尾。它们不支持诸如lseek()等文件定位操作。有名管道的名字存在于文件系统中,内容存放在内存中。
**


信号(Signal)

  • 信号是Linux系统中用于进程间互相通信或者操作的一种机制,信号可以在任何时候发给某一进程,而无需知道该进程的状态。
  • 如果该进程当前并未处于执行状态,则该信号就有内核保存起来,知道该进程回复执行并传递给它为止。
  • 如果一个信号被进程设置为阻塞,则该信号的传递被延迟,直到其阻塞被取消是才被传递给进程。

Linux系统中常用信号: (1)SIGHUP:用户从终端注销,所有已启动进程都将收到该进程。系统缺省状态下对该信号的处理是终止进程。 (2)SIGINT:程序终止信号。程序运行过程中,按Ctrl+C键将产生该信号。 (3)SIGQUIT:程序退出信号。程序运行过程中,按Ctrl+\\键将产生该信号。 (4)SIGBUS和SIGSEGV:进程访问非法地址。 (5)SIGFPE:运算中出现致命错误,如除零操作、数据溢出等。 (6)SIGKILL:用户终止进程执行信号。shell下执行kill -9发送该信号。 (7)SIGTERM:结束进程信号。shell下执行kill 进程pid发送该信号。 (8)SIGALRM:定时器信号。 (9)SIGCLD:子进程退出信号。如果其父进程没有忽略该信号也没有处理该信号,则子进程退出后将形成僵尸进程。

信号来源
信号是软件层次上对中断机制的一种模拟,是一种异步通信方式,,信号可以在用户空间进程和内核之间直接交互,内核可以利用信号来通知用户空间的进程发生了哪些系统事件,信号事件主要有两个来源:

  • 硬件来源:用户按键输入Ctrl+C退出、硬件异常如无效的存储访问等。
  • 软件终止:终止进程信号、其他进程调用kill函数、软件异常产生信号。

信号生命周期和处理流程
(1)信号被某个进程产生,并设置此信号传递的对象(一般为对应进程的pid),然后传递给操作系统;
(2)操作系统根据接收进程的设置(是否阻塞)而选择性的发送给接收者,如果接收者阻塞该信号(且该信号是可以阻塞的),操作系统将暂时保留该信号,而不传递,直到该进程解除了对此信号的阻塞(如果对应进程已经退出,则丢弃此信号),如果对应进程没有阻塞,操作系统将传递此信号。
(3)目的进程接收到此信号后,将根据当前进程对此信号设置的预处理方式,暂时终止当前代码的执行,保护上下文(主要包括临时寄存器数据,当前程序位置以及当前CPU的状态)、转而执行中断服务程序,执行完成后在回复到中断的位置。当然,对于抢占式内核,在中断返回时还将引发新的调度。
image.png


消息队列

  • 消息队列是存放在内核中的消息链表,每个消息队列由消息队列标识符表示。
  • 与管道(无名管道:只存在于内存中的文件;命名管道:存在于实际的磁盘介质或者文件系统)不同的是消息队列存放在内核中,只有在内核重启(即,操作系统重启)或者显示地删除一个消息队列时,该消息队列才会被真正的删除。
  • 另外与管道不同的是,消息队列在某个进程往一个队列写入消息之前,并不需要另外某个进程在该队列上等待消息的到达

消息队列特点总结: (1)消息队列是消息的链表,具有特定的格式,存放在内存中并由消息队列标识符标识.

(2)消息队列允许一个或多个进程向它写入与读取消息.

(3)管道和消息队列的通信数据都是先进先出的原则。

(4)消息队列可以实现消息的随机查询,消息不一定要以先进先出的次序读取,也可以按消息的类型读取.比FIFO更有优势。

(5)消息队列克服了信号承载信息量少,管道只能承载无格式字 节流以及缓冲区大小受限等缺。

(6)目前主要有两种类型的消息队列:POSIX消息队列以及System V消息队列,系统V消息队列目前被大量使用。系统V消息队列是随内核持续的,只有在内核重起或者人工删除时,该消息队列才会被删除。


共享内存(share memory)

  • 使得多个进程可以可以直接读写同一块内存空间,是最快的可用IPC形式。是针对其他通信机制运行效率较低而设计的。
  • 为了在多个进程间交换信息,内核专门留出了一块内存区,可以由需要访问的进程将其映射到自己的私有地址空间。进程就可以直接读写这一块内存而不需要进行数据的拷贝,从而大大提高效率。
  • 由于多个进程共享一段内存,因此需要依靠某种同步机制(如信号量)来达到进程间的同步及互斥

信号量(semaphore)

信号量是一个计数器,用于多进程对共享数据的访问,信号量的意图在于进程间同步。
为了获得共享资源,进程需要执行下列操作:
(1)创建一个信号量:这要求调用者指定初始值,对于二值信号量来说,它通常是1,也可是0。
(2)等待一个信号量:该操作会测试这个信号量的值,如果小于0,就阻塞。也称为P操作。
(3)挂出一个信号量:该操作将信号量的值加1,也称为V操作。
为了正确地实现信号量,信号量值的测试及减1操作应当是原子操作。为此,信号量通常是在内核中实现的。Linux环境中,有三种类型:Posix(可移植性操作系统接口)有名信号量(使用Posix IPC名字标识)Posix基于内存的信号量(存放在共享内存区中)System V信号量(在内核中维护)。这三种信号量都可用于进程间或线程间的同步。

信号量与普通整型变量的区别:
(1)信号量是非负整型变量,除了初始化之外,它只能通过两个标准原子操作:wait(semap) , signal(semap) ; 来进行访问;
(2)操作也被成为PV原语(P来源于荷兰语proberen”测试”,V来源于荷兰语verhogen”增加”,P表示通过的意思,V表示释放的意思),而普通整型变量则可以在任何语句块中被访问;

信号量与互斥量之间的区别:
(1)互斥量用于线程的互斥信号量用于线程的同步。这是互斥量和信号量的根本区别,也就是互斥和同步之间的区别。
互斥:是指某一资源同时只允许一个访问者对其进行访问,具有唯一性和排它性。但互斥无法限制访问者对资源的访问顺序,即访问是无序的。
同步:是指在互斥的基础上(大多数情况),通过其它机制实现访问者对资源的有序访问。
在大多数情况下,同步已经实现了互斥,特别是所有写入资源的情况必定是互斥的。少数情况是指可以允许多个访问者同时访问资源
(2)互斥量值只能为0/1,信号量值可以为非负整数。
也就是说,一个互斥量只能用于一个资源的互斥访问,它不能实现多个资源的多线程互斥问题。信号量可以实现多个同类资源的多线程互斥和同步。当信号量为单值信号量是,也可以完成一个资源的互斥访问。
(3)互斥量的加锁和解锁必须由同一线程分别对应使用,信号量可以由一个线程释放,另一个线程得到。


套接字(socket)

image.png
套接字可以看做是:不同主机之间的进程进行双向通信的端点。(套接字 = IP地址 + 端口号)