【Linux 系统】进程间通信(匿名管道 & 命名管道)-- 详解(下)

简介: 【Linux 系统】进程间通信(匿名管道 & 命名管道)-- 详解(下)

【Linux 系统】进程间通信(匿名管道 & 命名管道)-- 详解(上)https://developer.aliyun.com/article/1515599?spm=a2c6h.13148508.setting.19.11104f0e63xoTy

4、管道的特点

(1)管道只能用于具有血缘关系的进程进行通信,它常用父子进程通信

通常,一个管道由一个进程创建,然后该进程调用 fork(),此后父子进程之间就可应用该管道。


(2)管道为了让进程间协同,提供了访问控制(管道自带同步机制)

父进程写完数据后休眠 1s,而子进程却没有,子进程一瞬间就把数据读完了,在父进程休眠的那 1s 内,子进程在干什么呢?

管道和显示器都是一个文件。为什么之前父子进程同时往显示器打印写入时没有出现这样的情况?这种情况称为缺乏访问控制,而当前这个问题具有访问控制。

此时管道中并没有数据,子进程在进行等待管道内部有数据就绪,这需要写端造成。

如果此时父进程一直往管道里写,而子进程休眠上 20s 呢?

父进程写到 3972 次时就没再继续写了。换而言之,如果管道里写端已经写满了,此时是不能再继续写入的,而写端就在等待管道内部有空闲空间,这需要读端造成。

综上而言,通信双方在管道中,如果其中一方不写了,另一方把数据读完后就必须等待对方写入才可以继续读;反之如果一方写满了,另一方不读,那么一方就必须等待另一方读取后才可以继续写。这种特性就叫做进程间同步,它们两方必须得通过某种同步机制来保证数据安全:管道是内存空间。如果一方不写,另一方还在那读,那么读到的数据肯定是垃圾数据;同样,如果一方一直写入,但另外一方却不读,那就可能会导致原来的数据被覆盖。

进程间同步其实是一种保护临界资源的一种处理方案,后面会再详细介绍。


(3)管道提供面向流式的通信服务 —— 面向字节流

这里先简单理解一下,更进一步理解需要后面学习到网络部分。

流是什么?

下面是一段缓冲区,那么一定要有人去缓冲区中写入和读取。流就是想按几个字节就按几个字节写,想按几个字节读就按几个字节读。像这样的缓冲区对于读和写而言,就是字节流。


(4)管道是基于文件的

一般而言,进程退出,管道释放,所以管道的生命是随进程的,文件的生命周期也是随进程的。


(5)管道是单向通信的

管道是单向通信的,其本质是半双工通信的一种特殊情况。

管道是半双工的,数据只能向一个方向流动;当需要双方通信时,需要建立起两个管道。

举一个生活中的例子:人与人之间交流时一般是半双工(一个人说,一个人听),而在吵架时可能就是全双工(两个人都在说,也都在听)。


(6)管道能够保证一定程度的数据读取的原子性

如果往管道写 hello world,刚准备写 world,而 hello 就被读走了,此时就不能保证原子性。这里的一定程度一般指的是 4kb。

原子性的详细介绍主要是在后面的多线程部分。


4、管道的读写规则

当没有数据可读时:

  • O_NONBLOCK disable:read 调用阻塞,即进程暂停执行,一直等到有数据来到为止。
  • O_NONBLOCK enable:read 调用返回 -1,errno 值为 EAGAIN。

当管道满的时候:
  • O_NONBLOCK disable: write 调用阻塞,直到有进程读走数据。
  • O_NONBLOCK enable:调用返回 -1,errno 值为 EAGAIN。

  • 如果所有管道写端对应的文件描述符被关闭,则 read 返回 0。
  • 如果所有管道读端对应的文件描述符被关闭,则 write 操作会产生信号 SIGPIPE,进而可能导致 write 进程退出。

  • 当要写入的数据量不大于 PIPE_BUF 时,linux 将保证写入的原子性。
  • 当要写入的数据量大于 PIPE_BUF 时,linux 将不再保证写入的原子性。

⚪验证部分特性

a. 读写端测试

如果读端关闭,写端一直写,肯定是没有意义的,其本质就是在浪费资源,所以这个时候写进程会立马被 OS 通过发送信号的方式终止。而写进程是子进程,此时父进程就可以 waitpid,从而知道子进程退出的原因。这里可以看到唯一还没有研究的正是 write 端写和 read 端不读&关闭,这也就是为什么要让子进程写,父进程读的原因,因为它更适合测试。

此时子进程不断的写,而父进程读取一次后就关闭读,子进程再写,OS 就发送 13 号信号终止了进程。


