(1)进程间几种通信方式
管道、消息队列、共享内存、信号量、信号、socket
要知道管道、消息队列、共享内存的本质:内存本质、效率以及传输数据要求,各种使用方式
一、管道
管道通信效率低,不适合进程间频繁地交换数据。好处,简单,很容易得知已被另一进程读
匿名管道,通信范围存在父子关系进程。没有管道文件,只能 fork 复制父进程 fd 文件描述符来通信
命名管道,不相关进程间也能相互通信。因为命令管道,提前创建管道类型设备文件,只要用这个文件,就可通信
相同:都在缓存内核中读写,先进先出,不支持 lseek 之类文件定位操作
1、匿名管道
$ ps auxf | grep mysql 用完就销毁
| 就是一个管道,将前一个命令(ps auxf)输出,作为后一命令(grep mysql)输入,管道传数据是单向,如相互通信,要两个
2、命名管道FIFO
$ mkfifo myPipe(名) 用前要mkfifo 命令创建,指定管道名,数据先进先出
1)基于“Linux一切皆文件”,管道也文件,ls 看,文件类型是 p,就是 pipe(管道) 意思
2)往 myPipe 写入数据:因为内容没被读,只有读后,命令才正常退出
3)读出了,echo 命令正常退出
3、创建原理
1)匿名管道创建, 通过 int pipe(intfd[2]) 系统调用:
两个描述符:管道读取端 fd[0],写入端 fd[1]。ps:匿名管道是特殊文件,只在内存,不存文件系统
2)管道,就是内核里一串缓存。读写都在缓存内核中,传数据是无格式的流且大小受限
3)跨进程通信实现:fork 创建子进程,复制父进程文件描述符,两个进程各有两个「 fd[0] 与 fd[1]」,通过各自fd 读写同一管道文件
4)避免混乱:父进程:关读 fd[0],保留写fd[1] 子进程:关写 d[1],保留读fd[0]
所以,双向通信,应两个管道
5)到这里,仅解析父子进程通信,但shell 里面并不是这样
shell 里:执行 A | B命令时,都是 shell 创建的子进程,不存父子关系,父进程都是 shell
ps:shell 里能使用一个管道搞定的事情,就不要多用一个管道,减少创建子进程开销
二、消息队列
解决频繁地交换数据问题,内核中的消息链表
1、发时,分成一个个消息体(数据块),用户自定义数据类型,固定大小,不像管道是无格式字节流数据。读后,内核删除消息体
2、生命周期:随内核,没有释放消息队列或没关闭操作系统,管道随进程结束销毁
3、不足:通信不及时,大小限制
用户态与内核态间数据拷贝开销:写数据到内核队列时,从用户态拷到内核态过程,读同理,从内核到用户
不适合大数据传输,消息体、队列总长都有限制。Linux 内核中有MSGMAX 和 MSGMNB 消息和队列最大长,字节为单位
三、共享内存
解决 用户态与内核间的消息拷贝
1、内存管理:进程有自己独立的虚拟内存空间,不同进程的虚拟内存映射到不同物理内存中。即使进程 A B 虚拟地址一样,其实访问的是不同的物理内存地址,对于数据的增删查改互不影响。
2、共享内存机制:就是拿出一块虚拟地址空间来,映射到相同的物理内存中。这样写入,另一进程马上能看到,不需拷贝,传来传去,提高通信速度
四、信号量
共享内存问题,同时修改同一共享内存,冲突。信号量解决
信号量:整型计数器,表示资源的数量,实现进程间互斥与同步,而不是用于缓存进程间通信数据
1、控制信号量两种原子操作:
1)P 操作,减 -1,减后:< 0,被占用,进程阻塞等待; >= 0可用,继续执行
2)V 操作,加 1, <= 0,有阻塞唤醒运行; > 0,没有阻塞
P 在进入共享资源之前,V 离开后,成对出现
2、例:两进程互斥访问共享内存,初始化信号量1
先P 操作,信号量变 0,可用,访问共享内存
此时B 也访问, P 操作, -1被阻塞。
A 完V 操作,恢复为 0,唤醒阻塞中B,B 完成执行 V,恢复1
例2,A 负责生产数据,B 是负责读,有顺序。初始化信号量可为 0
五、信号
上面都是常规工作模式。异常用「信号」通知进程,唯一异步通信机制。ps:跟信号量虽然名字相似,用途完全不一样
1、Linux 操作系统, 为响应各种各样事件,提供几十种信号, kill -l 命令,查看所有
2、给进程发送信号
终端输入组合键: Ctrl+C 产生 SIGINT 信号,终止进程;
Ctrl+Z 产生 SIGTSTP 信号,停止进程,未结束;
如果进程在后台运行,用kill+PID 号 kill -9 1050 ,立即结束
3、进程对信号处理方式
1)执行默认操作:Linux 对每种信号规定默认操作,SIGTERM 信号,终止。Core Dump,终止进程后,通过Core Dump 将当前进程的运行状态保存在文件里,方便事后分析
2.捕捉信号:定义信号处理函数。信号发生,执行相应函数
3.忽略信号。不做任何处理。SIGKILL 和 SEGSTOP无法捕捉和忽略,用于任何时候中断或结束某一进程。
六、Socket
跨网络与不同主机上进程间通信(也可同主机),要 Socket
1、创建 socket 系统调用
int socket(int domain,int type, int protocal)
domain:协议族,如 AF_INET 用于 IPV4、AF_INET6 用于 IPV6、AF_LOCAL/AF_UNIX 用于本机;
type :通信特性,如 SOCK_STREAM 表示字节流,对应 TCP、SOCK_DGRAM 表示数据报,对应 UDP、SOCK_RAW 表示原始套接字;
protocal (基本废弃):写成 0 即可,原本是用来指定通信协议的,通过前两个完成
2、不同socket 类型,通信方式不同
实现 TCP 字节流通信:socket 是 AF_INET 和 SOCK_STREAM;
实现 UDP 数据报通信:socket 是 AF_INET 和 SOCK_DGRAM;
实现本地进程间通信:「本地字节流 socket 」类型是 AF_LOCAL 和 SOCK_STREAM,「本地数据报 socket 」类型是 AF_LOCAL 和 SOCK_DGRAM。ps:AF_UNIX 和 AF_LOCAL 是等价的,所以 AF_UNIX 也属于本地 socket;
3、三种通信编程模式
(1)TCP 协议通信socket 编程模型
1)服务端和客户端初始化 socket,得到文件描述符;
2)服务端调bind,绑定IP 地址和端口; 调listen监听;调accept,等待客户端连接;
3)客户端调connect,向服务器端的地址和端口发起连接请求;
4)服务端 accept 返回用于传输的 socket 文件描述符;ps:连接成功返回已完成连接socket,通过read 和 write 读写,像往文件流里面写东西一样
5)客户端调 write 写入数据;服务端调用 read 读取数据;
6)客户端断开连接时,调用 close,那么服务端 read 读取数据时,读到EOF,处理完close
监听socket 和 真正传数据socket(已完成连接 socket),是「两个」 socket
(2)针对 UDP 协议通信的 socket 编程模型
1)只要 IP 地址和端口号、bind,UDP没连接,不需要三次握手,不需像 TCP 调listen 和 connect,。
2)每次通信,调sendto 和 recvfrom,传入目标主机的 IP 地址和端口
(3)本地进程间通信socket 编程模型
用于同一主机通信的,
1)接口和 IPv4 、IPv6 套接字编程一致,支持「字节流」和「数据报」两种协议;效率大大高于 IPv4 和 IPv6 字节流、数据报 socket 实现;
本地字节流 socket,socket 类型AF_LOCAL 和 SOCK_STREAM。
本地数据报 socket,AF_LOCAL 和 SOCK_DGRAM。
2)两个bind 时,绑定一个本地文件,不像 TCP 和 UDP 要绑定 IP 地址和端口,最大区别
总结
每个进程都共享一个内核空间,来通信
1、Linux 内核提供「匿名管道」和「命名管道」:都写入缓存在内核中,另一个进程也从内核读,先进先出,不支持 lseek 文件定位
匿名:「|」竖线就是匿名管道,通信数据无格式的流并且大小受限,单向,双向要建两个管道,只能用于父子关系通信,随着进程创建而建,终止而消失
命名管道:突破父子限制,使用前提,要文件系统创建类型 p 的设备文件。
2、消息队列:解决管道无格式的字节流的问题,实际是保存在内核「消息链表」,用户可自定义消息体,发时被分成,一个个独立消息体,接时保持一致,不是最及时的,读写要经过用户态与内核态之间的拷贝过程。
3、共享内存:解决拷贝开销,直接分配共享空间,进程可直接访问,提高速度,缺点:多进程竞争同个共享资源错乱
4、信号量:实现互斥访问。实际是计数器(资源个数),P 和 V 操作控制
5、信号:唯一异步通信机制,在应用进程和内核间直接交互,内核用信号来通知用户,进程发生了哪些系统事件,三种方式响应信号 1. 执行默认操作、2. 捕捉信号、3. 忽略信号。ps:SIGKILL 和 SEGSTOP无法捕捉和忽略,方便任何时候结束进程
6、Socket:与不同主机的进程间通信,那么就需要 通信了。根据Socket不同类型,分为三种通信方式, TCP、UDP、本地进程间
https://mp.weixin.qq.com/s/MnIcTR0KKpgnSoA3xaPUSA