Linux——匿名管道、命名管道及进程池概念和实现原理

news2024/12/23 22:29:54

目录

 一.什么是匿名管道

二.如何使用匿名管道

(一).pipe原理

(二).pipe使用

三.命名管道概念及区别

(一).什么是命名管道

(二).与匿名管道的联系和区别

四.命名管道的使用

(一).系统指令

(二).mkfifo 

五.进程池

(一).概念与原理

(二).代码原理与分析

(三).进程池管道陷阱


 一.什么是匿名管道

匿名管道是linux中一种非常古老进程间通信方式,本质上就是一个内存级的文件。

一般用于父子进程间通信。概念上就是父进程与子进程共同使用一个管道文件来传输数据。

虽然父子进程都有对管道的读和写功能,但在使用时只能读或者写,因此管道是单向通信,半双工模式。

父进程把数据写入管道,子进程从管道中读取:

二.如何使用匿名管道

(一).pipe原理

linux为我们提供了系统接口pipe,用于创建管道进行通信。

 参数是长度为2的整形数组,pipefd[0]代表读端文件描述符,pipefd[1]代表写端文件描述符

返回值是int,创建成功返回0,失败返回-1,同时记录进errno。 

pipe的使用原理上,就是首先父进程创建一个管道文件,但同时赋予管道文件两个文件描述符。

一个是以读方式打开即pipefd[0],另一个是以写方式打开即pipefd[1]。

之后创建子进程,由于子进程会对父进程进行拷贝,会把父进程的fd_array同时拷贝一份,也就拥有了管道对应的两个文件描述符(读端&写端)。

图示如下:

(二).pipe使用

以下面代码为例:

父进程使用write接口将字符串给管道,子进程从管道中接收字符串并打印。

同时,子进程的read系统接口会阻塞,直到父进程往管道中写完数据,read一次性将此时管道内数据读取完并清空管道

当父进程关闭管道后,若管道中还有数据时read函数会一次性读取完并在下一次读取时返回0,没有数据时直接返回0。

#include<iostream>
#include<cstdio>
#include<unistd.h>
#include<string>
#include<assert.h>
using namespace std;
int main()
{
  int pfd[2] = { 0 };
  int ret = pipe(pfd);
  assert(ret == 0);
  pid_t id = fork();
  assert(id >= 0);
  if(id == 0)
  {
    close(pfd[1]);//关闭写端
    char GetStr[1024] = { 0 };
    ssize_t i = read(pfd[0], GetStr, sizeof GetStr);//接收数据
    GetStr[i] = '\0';
    cout << GetStr << endl;
    exit(0);
  }
  //父进程
  close(pfd[0]);
  char str[1024] = "hello world";
  write(pfd[1], str, sizeof str);//发送数据

  return 0;
}

三.命名管道概念及区别

(一).什么是命名管道

顾名思义,这是有名字的管道。它以文件的形式存在于系统中,在磁盘中有对应的节点但没有为其分配数据块(block)。

换一种说法,它是能被我们看到(看到名字)且有inode编号的文件,但是inode中记录的block数量为0,使用时只能使用内存空间。

(二).与匿名管道的联系和区别

和匿名管道一样,本质上都是利用内存空间进行通信。

都是半双工通信,任意时刻只能向一端发送数据

管道生命周期不同,匿名管道伴随进程,命名管道只要不删除一直存在。

通信对象不同,匿名管道一般用于父子进程通信,命名管道可用于任意进程间通信。

创建管道的方式不同,匿名管道使用pipe,命名管道使用mkfifo 

四.命名管道的使用

(一).系统指令

系统指令方式:$mkfifo 路径+管道名

 演示:

(二).mkfifo 

代码方式:int mkfifo(const char *pathname, mode_t mode)

自制翻译:int mkfifo(文件路径+管道名,文件权限);

头文件:<sys/types.h>、<sys/stat.h>

返回值int:成功返回0,失败返回-1并在errno中记录。

文件权限:用于规定管道文件读写权限, 实际权限=设定权限&~umask。

                  比如0666实际上就是:0666 & ~0002(umask == 0002)即0661。

因为命名管道是一个文件,使用方式与文件的使用方式相同,但如果只打开了读端或写端,该端口就会阻塞,直到写端或读端打开为止。

使用方式:

//读端
int i = mkfifo("./fifo.ipc", 0666);//创建命名管道
assert(i >= 0);
int fd = open("./fifo.ipc", O_RDONLY);//打开管道读端,O_RDONLY:只读方式打开
. . .
char buf[1024] = { '\0' };
ssize_t s  = read(fd, buf, sizeof buf);//读取数据
. . .
close(fd);//关闭读端管道


//写端
int fd = open("./fifo.ipc", O_WRONLY);打开管道写端,O_WRONLY:只写方式打开
. . .
std::string buf;
std::getline(std::cin, buf);
ssize_t s  = write(fd, buf.c_str(), buf.size());//向管道写入数据
. . .
close(fd);//关闭写端管道

五.进程池

(一).概念与原理

进程池可以简单理解为父进程调度多个子进程完成不同的任务。类似于人脑与四肢的关系。

我们可以自制一个简易的进程池。

首先利用父进程fork多个子进程,每次fork之前都先使用pipe创建与这个子进程用于联系的管道

同时记录子进程的pid和其对应的读端文件描述符(用于向其中传输数据与回收子进程)。

之后调度某个子进程传输相关数据。

(二).代码原理与分析

在这份代码中,模拟实现5个函数,父进程会随机选择任意一个子进程调用任意一个函数。

子进程调用系统read函数接收数据并调用对应的模拟函数。

为了提高英语能力,小编选择使用英文注释😂。

//"Command.h"
#pragma once
#include<iostream>
#include<cstdio>
#include<vector>
#include<functional>
using namespace std;
typedef function<void()> func;
vector<func> callCommand;
void Running()
{
  cout << "Running now" << endl;
}
void Writing()
{
  cout << "Writing now" << endl;
}
void Eating()
{
  cout << "Eating now" << endl;
}
void Sleeping()
{
  cout << "Sleeping now" << endl;
}
void Testing()
{
  cout << "Testing now" << endl;
}

void CommandInit()
{
  callCommand.push_back(Running);
  callCommand.push_back(Writing);
  callCommand.push_back(Eating);
  callCommand.push_back(Sleeping);
  callCommand.push_back(Testing);
}
void ShowAllCommand()
{
  //... if you want you can write one ^-^
}
#include<cstdlib>
#include<iostream>
#include<cstdio>
#include<vector>
#include<unistd.h>
#include<sys/wait.h>
#include<sys/fcntl.h>
#include<assert.h>
#include"Command.h"
using namespace std;
#define PROCESS_NUM 5 //number of process is 5

int main()
{
  CommandInit(); //init for Command function

  vector<pair<pid_t, int>> KvPidFd; //record children process pid & read file fd

  for(int i = 0; i < PROCESS_NUM; i++) //creat child process
  {
    int pipefd[2] = { 0 };
    int k = pipe(pipefd);
    assert(k == 0); //-1 : creat false
    pid_t id = fork();

    if(id == 0) //child
    {
      close(pipefd[1]);
      while(1)
      {
        uint32_t accept = -1;  //uint32_t : unsigned int in 32bit
        cout << "*********" << getpid() << endl; 
 
        int s = read(pipefd[0], &accept, sizeof accept);
        if(s == 0) break; //if s is zero, that's mean child read zero word in pipe

        assert(accept >= 0);
        cout << "I am child " << getpid() << " now accept command : ";
        callCommand[accept]();  //invoke function
      }
    
      cout << "child " << getpid() << "finished work !" << endl;
      close(pipefd[0]);
      exit(0);
    }
    
    //only father process can go to this step
    //take chiid pid & it's write fd as a mapping
    close(pipefd[0]);
    KvPidFd.push_back(make_pair(id, pipefd[1]));
  }

  srand((unsigned int)time(nullptr) * getpid() * 131);
  uint32_t command = 0;
  int proc = -1;
  int count = 0;

  while(1) //father
  {
    sleep(1); 
    //random distribute function which will be commanded 
    command = rand() % callCommand.size(); 
    assert(command >= 0);
    proc = rand() % PROCESS_NUM; //random distribute process which will be used 
    assert(proc >= 0);
    write(KvPidFd[proc].second, &command, sizeof(command));
    count++;
    if(count == 5) break;//stop this loop when calling child five times
  }
 

 //close pipe files & revoke children
  for(auto kv : KvPidFd)
  {
    close(kv.second);
  }

  for(auto kv : KvPidFd)
  {
    waitpid(kv.first, nullptr, 0);
  }

  return 0;
}

(三).进程池管道陷阱

 有人可能有疑惑,这里有什么陷阱呢?

嗯。。如果父进程在关闭写端管道同时关闭相应子进程呢

