Linux下进程间的通信方式

Linux下进程间的通信方式

每个进程的用户地址空间都是独立的,一般而言是不能互相访问的,但内核空间是每个进程都共享的,所以进程之间要通信必须通过内核。
在这里插入图片描述

1 管道
1)匿名管道
ps aux | grep mysql

匿名管道:将前一个命令(ps aux)的输出,作为后一个命令(grep mysql)的输入,,可以看出管道传输数据是单向的。

2)命名管道(FIFO)

mkfifo hello:创建一个名为htllo的管道
echo "helloworld" > hello:将数据"helloworld"写入管道
cat < hello:读取管道中的数据

缺点:
管道效率低,不适合进程间频繁地交换数据。

注:

  • 管道就是内核里面的一串缓存。从管道的一段写入的数据,实际上是缓存在内核中的,另一端读取。
  • 在 shell 里通过|匿名管道将多个命令连接在一起,实际上就是创建了多个子进程,那么在我们编写 shell 脚本时,能使用一个管道搞定的事情,就不要多用一个管道,这样可以减少创建子进程的系统开销。
  • 对于匿名管道,它的通信范围是存在父子关系的进程。因为管道没有实体,也就是没有管道文件,只能通过 fork 来复制父进程 fd 文件描述符,来达到通信的目的。
  • 对于命名管道,它可以在不相关的进程间也能相互通信。因为命令管道,提前创建了一个类型为管道的设备文件,在进程里只要使用这个设备文件,就可以相互通信。
  • 通信的方式是单向的,数据只能在一个方向上流动,如果要双向通信,需要创建两个管道,
2 消息队列

消息队列克服了管道通信的数据是无格式的字节流的问题,消息队列实际上是保存在内核的消息链表。

消息队列:A 进程要给 B 进程发送消息,A 进程把数据放在对应的消息队列后就可以正常返回了,B 进程需要的时候再去读取数据就可以了。

注:

  • 消息队列不适合比较大数据的传输,因为在内核中每个消息体都有一个最大长度的限制,同时所有队列所包含的全部消息体的总长度也是有上限。
  • 消息队列通信过程中,存在用户态与内核态之间的数据拷贝开销,因为进程写入数据到内核中的消息队列时,会发生从用户态拷贝数据到内核态的过程,同理另一进程读取内核中的消息数据时,会发生从内核态拷贝数据到用户态的过程。
3 共享内存

消息队列的读取和写入的过程,都会有发生用户态与内核态之间的消息拷贝过程。那共享内存的方式,就很好的解决了这一问题。

共享内存的机制,就是拿出一块虚拟地址空间来,映射到相同的物理内存中。这样A进程写入的东西,B进程马上就能看到,就不需要进行拷贝了,大大提高了进程间通信的速度。
在这里插入图片描述

4 信号量

用了共享内存通信方式,带来新的问题,那就是如果多个进程同时修改同一个共享内存,很有可能就冲突了。

信号量:使得共享的资源,在任意时刻只能被一个进程访问。信号量其实是一个整型的计数器,主要用于实现进程间的互斥与同步。

P:使信号量减1
V:使信号量加1

1)经过P操作后,信号量小于0,则说资源已被占用,进程需阻塞等待,否则就可以继续执行。

2)经过V操作后,信号量<=0(不到初始状态的1),说明还有阻塞进程,会将该阻塞进程唤醒运行,否则,则说明当前没有阻塞的进程。

(1)互斥信号量

信号量初始化为 1,就代表着是互斥信号量它可以保证共享内存在任何时刻只有一个进程在访问,这就很好的保护了共享内存。
在这里插入图片描述
具体的过程如下:

  • 进程 A 在访问共享内存前,先执行了 P 操作,由于信号量的初始值为 1,故在进程 A 执行 P 操作后信号量变为 0,表示共享资源可用,于是进程 A 就可以访问共享内存。
  • 若此时,进程 B 也想访问共享内存,执行了 P 操作,结果信号量变为了 -1,这就意味着临界资源已被占用,因此进程 B 被阻塞。
  • 直到进程 A 访问完共享内存,才会执行 V 操作,使得信号量恢复为 0,接着就会唤醒阻塞中的线程 B,使得进程 B 可以访问共享内存,最后完成共享内存的访问后,执行 V 操作,使信号量恢复到初始值 1。
