【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。


相关文章
|
10天前
|
存储 缓存 监控
Linux缓存管理:如何安全地清理系统缓存
在Linux系统中,内存管理至关重要。本文详细介绍了如何安全地清理系统缓存,特别是通过使用`/proc/sys/vm/drop_caches`接口。内容包括清理缓存的原因、步骤、注意事项和最佳实践,帮助你在必要时优化系统性能。
128 78
|
9天前
|
存储 监控 Linux
嵌入式Linux系统编程 — 5.3 times、clock函数获取进程时间
在嵌入式Linux系统编程中,`times`和 `clock`函数是获取进程时间的两个重要工具。`times`函数提供了更详细的进程和子进程时间信息,而 `clock`函数则提供了更简单的处理器时间获取方法。根据具体需求选择合适的函数,可以更有效地进行性能分析和资源管理。通过本文的介绍,希望能帮助您更好地理解和使用这两个函数,提高嵌入式系统编程的效率和效果。
62 13
|
10天前
|
Ubuntu Linux C++
Win10系统上直接使用linux子系统教程(仅需五步!超简单,快速上手)
本文介绍了如何在Windows 10上安装并使用Linux子系统。首先,通过应用商店安装Windows Terminal和Linux系统(如Ubuntu)。接着,在控制面板中启用“适用于Linux的Windows子系统”并重启电脑。最后,在Windows Terminal中选择安装的Linux系统即可开始使用。文中还提供了注意事项和进一步配置的链接。
28 0
|
6月前
|
监控 Linux 应用服务中间件
探索Linux中的`ps`命令:进程监控与分析的利器
探索Linux中的`ps`命令:进程监控与分析的利器
139 13
|
5月前
|
运维 关系型数据库 MySQL
掌握taskset:优化你的Linux进程,提升系统性能
在多核处理器成为现代计算标准的今天,运维人员和性能调优人员面临着如何有效利用这些处理能力的挑战。优化进程运行的位置不仅可以提高性能,还能更好地管理和分配系统资源。 其中,taskset命令是一个强大的工具,它允许管理员将进程绑定到特定的CPU核心,减少上下文切换的开销,从而提升整体效率。
掌握taskset:优化你的Linux进程,提升系统性能
|
5月前
|
弹性计算 Linux 区块链
Linux系统CPU异常占用(minerd 、tplink等挖矿进程)
Linux系统CPU异常占用(minerd 、tplink等挖矿进程)
192 4
Linux系统CPU异常占用(minerd 、tplink等挖矿进程)
|
4月前
|
算法 Linux 调度
探索进程调度:Linux内核中的完全公平调度器
【8月更文挑战第2天】在操作系统的心脏——内核中,进程调度算法扮演着至关重要的角色。本文将深入探讨Linux内核中的完全公平调度器(Completely Fair Scheduler, CFS),一个旨在提供公平时间分配给所有进程的调度器。我们将通过代码示例,理解CFS如何管理运行队列、选择下一个运行进程以及如何对实时负载进行响应。文章将揭示CFS的设计哲学,并展示其如何在现代多任务计算环境中实现高效的资源分配。
|
5月前
|
存储 缓存 安全
【Linux】冯诺依曼体系结构与操作系统及其进程
【Linux】冯诺依曼体系结构与操作系统及其进程
180 1
|
5月前
|
小程序 Linux
【编程小实验】利用Linux fork()与文件I/O:父进程与子进程协同实现高效cp命令(前半文件与后半文件并行复制)
这个小程序是在文件IO的基础上去结合父子进程的一个使用,利用父子进程相互独立的特点实现对数据不同的操作
130 2
|
5月前
|
SQL 自然语言处理 网络协议
【Linux开发实战指南】基于TCP、进程数据结构与SQL数据库:构建在线云词典系统(含注册、登录、查询、历史记录管理功能及源码分享)
TCP(Transmission Control Protocol)连接是互联网上最常用的一种面向连接、可靠的、基于字节流的传输层通信协议。建立TCP连接需要经过著名的“三次握手”过程: 1. SYN(同步序列编号):客户端发送一个SYN包给服务器,并进入SYN_SEND状态,等待服务器确认。 2. SYN-ACK:服务器收到SYN包后,回应一个SYN-ACK(SYN+ACKnowledgment)包,告诉客户端其接收到了请求,并同意建立连接,此时服务器进入SYN_RECV状态。 3. ACK(确认字符):客户端收到服务器的SYN-ACK包后,发送一个ACK包给服务器,确认收到了服务器的确
202 1