看似没有问题?

//close pipe files & revoke children
  for(auto kv : KvPidFd)
  {
    close(kv.second);
    pid_t id = waitpid(kv.first, nullptr, 0);
    assert(id > 0);
    cout << "From father : child " << id << "finish work" << endl;
  }

但是程序直接夯住了!

 不要着急,我们来仔细梳理一下父进程与子进程的管道关系就能得到答案。

当父进程创建子进程1前,先创建了管道1,子进程1与父进程的fd_array都会记录管道1的fd。

当父进程创建子进程2前,先创建了管道2,子进程2与父进程fd_array都会记录管道2的fd。但是,子进程2的fd_array中也会记录管道1的fd

是的,后面创建的子进程不仅会记录自己管道的fd,也会记录之前创建的管道fd,准确来讲是继承之前管道的写端

于是,当父进程关闭管道写端后,管道写端并没有被全部关闭,read函数会一直阻塞,进而根本无法执行到waitpid的步骤。

因此,只有将父进程所有管道的写端都关闭后,首先是最后创建的子进程完成read返回0的操作,因为对它而言,有它管道的写端的只有父进程。当最后一个子进程关闭后,倒数第二个开始关闭,...直到第一个关闭。

所以,我们这是使用关闭管道和回收子进程分开的方式进行。

图示如下:

 

如果你是房间里最聪明的人,那么你走错房间了——未名


如有错误,敬请斧正 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/59372.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Python——分支语句

1.bool 数据类型&#xff1a;真和假&#xff0c;只有两个值&#xff0c;就是True和False。 2.if语句使用的语法&#xff1a; 3.else语句&#xff1a;&#xff08;同上&#xff09; 4.比较运算符&#xff1a; a b&#xff1a;a和b是否相等 a ! b&#xff1a; a和b是否不相等 a…

【网络层】子网划分、无分类编址CIDR、构成超网、ARP协议

注&#xff1a;最后有面试挑战&#xff0c;看看自己掌握了吗 文章目录子网划分-----减少浪费IP----两级IP不够灵活-----变三级IP地址-----对外还是表现以前的网络号---------只是拿出部分主机号来做子网号子网划分实例-------对外不展示内部的子网划分----子网掩码---与运算---…

什么是【固件】?

文章目录一、软件 硬件 固件二、BIOS&#xff08;Basic Input/output System&#xff09;三、百度百科的解释四、固件的工作原理五、应用六、参考链接一、软件 硬件 固件 通常我们会将硬件和软件分开看待&#xff0c;二者协同工作为我们提供计算机的体验。硬件是摸得着的实体&…

[附源码]计算机毕业设计学生宿舍维修管理系统Springboot程序

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

假设检验(1)-假设检验的基本概念

数理统计的另一基本任务是对总体参数作某种假设&#xff0c;然后根据所得的样本&#xff0c;运用统计分析的方法来检验这一假设是否成立&#xff0c;从而作出接受或拒绝的决定&#xff0e; 这就是假设检验问题&#xff0e; 3. 1. 1 假设检验的基本思想和推理方法 我们先举一个…

C\C++刷题DAY5

目录 1.第一题 2.第二题 3.第三题 1.第一题 160. 相交链表 - 力扣&#xff08;LeetCode&#xff09; 思路分析&#xff1a; 看链表相不相交&#xff0c;是看链表的地址。把两个链表的地址一一比对&#xff0c;如有有相同的地址&#xff0c;那么相交&#xff0c;如果各不相同…

详解自监督发展趋势! 何恺明连获三年CVPR最高引用的秘诀是?

点击文末公众号卡片&#xff0c;不错过计算机会议投稿信息 0 引言 许多加了我好友的读者知道尼谟之前的研究方向是“自监督学习”&#xff0c;而最近我无意中发现&#xff0c;CVPR最近三年引用量最高的论文竟然都是来自监督学习领域的&#xff0c;且三篇论文的作者都包括Face…

(四)进程管理:进程基本概念

文章目录一. 进程的概念二. 进程的结构和特征1. 进程的结构2. 进程的特征三. 进程与【线程】1. 进程与线程的关系与区别2. 线程的实现方式1. 纯用户级方式&#xff08;淘汰&#xff09;2. 纯内核级方式3. 组合方式一. 进程的概念 进程&#xff08;Process&#xff09;&#xff…

C#,彩色图片转为灰度图的快速算法与源代码