(2)同步信号量

信号初始化为 0,就代表着是同步信号量它可以保证进程 A 应在进程 B 之前执行。

进程 A 必须先生产了数据,进程 B 才能读取到数据,执行是需要有前后顺序的,此时就需要将初始信号量设为0.
在这里插入图片描述
具体过程:

  • 如果进程 B 比进程 A 先执行了,那么执行到 P 操作时,由于信号量初始值为 0,故信号量会变为 -1,表示进程 A还没生产数据,于是进程 B 就阻塞等待;
  • 接着,当进程 A 生产完数据后,执行了 V 操作,就会使得信号量变为 0,于是就会唤醒阻塞在 P 操作的进程 B;
  • 最后,进程 B 被唤醒后,意味着进程 A 已经生产了数据,于是进程 B 就可以正常读取数据了。
5 信号

对于异常情况下的工作模式,就需要用 信号 的方式来通知进程。

信号事件的来源主要有 硬件来源(如键盘 Cltr+C )和 软件来源(如 kill 命令)。

可以通过 kill -l 命令,查看所有的信号。

(1)硬件来源

运行在 shell 终端的进程,我们可以通过键盘输入某些组合键的时候,给进程发送信号。例如:

  • Ctrl+C 产生 SIGINT 信号,表示终止该进程;
  • Ctrl+Z 产生 SIGTSTP 信号,表示停止该进程,但还未结束;
(2)软件来源

如果进程在后台运行,可以通过 kill 命令的方式给进程发送信号,但前提需要知道运行中的进程 PID 号,例如:

kill -9 1050 :表示给 PID 为 1050 的进程发送 SIGKILL 信号,用来立即结束该进程;

注意:

  • 信号是进程间通信机制中唯一的异步通信机制,因为可以在任何时候发送信号给某一进程。
6 Socket

管道、消息队列、共享内存、信号量和信号都是在同一台主机上进行进程间通信,那要想跨网络与不同主机上的进程之间通信,就需要 Socket 通信了。

Socket 通信不仅可以跨网络与不同主机的进程间通信,还可以在同主机上进程间通信。

(1) 针对 TCP 协议通信的 socket 编程模型

在这里插入图片描述

  • 服务端和客户端初始化 socket,得到文件描述符;
  • 服务端调用 bind,将绑定在 IP 地址和端口;
  • 服务端调用 listen,进行监听;
  • 服务端调用 accept,等待客户端连接;
  • 客户端调用 connect,向服务器端的地址和端口发起连接请求;
  • 服务端 accept 返回用于传输的 socket 的文件描述符;
  • 客户端调用 write 写入数据;服务端调用 read 读取数据;
  • 客户端断开连接时,会调用 close,那么服务端 read 读取数据的时候,就会读取到了 EOF,待处理完数据后,服务端调用close,表示连接关闭。

注意:
服务端调用 accept 时,连接成功了会返回一个已完成连接的 socket,后续用来传输数据。所以监听的 socket 和真正用来传送数据的 socket,是「两个」 socket,一个叫作监听 socket,一个叫作已完成连接 socket。

(2)针对 UDP 协议通信的 socket 编程模型

在这里插入图片描述
UDP 是没有连接的,所以不需要三次握手,也就不需要像 TCP 调用 listen 和 connect,但是 UDP 的交互仍然需要 IP 地址和端口号,因此也需要 bind。

对于 UDP 来说,不需要要维护连接,那么也就没有所谓的发送方和接收方,甚至都不存在客户端和服务端的概念,只要有一个 socket 多台机器就可以任意通信,因此每一个 UDP 的 socket 都需要 bind。

注意:
每次通信时,调用 sendto 和 recvfrom,都要传入目标主机的 IP 地址和端口。

(3)针对本地进程间通信的 socket 编程模型

对于本地字节流 socket,其 socket 类型是 AF_LOCAL 和 SOCK_STREAM。

对于本地数据报 socket,其 socket 类型是 AF_LOCAL 和 SOCK_DGRAM。

本地字节流 socket 和 本地数据报 socket 在 bind 的时候,不像 TCP 和 UDP 要绑定 IP 地址和端口,而是绑定一个本地文件,这也就是它们之间的最大区别。

参考文章:https://mp.weixin.qq.com/s/mblyh6XrLj1bCwL0Evs-Vg

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值