登录
首页 >  文章 >  linux

【Linux进程通信】二、深度解析匿名管道

时间:2025-04-20 17:13:56 491浏览 收藏

本文详细介绍了Linux进程间通信中的匿名管道。首先阐述了管道的概念,即基于文件系统、单向连接的数据流,其本质是内核缓冲区,并非磁盘文件,从而保证高效的数据传输。随后,文章深入探讨了匿名管道的原理和创建方法,重点讲解了利用`pipe()`系统调用创建匿名管道,以及父子进程通过继承文件描述符实现共享同一缓冲区的方法,并强调了关闭相应读写端以确保单向通信的必要性。最后,文章简述了匿名管道的读写特征,为读者理解Linux进程间通信提供了清晰的思路。

Ⅰ. 管道一、管道的概念

​ 管道是 Unix 中最古老的进程间基于文件系统通信的形式。我们把从一个进程连接到另一个进程的一个数据流称为一个 “管道”。注意管道是单向连通的,不存在说双向管道,就像生活中水往低处流而不会往高处流一样!

【Linux进程通信】二、匿名管道

​ 进程 A 通过管道将数据写入到 “公共内存” 中,并且进程 B 可以从该段 “公共内存” 中读取这些数据,这样子的话就达到了两个进程之间的交互!

​ 那么有人可能会有问题:既然这段 “公共内存” 是共享的并且都是基于文件系统的,那这个管道文件是不是在磁盘上面呢,然后进程A通过写入到磁盘中的管道文件,进程B再去读取这样子的方式 ❓❓❓

​ 其实不是的,因为我们都知道,文件 IO 的效率是相当的低的,所以操作系统肯定不笨,操作系统会将这个管道文件 load 到内存中,也就是内存级别的文件,而我们两个进程之间只需要和这个内存级文件打交道即可,这样子大大的提高了效率!

​ 任何一个文件包括两套资源:

struct file 的操作方法有属于自己的内核缓冲区,所以父进程和子进程有一份公共的资源:文件系统提供的内核缓冲区,父进程可以向对应的文件的文件缓冲区写入,子进程可以通过文件缓冲区读取,此时就完成了进程间通信,这种方式提供的文件称为管道文件。管道文件本质就是内存级文件,不需要 IO

​ 管道的本质是内核中的缓冲区,通过内核缓冲区实现通信,管道文件只是一个标识符,用于让多个进程能够访问同一块缓冲区,并非通信介质。

​ 并且通过这个内存级别文件的不同形式,我们分为匿名管道和命名管道,后面我们来一一介绍!

二、管道通信原理

​ 通过我们上面所说的,管道是个 内存级别的文件(struct file 中特有的,就像内核缓冲区一样),我们要知道为什么它会叫做 “管道” 呢,是因为它一开始就叫做 “管道” 吗,那肯定不是,是因为它的原理和 “管道” 是类似的,人们后期才会将其命名为 “管道”,而管道是单通向的,不存在双向管道!

​ 并且我们生活中常见的管道比如说水管、油等等它们的作用就是来传输水和油,那么我们计算机中的管道就是传输数据,那么这个流向肯定要是一条单向的,我们就不能想象出其原理结构:

【Linux进程通信】二、匿名管道

​ 除此之外,我们可以再说说普通文件,我们父进程创建子进程,子进程拷贝父进程的文件描述符表,所以都指向 log 文件,而每次我们写入完毕之后,可能会存在写满等情况就会刷新,那么会访问磁盘,每次读取时候又将 log 加载进内存,不断的来回,这样子 IO 次数非常的多,效率也就非常的低,所以说为什么存在管道文件,其实就是为了避开普通文件通信的效率底下问题!

【Linux进程通信】二、匿名管道

Ⅱ. 匿名管道一、匿名管道的原理与创建方法

​ 在讲匿名管道之前呢,我们必须知道管道它的通信方法,而对于匿名管道来说,其实就是 通过 fork 创建子进程!(而命名管道的方法不需要创建子进程,后面会讲)

​ 为什么对于匿名管道来说需要创建子进程呢 ❓❓❓