彩色图转为灰度图的场景非常多&#xff0c;比如人工智能的训练与识别时&#xff0c;需要将彩色图片转为灰度图。 以下文字来自于&#xff1a; 彩色图像转灰度图像原理python_蜗牛的笨笨的博客-CSDN博客_python 彩色图转灰度图现在我们所接触到的图像绝大多数都是数字图像&…

[附源码]计算机毕业设计springboot疫情背景下社区互助服务系统

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

u-boot常用命令

u-boot常用命令查看u-boot所支持的命令查询命令u-boot版本环境变量板子相关信息环境变量操作内存操作网络操作EMMC和 SD卡操作FAT 格式文件系统操作EXT格式文件系统操作ubi格式文件系统操作boot 操作bootzbootmbootUMS 命令常用其他uboot环境变量&#xff1a;bootcmd和bootargs…

python基础语法15-网络编程理论

网络编程是指在程序中实现两台计算机之间的通信。 Python提供了大量的内置模块和第三方模块用于支持各种网络访问&#xff0c;而且Python语言在网络通信方面的优点特别突出&#xff0c;远远领先其他语言。 一、IP: 1.概念&#xff1a; IP:互联网协议地址(Internet Protocol Add…

CCNA-应试教育-思科网院-CCNAv7: Switching, Routing, and Wireless Essentials 交换、路由和无线基础。

CCNA-应试教育-思科网院-CCNAv7: Switching, Routing, and Wireless Essentials 交换、路由和无线基础。 拓扑 – SRWE 最终 PT 技能评估 &#xff08;PTSA&#xff09; 拓扑图这样连 题目 SRWE Final PT Skills Assessment (PTSA) A few things to keep in mind while co…

大淘营淘宝复制的“添加、删除、替换属性”功能如何使用?

一、添加属性添加属性填写的格式为“属性名称:属性值”&#xff0c;“”是添加的意思。举个例子&#xff0c;比如我要添加的属性为“AA”&#xff0c;属性值为“aa”&#xff0c;那么&#xff0c;在软件上需要填“AA:aa”。注&#xff1a;“:”是半角字符&#xff0c;中间不要有…

嵌入式 程序调试之gdb和gdbserver的交叉编译及使用

嵌入式 程序调试之gdb和gdbserver的交叉编译及使用 一、简述 记--交叉编译gdb、gdbserver并调试嵌入式程序。 gdb是功能非常强大的常用调试工具&#xff0c;可以直接下断点进行单步调试&#xff0c;是差错排错的利器。 常见三种不同使用场景的gdb: x86 pc端gdb&#xff08;以下…

MyBatis ---- 自定义映射resultMap

MyBatis ---- 自定义映射resultMap1. resultMap处理字段和属性的映射关系2. 多对一映射处理a>级联方式处理映射关系b>使用association处理映射关系c>分布查询3. 一对多映射处理a>collectionb>分步查询1. resultMap处理字段和属性的映射关系 当实体类中的属性名…

一文详解数据链路相关技术

一文详解数据链路相关技术1.MAC地址2.共享介质型网络争用方式令牌传递3.非共享介质网络4.环路检测技术生成树方式源路由法5.VLAN1.MAC地址 MAC地址直译为媒体存取控制位址&#xff0c;也称为局域网地址&#xff08;LAN Address&#xff09;&#xff0c;MAC位址&#xff0c;以太…

云服务器使用及Linux基本命令

文章目录前言一、Linux1.Linux发现版本2.Linux环境搭建方式云服务器使用终端软件连接Linux3.Linux基础命令&#xff08;1&#xff09;ls:列出该目录下的所有子目录与文件。&#xff08;2&#xff09;pwd&#xff1a;显示当前所在用户&#xff08;3&#xff09;cd &#xff1a;改…

终于来了

程序员求职简历&#xff0c;项目经验怎么写&#xff1f;免费修改简历、提供模板并内部推荐昨天我还在说"三年了&#xff0c;乌云还未散尽&#xff0c;仿佛若有光"。今天一大早&#xff0c;光就照进来了。深圳卫健委宣布公交、地铁、药店、公园、旅游景点等场所都不需…

数据结构(王卓)(4)附:链表的销毁与清空

销毁 Status 销毁单链表(LinkList L) {LinkList p;while (L){p L;L->next;delete p;}return OK; } 运行逻辑&#xff1a; &#xff08;1&#xff09;:设定一个指针&#xff0c;让指针指向链表的头指针L &#xff08;2&#xff09;&#xff1a;让头指针等于头指针里面指向下…