b. 单机版的负载均衡  

运行效果:


(1)管道的大小

a. 子进程一直写,每次写一个字节,然后计数器统计,父进程不要读。可以看到结果是 65536 byte,也就是管道的大小是 64kb,不过操作系统不同数据可能不一样


b. ulimit -a 查看系统资源

这里通过计算器算出来结果其实也才 4 kb,而实践出来却是 64kb。这里的 64kb 是当前云服务器管道的最大容量,而这里的 4kb 只是以原子性写入管道中的单元大小(可以通过 man 7 pipe 手册进行查看,可以看到 PIPE_BUF 是 4096 byte(4kb),只要在这个范围内就都是原子的)。


5、命名管道 fifo

命名管道是一种特殊类型的文件。

命名管道是供毫不相关的进程进行进程间通信。命名管道一般叫做 fifo,fifo 一定不陌生,因为数据结构中队列就是这种特性。


(1)理解命名管道的原理

要让两个毫不相干的进程进行通信,首先一定是要保证这两个进程可以看到同一份资源。因为需要相同的文件路径,所以让进程 1 和 进程 2 分别以读写的方式打开同一路径下的文件,此时内存中一定会包含 struct file 结构体,以及该文件所对应的缓冲区。那么此时进程 2 把数据写到缓冲区中,进程 1 就可以进行读取。

命名管道也是管道,它也遵守管道的面向字节流,同步机制,单向通信等特点。唯一和匿名管道不同的是它可以和不相关的进程进行通信。

对于普通文件来说,是需要将数据刷新到磁盘上持久化存储的,所以它就应该要把写入的数据刷新到磁盘上。换而言之,进程 2 把文件打开写数据到磁盘然后关闭,进程 1 再从磁盘读取,那么这当然可以通信,但是数据放在磁盘上的效率就太低了,便没有什么价值。

所以系统中就存在一种特殊的文件 —— 管道文件,虽然它也有路径标识,但是系统不会把它对应的内存数据刷新到磁盘上,既然它是文件,那么它就一定有自己的名字,且它一定在系统路径中。而路径是唯一的,那么双方进程就可以通过管道文件的路径看到同一份资源。


(2)创建命名管道

a. 命令行创建
mkfifo filename


b. 在程序里创建
int mkfifo(const char *filename,mode_t mode);

(3)用命名管道实现 server & client 通信

A. mkfifo name_pipe

创建完管道文件之后,向管道里面写入内容,但是因为对方还没打开,此时处于阻塞状态。

一方写入,另一方读取,就可以看到所写入的内容了。这就叫作一个进程向另一个进程写入消息的过程。

name_pipe 就是一个管道文件,此时往文件中写入数据后,可以发现它的大小依旧是 0,因为数据只会在内存中,不会往磁盘刷。


B. 一边不断的往管道里写数据,另一边以管道作为标准输入然后输出重定向到 cat,最后显示出来

也可以说是 cat 从管道中把数据读取出来,这就完成了两个进程之间的通信。


C. 代码
a. 准备工作

想要 make 后一次生成两个不相关的可执行程序,需要我们在开头的时候定义 all 伪目标,它依赖的是两个可执行程序,没有依赖方法(因为它有依赖关系,所以 makefile 会推导 client 和 server 是怎么形成的)。

虽然 makefile 这样的技术已经很老了,但是它很稳定,几乎是现在主流的各种各样的工具的基础。虽然实际在公司并不会自己写 makefile(除非自己写测试代码),公司一般都有很多工具来自动生成 makefile,但是必要的 makefile 编写还是需要我们了解的,因为上层的工具和 makefile 有关系。


b. mkfifo 函数

mkfifo 既是命令,也是一个库函数。

第一个参数是命名管道的路径,第二个参数是命名管道的权限。成功返回 0,失败返回 -1。


c. 实现管道通信

此时代码中的 mkfifo 和命令中的 mkfifo 达到的效果是一样的。


下面 client.c 中以写打开管道文件,然后从键盘读取数据到 buffer,然后在往管道中写入 buffer 中的数据。然后 server.c 以读打开管道文件,把数据往 buffer 中读,然后再打印 buffer 中的数据。

【单个进程实现方式】

结果显示:


【多个子进程实现方式】

显示结果:

当前只有一个子进程:

一个管道可以有多个读端,它是单向通信的。


6、命名管道的打开规则

如果当前打开操作是为读而打开 FIFO 时:

  • O_NONBLOCK disable:阻塞直到有相应进程为写而打开该 FIFO。
  • O_NONBLOCK enable:立刻返回成功。