​ 匿名管道的名称由来也是因为这个原因,我们需要通过 fork 创建子进程,我们都知道,子进程会继承父进程的大部分属性和内容包括文件描述符(若发生写时拷贝则会改变),也就是说父进程指向的文件 file,子进程也会指向同一个文件 file(父子进程文件描述符表是独立的,但是指向的文件是同一个),而就像我们下图,父子进程可以都指向该管道文件,这样子的话我们就无需说让子进程和父进程去专门创建一个带名称的管道并且指向它,也就是说我们可以 利用父子进程的继承性让子进程继承这个管道文件达到共同指向它的目的,所以这个管道文件没必要带名称,所以叫做匿名管道!

​ 所以,看待管道,就如同看待文件一样!管道的使用和文件一致,迎合了 “Linux 一切皆文件思想“!

【Linux进程通信】二、匿名管道

​ 那么我们如何创建这个匿名管道文件呢 ❓❓❓

​ 下面就得调用我们的系统函数 pipe()

代码语言:javascript代码运行次数:0运行复制
#include #include #include #include #include #include #include #include #include #include using namespace std;#define MakeSeed() srand((unsigned long)time(nullptr) ^ getpid() ^ 0x171237 ^ rand() % 1234)///中间部分为任务功能部分/typedef void(* func_t)(); // 函数指针void IOTask(){    cout * funcMap){    assert(funcMap);    funcMap->push_back(IOTask);    funcMap->push_back(DownloadTask);    funcMap->push_back(flushTask);}///以下为管道池的管理const int PROCESS_NUM = 5; // 子进程池的个数// 描述每对子进程和管道文件的结构体class childProcess{public:    childProcess(pid_t pid, int writeFd)        :_pid(pid), _writeFd(writeFd)    {        char buffer[1024];        snprintf(buffer, sizeof(buffer), "process-%d[pid(%d)|writeFd(%d)]", num++, _pid, _writeFd);        _name = buffer;    }public:    string _name; // 以统一的规则命名的名称    pid_t _pid; // 子进程pid    int _writeFd; // 管道文件的写入端    static int num; // 当前子进程为第几个进程编号};int childProcess::num = 0;void SendTask(const childProcess& cp, int index_func){    cout  " * pipePool, vector& funcMap){    vector deleteFd; // 记录每次要关闭的前面的子进程的写端    for(int i = 0; i = 0); // 断言一下是否fork成功        // 子进程的执行部分        if(id == 0)        {            // 每次删掉子进程拷贝父进程的前n个写入端指向            for(int i = 0; i = 0 && commandCode push_back(move(cp)); // 将该对象调用move移动构造到管道池        deleteFd.push_back(pipefd[1]); // 记录每个子进程的前n个写入端fd    }}void loadBlanceContrl(vector& cp, vector& fmp, int taskCnt){    int pipe_size = cp.size(); // 管道池个数    int func_size = fmp.size(); // 任务个数    bool forever = (taskCnt == 0 ? true : false); // 判断是否为永远    while(true)    {        // 1、选择一个子进程        int index_process = rand() % pipe_size;        // 2、选择一个任务        int index_func = rand() % func_size;        // 3、任务发送给选择的进程        SendTask(cp[index_process], index_func);        sleep(1);        if(!forever)        {            taskCnt--;            if(taskCnt == 0) break;           }    }    // 关闭写入端    for(int i = 0; i & cp){    for(int i = 0; i  "  funcMap;    LoadFunction(&funcMap);    vector pipePool;    CreateProcessPool(&pipePool, funcMap);    // 2、父进程控制子进程完成任务,负载均衡的向子进程发送命令码,若父进程退出则关闭子进程    int taskCnt = 3; // 0: 永远进行,其它则表示计数    loadBlanceContrl(pipePool, funcMap, taskCnt);        // 3、回收子进程信息    waitProcess(pipePool);    return 0;}

今天关于《【Linux进程通信】二、深度解析匿名管道》的内容介绍就到此结束,如果有什么疑问或者建议,可以在golang学习网公众号下多多回复交流;文中若有不正之处,也希望回复留言以告知!

相关阅读
更多>
最新阅读
更多>
课程推荐
更多>