# IPC 进程间通信
# 管道通信
# 匿名管道
匿名管道通信步骤:
- 调用 pipe 函数,由父进程创建管道,得到两个
文件描述符
指向管道的两端。 - 父进程 fork 创建子进程,子进程业火的两个
文件描述符
指向管道两端。 - 父进程关闭读端,只进行写操作,子进程关闭写端,只进行读操作。管道由
循环队列
实现,数据从写端流向读端。
匿名管道的局限性:
- 只支持单向数据流;
- 只能用于具有亲缘关系的进程之间;
- 管道的缓冲区是有限的(管道制存在于内存中,在管道创建时,为缓冲区分配一个页面大小);
- 管道所传送的是无格式字节流,这就要求管道的读出方和写入方必须事先约定好数据的格式,比如多少字节算作一个消息(或命令、或记录)等等;
# 有名管道
与管道的区别:提供了一个路径名与之关联,以 FIFO 文件的形式存储于文件系统中,能够实现任何两个进程之间通信。而匿名管道对于文件系统是不可见的,它仅限于在父子进程之间的通信。
FIFO 是一个设备文件,在文件系统中以文件名的形式存在,因此即使进程与创建 FIFO 的进程不存在血缘关系也依然可以通信,前提是可以访问该路径。
FIFO (first input first output) 总是遵循先进先出的原则,即第一个进来的数据会第一个被读走。
命名管道是 设备文件
,它是存储在硬盘上的,而管道是存储在 内存
中的特殊文件。
这篇文章写的很好:匿名管道和有名管道
** 无名管道阻塞问题:** 无名管道无需显示打开,创建时直接返回文件描述符,在读写时需要确定对方的存在,否则将退出。如果当前进程向无名管道的一端写数据,必须确定另一端有某一进程。如果写入无名管道的数据超过其最大值,写操作将阻塞,如果管道中没有数据,读操作将阻塞,如果管道发现另一端断开,将自动退出。
无名管道如果只有一个进程操作就退出。
有名管道阻塞问题:有名管道在打开时需要确认的存在,否则将阻塞。即以读方式打开某管道,在此之前必须一个进程以写方式打开管道,否则阻塞。此外,可以以读写(O_RDWR)模式打开有名管道,即当前进程读,当前进程写,不会阻塞。
有名管道如果只有一个进程操作就阻塞。
# 信号
- 信号是进程间相互通信的一种机制,信号可以在任何时候发送给某一个进程,而不需要知道该进程状态。
- 如果该进程当前并未处于执行状态,则该信号就有内核保存起来,知道执行才把信号传递给它。
- 如果该进程处于阻塞,同样的,知道取消阻塞才把信号传递给该进程。
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:子进程退出信号。如果其父进程没有忽略该信号也没有处理该信号,则子进程退出后将形成僵尸进程。
sigterm 和 sigkill 区别:sigterm 可以被忽略而 sigkill 不能被忽略。
# 信号来源
信号是软件层次上对中断机制的一种模拟,是一种异步通信方式。信号可以在用户空间进程和内核直接交互,内核可以利用信号来通知进程发生了什么事情。
信号来源自:硬件来源(Ctrl+c),软件终止(kill pid)
# 信号生命周期
信号被某个进程产生,并设置此信号传递的对象(一般为对应进程的 pid),然后传递给操作系统。
操作系统根据接收进程的设置(是否阻塞)而选择性的发送给接收者,如果接收者阻塞该信号(且该信号是可以阻塞的),操作系统将暂时保留该信号,而不传递,直到该进程解除了对此信号的阻塞(如果对应进程已经退出,则丢弃此信号),如果对应进程没有阻塞,操作系统将传递此信号
目的进程接收到此信号后,将根据当前进程对此信号设置的预处理方式,暂时终止当前代码的执行,保护上下文(主要包括临时寄存器数据,当前程序位置以及当前 CPU 的状态)、转而执行中断服务程序,执行完成后在回复到中断的位置。当然,对于抢占式内核,在中断返回时还将引发新的调度。
进程产生信号➡️ 内核接受信号后,根据接受进程状态选择是否发送 ➡️ 目的进程收到信号,根据信号类型执行对应的操作。
# 消息队列
消息队列可以认为是一个消息链表,System V 消息队列使用消息队列标识符标识。具有足够特权的任何进程都可以往一个队列放置一个消息,具有足够特权的任何进程都可以从一个给定队列读出一个消息。
在某个进程往一个队列写入消息之前,并不需要另外某个进程在该队列上等待消息的到达。
System V 消息队列是随内核持续的,只有在内核重起或者显示删除一个消息队列时,该消息队列才会真正被删除。
匿名管道放在内存中,有名管道放在硬盘中,消息队列放在内核中。
消息队列克服了信号承载信息量少,管道只能承载无格式字节流以及缓冲区大小受限等缺。
消息队列除了先进先出还可以按消息的类型进行存储。
# 共享内存
- 使得多个进程可以可以直接读写同一块内存空间,是最快的可用 IPC 形式。是针对其他通信机制运行效率较低而设计的。
- 为了在多个进程间交换信息,内核专门留出了一块内存区,可以由需要访问的进程将其映射到自己的私有地址空间。进程就可以直接读写这一块内存而不需要进行数据的拷贝,从而大大提高效率。
- 由于多个进程共享一段内存,因此需要依靠某种同步机制(如信号量)来达到进程间的同步及互斥。
# 信号量
这个就是之前学操作系统中的信号量机制。PV 操作。
因为对信号量的操作应当是原子操作,所以信号量一般在内核中实现。
互斥量只能为 0/1,而信号量可以是非负整数。
# 套接字
套接字 ( socket
) 也是一种进程间的通信方式,不过他是不同主机之间进行的相互通信。
套接字特性
套接字的特性由 3 个属性确定,它们分别是:域、端口号、协议类型。
(1)套接字的域
它指定套接字通信中使用的网络介质,最常见的套接字域有两种:
** 一是 AF_INET,它指的是 Internet 网络。** 当客户使用套接字进行跨网络的连接时,它就需要用到服务器计算机的 IP 地址和端口来指定一台联网机器上的某个特定服务,所以在使用 socket 作为通信的终点,服务器应用程序必须在开始通信之前绑定一个端口,服务器在指定的端口等待客户的连接。
** 另一个域 AF_UNIX,表示 UNIX 文件系统,** 它就是文件输入 / 输出,而它的地址就是文件名。
(2)套接字的端口号
每一个基于 TCP/IP 网络通讯的程序 (进程) 都被赋予了唯一的端口和端口号,端口是一个信息缓冲区,用于保留 Socket 中的输入 / 输出信息,端口号是一个 16 位无符号整数,范围是 0-65535,以区别主机上的每一个程序(端口号就像房屋中的房间号),低于 256 的端口号保留给标准应用程序,比如 pop3 的端口号就是 110,每一个套接字都组合进了 IP 地址、端口,这样形成的整体就可以区别每一个套接字。
(3)套接字协议类型
因特网提供三种通信机制
** 一是流套接字,** 流套接字在域中通过 TCP/IP 连接实现,同时也是 AF_UNIX 中常用的套接字类型。流套接字提供的是一个有序、可靠、双向字节流的连接,因此发送的数据可以确保不会丢失、重复或乱序到达,而且它还有一定的出错后重新发送的机制。
** 二个是数据报套接字,** 它不需要建立连接和维持一个连接,它们在域中通常是通过 UDP/IP 协议实现的。它对可以发送的数据的长度有限制,数据报作为一个单独的网络消息被传输,它可能会丢失、复制或错乱到达,UDP 不是一个可靠的协议,但是它的速度比较高,因为它并一需要总是要建立和维持一个连接。
** 三是原始套接字,** 原始套接字允许对较低层次的协议直接访问,比如 IP、 ICMP 协议,它常用于检验新的协议实现,或者访问现有服务中配置的新设备,因为 RAW SOCKET 可以自如地控制 Windows 下的多种协议,能够对网络底层的传输机制进行控制,所以可以应用原始套接字来操纵网络层和传输层应用。比如,我们可以通过 RAW SOCKET 来接收发向本机的 ICMP、IGMP 协议包,或者接收 TCP/IP 栈不能够处理的 IP 包,也可以用来发送一些自定包头或自定协议的 IP 包。网络监听技术很大程度上依赖于 SOCKET_RAW。
这一块可以参考:进程间通信 IPC (InterProcess Communication)