如果当前打开操作是为写而打开 FIFO 时:
  • O_NONBLOCK disable:阻塞直到有相应进程为读而打开该 FIFO。
  • O_NONBLOCK enable:立刻返回失败,错误码为 ENXIO。

7、匿名管道与命名管道的比较

  1. 匿名管道是供具有血缘关系的进程进行进程间通信;命名管道可供非具有血缘关系的进程进行进程间通信。
  2. 让不同进程看到同一份资源的手段不一样,匿名管道是通过子进程继承的方式(父子共享文件的特征让进程看到同一份资源);命名管道是通过打开同一目录的方式(命名管道是文件路径具有唯一性的特征)。
  3. pipe 创建的管道文件因为没有名字,所以它只能在在内存上;fifo 创建的管道文件有名字,所以它在磁盘上,只不过不会把数据写到磁盘上。
  4. FIFO(命名管道)与 pipe(匿名管道)之间唯一的区别在它们创建与打开的方式不同,一但这些工作完成之后,它们具有相同的语义。匿名管道由 pipe 函数创建并打开,命名管道由 mkfifo 函数创建,打开用 open。


相关文章
|
20天前
|
缓存 监控 Linux
linux进程管理万字详解!!!
本文档介绍了Linux系统中进程管理、系统负载监控、内存监控和磁盘监控的基本概念和常用命令。主要内容包括: 1. **进程管理**: - **进程介绍**:程序与进程的关系、进程的生命周期、查看进程号和父进程号的方法。 - **进程监控命令**:`ps`、`pstree`、`pidof`、`top`、`htop`、`lsof`等命令的使用方法和案例。 - **进程管理命令**:控制信号、`kill`、`pkill`、`killall`、前台和后台运行、`screen`、`nohup`等命令的使用方法和案例。
53 4
linux进程管理万字详解!!!
|
11天前
|
存储 运维 监控
深入Linux基础:文件系统与进程管理详解
深入Linux基础:文件系统与进程管理详解
51 8
|
8天前
|
网络协议 Linux 虚拟化
如何在 Linux 系统中查看进程的详细信息?
如何在 Linux 系统中查看进程的详细信息?
22 1
|
8天前
|
Linux
如何在 Linux 系统中查看进程占用的内存?
如何在 Linux 系统中查看进程占用的内存?
|
19天前
|
算法 Linux 定位技术
Linux内核中的进程调度算法解析####
【10月更文挑战第29天】 本文深入剖析了Linux操作系统的心脏——内核中至关重要的组成部分之一,即进程调度机制。不同于传统的摘要概述,我们将通过一段引人入胜的故事线来揭开进程调度算法的神秘面纱,展现其背后的精妙设计与复杂逻辑,让读者仿佛跟随一位虚拟的“进程侦探”,一步步探索Linux如何高效、公平地管理众多进程,确保系统资源的最优分配与利用。 ####
53 4
|
20天前
|
缓存 负载均衡 算法
Linux内核中的进程调度算法解析####
本文深入探讨了Linux操作系统核心组件之一——进程调度器,着重分析了其采用的CFS(完全公平调度器)算法。不同于传统摘要对研究背景、方法、结果和结论的概述,本文摘要将直接揭示CFS算法的核心优势及其在现代多核处理器环境下如何实现高效、公平的资源分配,同时简要提及该算法如何优化系统响应时间和吞吐量,为读者快速构建对Linux进程调度机制的认知框架。 ####
|
22天前
|
消息中间件 存储 Linux
|
28天前
|
运维 Linux
Linux查找占用的端口,并杀死进程的简单方法
通过上述步骤和命令,您能够迅速识别并根据实际情况管理Linux系统中占用特定端口的进程。为了获得更全面的服务器管理技巧和解决方案,提供了丰富的资源和专业服务,是您提升运维技能的理想选择。
36 1
|
4月前
|
运维 关系型数据库 MySQL
掌握taskset:优化你的Linux进程,提升系统性能
在多核处理器成为现代计算标准的今天,运维人员和性能调优人员面临着如何有效利用这些处理能力的挑战。优化进程运行的位置不仅可以提高性能,还能更好地管理和分配系统资源。 其中,taskset命令是一个强大的工具,它允许管理员将进程绑定到特定的CPU核心,减少上下文切换的开销,从而提升整体效率。
掌握taskset:优化你的Linux进程,提升系统性能
|
4月前
|
弹性计算 Linux 区块链
Linux系统CPU异常占用(minerd 、tplink等挖矿进程)
Linux系统CPU异常占用(minerd 、tplink等挖矿进程)
176 4
Linux系统CPU异常占用(minerd 、tplink等挖矿进程)
下一篇
无影云桌面