学习系统编程No.20【进程间通信之命名管道】

简介: 学习系统编程No.20【进程间通信之命名管道】

引言:

北京时间:2023/4/15/10:34,今天起床时间9:25,睡了快8小时,昨天刷视屏刷了一个小时,本来12点的时候发完博客洗把脸就要睡了,可惜,看到了一个标题,说实话,现在的标题党是懂人性的,接下来就是无法自拔的一个小时快乐时光,但导致莫名间接熬夜,你说烦人不烦人!但是不怕,这个星期5天,几乎没有摆烂,只要今天和明天不摆烂 ,这个星期就是成功滴,一想美滋滋!所以让我们抓紧进入今天的学习吧! 今天我们就正式学习有关内存共享的知识,当然还有管道遗落的知识,因为管道真的是太经典了,但是前提是,现在我要去把我的被单和毯子给晒一晒,不然晚上无法舒舒服服的睡觉啦!并且由于昨天买的羽毛球拍到了,所以下午是打羽毛球的快乐时光,所以该篇博客,今天能不能发,是未可知的!



image.png


回顾管道控制进程

之前开始学习进程间通信的时候,我们就学习了如何使用管道来进行进程间通信,了解了通过一个管道进行进程间通信之后,我们又通过管道的读写规则了解了"血缘关系"进程之间的通信方法,此时我们通过一个 很经典的通信方式(当管道中没有数据的时候,对应读取数据的进程只能等待对应写数据的进程) ,所以通过这一重要的读写原理,此时我们就设计出了进程控制的结构,一个进程(常常是父进程)通过创建多个管道和子进程,然后向对应的管道中写入数据,进而控制与该管道文件匹配的子进程,所以明白了这个原理之后,此时就可以进行具体的编码和优化,最终得到了上篇博客所示的进程控制代码,但是该代码中,还存在一个很隐蔽的问题,上篇博客,我们没有进行详细的讲解,所以在这里,我们进行补充讲解,如下:


回收子进程

1.让子进程退出

从进程控制代码中(上篇博客),我们看出,当我们构建好了进程控制代码,可以让父进程通过对管道文件的读写规则同时去控制子进程完成不同的任务时,在最后任务完成结束,代码要退出时,我们是需要将子进程进行回收(因为子进程占用的是内存上的空间,避免内存泄露),所以此时在回收子进程的问题上,就存在那个很隐蔽的问题,此时我们就需要解决这个问题,如下:


想要解决这个问题,首先明白一点,就是如何让子进程退出,这点想必大家都很清楚(上篇博客详细介绍了),想要让一个子进程退出(场景:上篇博客代码,子进程读取管道文件,父进程向管道文件写入),只要让父进程无法向对应子进程的管道文件中写入数据,此时操作系统就会因为不允许资源浪费,使 用13号信号,让子进程退出,所以让一个子进程退出,就是将对应管道文件的写端关闭就行


明白了这点,此时我们距离解决这个隐蔽问题就剩下一个现象,看到了这个现象,此时一切都是So,So,如下图所示:


107.png

如上图所示,明白到了,除了第一次循环的子进程是只继承了一个写文件描述符,其它子进程的写文件描述符都是在累加,所以导致除了最后一次循环创建的那个管道文件的写端只有父进程指向之外(因为最后一次循环子进程为了构建单向信道,需要把写端关闭),其它的管道文件都被除了父进程以外的一个或多个进程以写的方式指向(如上图,第五次循环之后的子进程,此时对前面创建的4个管道文件的写端都有写入的能力),具体如下图所示:


108.png

所以上图的第一个管道文件的写端由于被不止一个进程打开,所以此时单单只是将父进程对应的写端关闭,还有剩下多个子进程可以向该管道文件写入数据,所以此时操作系统并不会使用13号信号,将该子进程退出,所以最好的解决方法就是如上图所示:

从后向前关闭父进程对应管道文件的写端,这样就可以让子进程挨个退出了,进而最终达到回收子进程的目的


有关上述现象图的继承理解:

注意: 在常见的操作系统中,当子进程从父进程中继承文件描述符表时,子进程会得到指向同一文件表项的新的文件描述符副本(继承),关闭一个文件描述符只会减少该文件描述符所指向的文件表项的引用计数,如果仍然有其他文件描述符指向该文件表项,则该文件表项仍然存在;因此,无论父进程关闭其中的一个文件描述符或者两个,当子进程从它那里继承文件描述符表时,子进程都会得到对这两个打开文件的文件描述符


总结: 只要父进程分别以读写的方式打开了同一文件,创建了两个文件描述符,那么此时无论父进程是关闭一个文件描述符,还是两个都关闭,此时由于子进程继承了父进程pcb大部分的内容,并且因为进程间具有独立性,所以此时操作系统检测都父进程将要关闭文件描述符的时候(也就是修改数据的时候),就会进行写实拷贝(在内存中重新开辟一个空间给父进程去修改数据),所以导致子进程中继承的父进程pcb是不会被改变,所以子进程任然可以分别继承以读写方式打开的两个文件描述符


2.等待僵尸状态的子进程

明白了上述知识点之后,此时就可以明白除了最后一次循环创建的管道文件只被一个进程以写的方式打开之外,其它的管道文件都是同时被两个或者两个以上的进程以写的方式打开(父进程和子进程),所以最好的方法就是从后向前将父进程对应管道文件的写端关闭,此时所有的子进程就被很好的关闭了,所以当子进程被关闭之后,此时最后一步就是将处于僵尸状态的子进程回收(waitpid),具体代码如下:


0.png

具体现象如下:

1.png

2.png


所以此时因为进程控制创建出的被控制进程和创建的管道文件就都被关闭和回收啦!利用管道控制进程的代码就大功告成啦! 具体代码贴在该博客最后(虽然上篇博客中有)


深入进程控制结构

通过上述的知识,我们知道,我们可以很好的利用进程间通信的知识和模板去构建出一个进程控制的结构,并且这个结构中存在的问题(上述问题),因为子进程会继承上一进程打开的所有文件描述符,导致一个子进程同时具有多个读端和写端,此时根据这个现象,就会存在很多的问题,不仅仅只是上述回收子进程的时候需要从后向前回收,更重要的是 ,此时还有引起另一个严重问题,就是后一个子进程也可以向前一个管道文件中写入数据,有甚者更是可以向多个管道文件中写入数据(虽然因为没有特定的文件描述符,子进程并不能写入,但是为了防止),所以需要避免这个问题,此时我们就可以通过将代码结构进行一定的改变,进而真正的构建出像进程间通信一般的单向信道,如下:


原理:本质上,在实现上述进程控制的前提下,让每一个管道和进程之间都互不干扰,实现单向通信是不难的,实际从问题出发,就是要解决子进程继承父进程文件描述符时,多余进程的文件描述符而已,所以此时我们只需要再创建一个数组,用这个数组专门来保存父进程创建的写端文件描述符,得到父进程所有的写端文件描述符,然后把这个数组中的数据拿给子进程使用,间接在子进程中遍历这个数组,并且使用close关闭子进程继承的文件描述符表中对应数组下标对应的写端文件描述符,这样子进程中多余进程到的写端文件描述符就被全部关闭,最终真正的实现单向信道进程控制结构


具体代码实现如下:


2.png


命名管道

无论是之前的进程间数据传送的知识,还是上述进程控制的知识,本质上都只是在利用管道进行而已,准确的来说也就是利用一个内存级的文件而已,并且要明白,此时的这个管道是使用系统调用接口 pipe 来创建的,所以本质上这个管道文件是操作系统给我们提供的,我们是摸不着,看不到的,所以准确的来说,我们之前学习的进程数据传送和进程控制利用的管道都是匿名管道,一个由内核创建,操作系统管理的文件


并且还可以得出一个结论,我们一直利用的都是具有"血缘关系"的进程可以继承同一文件描述符的特性和同时以读写方式打开同一匿名管道文件进行学习有关进程间通信的知识,此时提出问题:那么如果是两个完全没有关系的进程,此时进程间还可以进行通信吗?首先答案是可以的,不然它怎么能叫进程间通信呢?就应该叫血缘关系进程进程间通信了,具体如下述所说:


如何创建命名管道

首先明白,想要让两个不同的进程支持相互通信,那么此时就不可以使用匿名管道,而要用命名管道,所以下面第一个知识点,我们就来了解一下命名管道的创建,注意:命名管道的创建区别于匿名管道的创建,它不仅可以直接使用命令行创建,指令:mkfifo filename,也可以使用系统调用创建,调用接口:int mkfifo(const char *filename,mode_t mode);基本使用:mkfifo("filename", 0644);具体使用方式如下图所示:


3.png

通过上图中对 mkfifo 系统调用接口的描述,此时我们就可以知道,该接口的功能就是用来创建一个命名管道,头文件为#include<sys/types.h>,#include<sys/stat.h>等具体使用方式,此时我们就可以很好的自己创建一个命名管道出来啦!


注意: 虽然,mkfifo是创建一个文件,并且这个文件不像是匿名管道文件一样是由内核创建的内存级文件(不占用磁盘空间),并且该文件是位于文件系统中,但该文件却并不对应任何物理磁盘上的文件,而只是在内存中分配一个空间来存储不同进程之间进行进程间通信时的读取或写入的数据而已(类似于文件对象自带的缓冲区),所以从这个意义上来说,我们可以将mkfifo创建的文件视为一种内存级文件,因为它们的存在仅限于进程之间的通信,当进程间完成通信之后,回收进程时,此时由于它们的生命周期与它们关联的进程相同,所以当这些进程终止时,这些文件也会被自动删除,所以本质上命名管道文件就是一个内存级缓冲区文件!


得出结论: 使用 mkfifo 创建的文件并不会占用磁盘空间,因为它只是一个命名管道(内存级文件),只有在进程进行通信时,也就是一个进程打开一个命名管道并向其中写入数据,另一个进程打开同一个命名管道并从中读取数据,此时才会产生实际的数据传输和占用磁盘空间,否则执行 mkfifo 命令只会在文件系统中创建一个新的文件节点,并不会分配任何实际的磁盘空间。


管道文件类型:


4.png


对文件类型和文件属性感兴趣的同学可以参考该链接博客:文件类型详解


文件类型
普通文件 -
目录文件 d
链接文件 l
块设备 b
字符设备 c
管道文件 p
套接字文件 s


深入命名管道

搞定了上述有关命名管道文件的知识,此时我们就可以具体的来看一看,是如何利用命名管道实现不同进程之间的通信了,如下图所示:


5.png


如上图所示:此时我们就构建出了一个和构建父子进程间通信结构类似的模板,此时按照这个原理,我们就可以进行代码的编写啦!简单理解就是两个进程打开了同一个文件,一个进程以读的方式打开,一个进程以写的方式打开,本质能够通信还是因为,操作系统为了节约资源,所有进程共享同一个已经打开的文件,而不是重复打开,并且 注意, 如果我们想要让不同的两个进程打开同一个文件,此时就需要让它们根据同一路径去寻找该对应文件**,所以只要让不同的进程通过文件路径和文件名看到同一文件,并打开,这就是两个不同进程看到同一份资源的前提,也就是进程间通信的前提!


代码实现

原理: 1.创建一个管道文件 2.让读写端进程分别按照自己的需求打开文件 3.开始通信

首先创建两个文件,一个serves.cpp,一个client.cpp,用来分别表示两个不一样的进程,具体代码如下:


6.png


client.cpp文件

7.png

command.hpp共享文件

8.png



两个进程代码通信现象:


8.png如上图所示,此时一个进程就可以很好的把数据传送给另一个进程,也就类似于一个用户端可以把数据传送给服务端

总:明白了使用匿名管道的进程控制,玩一个命名管道So,So!


image.png


总结:快乐生活每一天,有关管道的知识就这样了吧!

使用匿名管道进行进程控制代码如下:


10.png



相关文章
|
28天前
|
运维 关系型数据库 MySQL
掌握taskset:优化你的Linux进程,提升系统性能
在多核处理器成为现代计算标准的今天,运维人员和性能调优人员面临着如何有效利用这些处理能力的挑战。优化进程运行的位置不仅可以提高性能,还能更好地管理和分配系统资源。 其中,taskset命令是一个强大的工具,它允许管理员将进程绑定到特定的CPU核心,减少上下文切换的开销,从而提升整体效率。
掌握taskset:优化你的Linux进程,提升系统性能
|
23天前
|
弹性计算 Linux 区块链
Linux系统CPU异常占用(minerd 、tplink等挖矿进程)
Linux系统CPU异常占用(minerd 、tplink等挖矿进程)
30 4
Linux系统CPU异常占用(minerd 、tplink等挖矿进程)
|
18天前
|
安全 开发者 Python
揭秘Python IPC:进程间的秘密对话,让你的系统编程更上一层楼
【8月更文挑战第1天】在系统编程中, 进程间通信 (IPC) 是连接独立进程的关键技术, 提升了系统的并发性和灵活性。Python 提供了丰富的 IPC 机制, 包括管道 (`Pipe`), 队列 (`Queue`), 共享内存 (`Value`, `Array`) 和套接字 (`Sockets`)。这些机制支持不同的应用场景, 如简单的父子进程通信或复杂的分布式系统构建。合理选择 IPC 方法可帮助开发者构建高效、可靠的多进程应用, 但同时也需注意同步和数据一致性等问题。
30 1
|
26天前
|
算法 调度 UED
操作系统中的进程调度策略及其对系统性能的影响
本文深入探讨了操作系统中进程调度的多种策略,包括先来先服务、短作业优先、优先级调度、轮转与多级队列等,并分析了它们对系统性能的具体影响。通过比较不同调度算法的效率和公平性,本文旨在为系统管理员提供选择合适调度策略的依据,以优化系统的整体表现。
|
16天前
|
Python
Python IPC深度探索:解锁跨进程通信的无限可能,以管道与队列为翼,让你的应用跨越边界,无缝协作,震撼登场
【8月更文挑战第3天】Python IPC大揭秘:解锁进程间通信新姿势,让你的应用无界连接
13 0
|
19天前
|
消息中间件 存储 网络协议
从零开始掌握进程间通信:管道、信号、消息队列、共享内存大揭秘
在操作系统中,进程间通信(IPC)是至关重要的,它提供了多种机制来实现不同进程间的数据交换和同步。本篇文章将详细介绍几种常见的IPC方式,包括管道、信号、消息队列、共享内存、信号量和套接字,帮助你深入理解并合理应用这些通信方式,提高系统性能与可靠性。
39 0
|
2月前
|
监控 Linux 应用服务中间件
探索Linux中的`ps`命令:进程监控与分析的利器
探索Linux中的`ps`命令:进程监控与分析的利器
|
18天前
|
算法 Linux 调度
探索进程调度:Linux内核中的完全公平调度器
【8月更文挑战第2天】在操作系统的心脏——内核中,进程调度算法扮演着至关重要的角色。本文将深入探讨Linux内核中的完全公平调度器(Completely Fair Scheduler, CFS),一个旨在提供公平时间分配给所有进程的调度器。我们将通过代码示例,理解CFS如何管理运行队列、选择下一个运行进程以及如何对实时负载进行响应。文章将揭示CFS的设计哲学,并展示其如何在现代多任务计算环境中实现高效的资源分配。
|
1月前
|
存储 缓存 安全
【Linux】冯诺依曼体系结构与操作系统及其进程
【Linux】冯诺依曼体系结构与操作系统及其进程
132 1
|
1月前
|
小程序 Linux
【编程小实验】利用Linux fork()与文件I/O:父进程与子进程协同实现高效cp命令(前半文件与后半文件并行复制)
这个小程序是在文件IO的基础上去结合父子进程的一个使用,利用父子进程相互独立的特点实现对数据不同的操作