Linux-管道

目录

  • 无名管道
    • 关闭未使用的管道文件描述符
  • 管道对应的内存大小
  • 与shell命令进行通信(popen)
  • 命名管道FIFO
    • 创建FIFO文件
    • 打开FIFO文件

无名管道

管道是最早出现的进程间通信的手段。

管道的作用是在有亲缘关系的进程之间传递消息。所谓有亲缘关系,是指有一个共同的祖先。所以管道并非只能用于父子进程之间,也可以用在兄弟进程之间,还可以用于祖孙进程之间甚至是叔侄进程之间。

管道的本质是内核维护了一块缓冲区与管道文件相关联,对管道文件的操作,被内核转换成对这块缓冲区内存的操作。

在Linux下,可以使用如下接口创建管道:

#include <unistd.h>
int pipe(int pipefd[2]);

如果成功,则返回值是0,如果失败,则返回值是-1,并且设置errno。

errno原因
EMFILE该进程使用的文件描述符已经多于MAX_OPEN-2
ENFILE系统中同时打开的文件已经超过了系统的限制
EFAULTpipefd参数不合法

成功调用pipe函数之后,会返回两个打开的文件描述符,一个是管道的读取端描述符pipefd[0],另一个是管道的写入端描述符pipefd[1]。

不应该对读取端描述符调用写操作,也不应该对写入端描述符调用读操作。

对读取端描述符执行write操作,内核就会执行bad_pipe_w函数;对写入端描述符执行read操作,内核就会执行bad_pipe_r函数。这两个函数比较简单,都是直接返回-EBADF。因此对应的read和write调用都会失败,返回-1,并置errno为EBADF。

// 举例:父进程关闭读端进行写数据,子进程关闭写端进行读数据#include <iostream>
#include <unistd.h>
#include <sys/wait.h>
#include <stdlib.h>
#include <strings.h>int main()
{// 管道的文件描述符int pipefd[2];// 创建无名管道if (pipe(pipefd) == -1){perror("pipe");return -1;}int pid = fork();if (pid < 0){std::cout << "创建子进程失败" << std::endl;return -1;}else if (pid == 0){// 子进程关闭写端close(pipefd[1]);char buf[50];while (1){read(pipefd[0], buf, 50);std::cout << buf << std::endl;}}else if (pid > 0){// 父进程关闭读端close(pipefd[0]);std::string msg = "hello, child process!";while (1){write(pipefd[1], msg.c_str(), msg.size());sleep(1);}waitpid(pid, nullptr, 0);}
}[root@Zhn 管道]# g++ pipe.cpp -o pipe
[root@Zhn 管道]# ./pipe
hello, child process!
hello, child process!
hello, child process!
hello, child process!
hello, child process!
hello, child process!
hello, child process!
hello, child process!
hello, child process!
hello, child process!
hello, child process!
^C
[root@Zhn 管道]# 

在这里插入图片描述
在这里插入图片描述


这么做不仅仅是为了让数据的流向更加清晰,也不仅仅是为了节省文件描述符,更重要的原因是:关闭未使用的管道文件描述符对管道的正确使用影响重大。

以上是父子进程之间通信,也可以兄弟进程之间通信。

步骤就是父进程再fork一个子进程,关闭写端和读端,第二次fork的子进程关闭读端进行写。

关闭未使用的管道文件描述符

这么做不仅仅是为了让数据的流向更加清晰,也不仅仅是为了节省文件描述符,更重要的原因是:关闭未使用的管道文件描述符对管道的正确使用影响重大。

管道有如下三条性质:

  • 只有当所有的写入端描述符都已关闭,且管道中的数据都被读出,对读取端描述符调用read函数才会返回0(即读到EOF标志)。
  • 如果所有读取端描述符都已关闭,此时进程再次往管道里面写入数据,写操作会失败,errno被设置为EPIPE,同时内核会向写入进程发送一个SIGPIPE的信号。
  • 当所有的读取端和写入端都关闭后,管道才能被销毁。

管道对应的内存大小

查看系统管道的容量,单位:字节:

[root@Zhn vscode]# cat /proc/sys/fs/pipe-max-size 
1048576
[root@Zhn vscode]# 

管道有大小,写入须谨慎,不能连续地写入大量的内容,一旦管道满了,写入就会被阻塞;对于读取端,要及时地读取,防止管道被写满,造成写入阻塞。

与shell命令进行通信(popen)

管道的一个重要作用是和外部命令进行通信。在日常编程中,经常会需要调用一个外部命令,并且要获取命令的输出。而有些时候,需要给外部命令提供一些内容,让外部命令处理这些输入。Linux提供了popen接口来帮助程序员做这些事情。

popen接口定义如下:

#include <stdio.h>FILE *popen(const char *command, const char *type);int pclose(FILE *stream);

popen函数会创建一个管道,并且创建一个子进程来执行shell,shell会创建一个子进程来执行command。根据type值的不同,分成以下两种情况。

如果type是r:command执行的标准输出,就会写入管道,从而被调用popen的进程读到。通过对popen返回的FILE类型指针执行read或fgets等操作,就可以读取到command的标准输出,如图所示:

在这里插入图片描述

如果type是w:调用popen的进程,可以通过对FILE类型的指针fp执行write、fputs等操作,负责往管道里面写入,写入的内容经过管道传给执行command的进程,作为命令的输入,如图所示:

在这里插入图片描述

popen函数成功时,会返回stdio库封装的FILE类型的指针,失败时会返回NULL,并且设置errno。常见的失败有fork失败,pipe失败,或者分配内存失败。

I/O结束了以后,可以调用pclose函数来关闭管道,并且等待子进程的退出。

// 示例#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<errno.h>
#include<sys/wait.h>
#include<signal.h>#define MAX_LINE_SIZE 8192void print_wait_exit(int status)
{printf("\nstatus = %d", status);if (WIFEXITED(status)){printf("\nnormal termination,exit status = %d", WEXITSTATUS(status));}else if (WIFSIGNALED(status)){printf("abnormal termination,signal number =%d%s\n",WTERMSIG(status),
#ifdef WCOREDUMPWCOREDUMP(status) ? "core file generated" : "");
#else"");
#endif}
}int main(int argc, char* argv[])
{FILE* fp = NULL;char command[MAX_LINE_SIZE], buffer[MAX_LINE_SIZE];if (argc != 2){fprintf(stderr, "Usage: %s filename \n", argv[0]);exit(1);}// 执行 cat 文件名 的shell命令snprintf(command, sizeof(command), "cat %s", argv[1]);fp = popen(command, "r");if (fp == NULL){fprintf(stderr, "popen failed (%s)", strerror(errno));exit(2);}// 获取输出while (fgets(buffer, MAX_LINE_SIZE, fp) != NULL){fprintf(stdout, "%s", buffer);}int ret = pclose(fp);if (ret == 127){fprintf(stderr, "bad command : %s\n", command);exit(3);}else if (ret == -1){fprintf(stderr, "failed to get child status (%s)\n",strerror(errno));exit(4);}elseprint_wait_exit(ret);exit(0);
}

在这里插入图片描述

将文件名作为参数传递给程序,执行cat filename的命令。popen创建子进程来负责执行cat filename的命令,子进程的标准输出通过管道传给父进程,父进程可以通过fgets来读取command的标准输出。

popen函数和system有很多相似的地方,但是也有显著的不同。调用system函数时,shell命令的执行被封装在了函数内部,所以若system函数不返回,调用system的进程就不再继续执行。但是popen函数不同,一旦调用popen函数,调用进程和执行command的进程便处于并行状态。然后pclose函数才会关闭管道,等待执行command的进程退出。换句话说,在popen之后,pclose之前,调用popen的进程和执行command的进程是并行的,这种差异带来了两种显著的不同:

  • 在并行期间,调用popen的进程可能会创建其他子进程,所以标准规定popen不能阻塞SIGCHLD信号。这也意味着,popen创建的子进程可能被提前执行的等待操作所捕获。若发生这种情况,调用pclose函数时,已经无法等待command子进程的退出,这种情况下,将返回-1,并且errno为ECHILD。

  • 调用进程和command子进程是并行的,所以标准要求popen不能忽略SIGINT和SIGQUIT信号。如果是从键盘产生的上述信号,那么,调用进程和command子进程都会收到信号。

命名管道FIFO

FIFO与管道类似,最大的差别就是有实体文件与之关联。由于存在实体文件,不相关的没有亲缘关系的进程也可以通过使用FIFO来实现进程之间的通信。

创建FIFO文件

创建命名管道的接口定义如下:

#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char *pathname, mode_t mode);

第一个参数是打开的fifo文件名;

第二个参数mode指定了新创建的命名管道的权限。这个参数是一个用于指定文件权限的八进制数字。

在Linux系统中,文件权限由三组权限组成:文件所有者的权限、文件所属组的权限以及其他用户的权限。每组权限都可以分别包括读取(R)、写入(W)和执行(X)权限,用数字表示分别为4、2和1。因此,八进制数字可以用来表示这些权限的组合。

常见的权限值包括:

  • 0666:文件所有者、文件所属组和其他用户均有读写权限。
  • 0777:文件所有者、文件所属组和其他用户均有读写执行权限。

打开FIFO文件

一旦FIFO文件创建好了,就可以把它用于进程间的通信了。一般的文件操作函数如open、read、write、close、unlink等都可以用在FIFO文件上。

FIFO文件和普通文件相比,有一个明显的不同:程序不应该以O_RDWR模式打开FIFO文件。POSIX标准规定,以O_RDWR模式打开FIFO文件,结果是未定义的。

对FIFO文件推荐的使用方法是,两个进程一个以只读模式(O_RDONLY)打开FIFO文件,另一个以只写模式(O_WRONLY)打开FIFO文件。

在没有进程以写模式(O_RDWR或O_WRONLY)打开FIFO文件的情况下,以O_RDONLY模式打开一个FIFO文件时,调用进程会陷入阻塞,直到另一进程以O_WRONY(或者O_RDWR)的标志位打开该FIFO文件为止。同样的道理,在没有进程以读模式(O_RDONLY或O_RDWR)打开FIFO文件的情况下,如果一个进程以O_WRONLY的标志位打开一个FIFO文件,调用进程也会阻塞,直到另一个进程以O_RDONLY(或者O_RDWR)的标志位打开该FIFO文件为止。也就是说,打开FIFO文件会同步读取进程和写入进程。

乍看之下,O_RDONLY模式打开不能返回,在等写打开,同样O_WRONLY打开不能返回,在等读打开,造成死锁,谁都返回不了。事实上不是这样的。当O_RDONLY打开和O_WRONLY打开的请求都到达FIFO文件时,两者就都能返回了。

FIFO文件提供了O_NONBLOCK标志位,该标志位会显著影响open的行为模式。将O_RDONLY、O_WRONLY及O_NONBLOCK三种标志位结合在一起考虑,共有以下四种组合方式,如表所示:

在这里插入图片描述

// 往管道里写#include <stdio.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>int main(int argc, char* argv[])
{int fd, i;char buf[4096];if (argc < 2){printf("Enter like this: ./a.out fifoname\n");return -1;}fd = open(argv[1], O_WRONLY);if (fd < 0){perror("open");exit(1);}i = 0;while (1){sprintf(buf, "hello itcast %d\n", i++);write(fd, buf, strlen(buf));sleep(1);}close(fd);return 0;
}
// 往管道里读#include <stdio.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>int main(int argc, char* argv[])
{int fd, len;char buf[4096];if (argc < 2){printf("./a.out fifoname\n");return -1;}fd = open(argv[1], O_RDONLY);if (fd < 0){perror("open");exit(1);}while (1){len = read(fd, buf, sizeof(buf));write(STDOUT_FILENO, buf, len);sleep(3);           //多个读端时应增加睡眠秒数,放大效果.}close(fd);return 0;
}

在这里插入图片描述
在这里插入图片描述

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

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

相关文章

MySQL中的SQL高级语句[二]

使用语言 MySQL 使用工具 Navicat Premium 16 代码能力快速提升小方法&#xff0c;看完代码自己敲一遍&#xff0c;十分有用 拖动表名到查询文件中就可以直接把名字拉进来以下是使用脚本方法&#xff0c;也可以直接进行修改中括号&#xff0c;就代表可写可不写 有些地方的代…

IO——标准IO

1.1概念 标准IO&#xff1a;是在C库中定义的一组专门用于输入输出的函数。 1.2特点 &#xff08;1&#xff09;通过缓冲机制减少系统调用&#xff0c;提高效率 &#xff08;2&#xff09;围绕流操作&#xff0c;用FILE*描述 &#xff08;3&#xff09;标准IO默认打开三个流&a…

PCIe错误定义与分类

前言&#xff1a; PCI总线中定义两个边带信号&#xff08;PERR#和SERR#&#xff09;来处理总线错误。其中PERR#主要对应的是普通数据奇偶校检错误&#xff08;Parity Error&#xff09;&#xff0c;而SERR#主要对应的是系统错误&#xff08;System Error&#xff09;。具体如下…

数据结构复习指导之绪论(算法的概念以及效率的度量)

文章目录 绪论&#xff1a; 2.算法和算法评价 知识总览 2.1算法的基本概念 知识点回顾与重要考点 2.2算法效率的度量 知识总览 1.时间复杂度 2.空间复杂度 知识点回顾与重要考点 归纳总结 绪论&#xff1a; 2.算法和算法评价 知识总览 2.1算法的基本概念 算法( Al…

【现代C++】模块的使用

C20引入了模块的概念&#xff0c;这是一个重要的新特性&#xff0c;旨在替代传统的预处理器和头文件机制。模块旨在提高编译速度、改善代码封装性、减少名称污染&#xff0c;并提供更好的工具支持。下面详细介绍模块的关键概念和使用方法&#xff1a; 1. 模块的基本概念 模块…

openGauss学习笔记-263 openGauss性能调优-TPCC性能调优测试指导-前置软件安装

文章目录 openGauss学习笔记-263 openGauss性能调优-TPCC性能调优测试指导-前置软件安装263.1 安装jdk263.2 安装numactl263.3 安装ant263.4 安装htop工具 openGauss学习笔记-263 openGauss性能调优-TPCC性能调优测试指导-前置软件安装 本章节主要介绍openGauss数据库内核基于…

谷歌浏览器的开发者插件vue-devtools

在这里我留下一个git地址用来下载插件包&#xff0c;首先在自己喜欢的位置创建一个新的文件夹&#xff0c;起一个自己喜欢的文件夹名字&#xff0c;下载到包后&#xff0c;然后点进文件夹里下载依赖&#xff0c;npm install,下载后如下面这个样子 git clone https://gitee.com…

【投稿优惠-EI稳定检索】2024年人工智能、自然语言处理与机器学习国际会议(ICAINLPML 2024)

2024 International Conference on Artificial Intelligence, Natural Language Processing and Machine Learning (ICAINLPML 2024) 网址&#xff1a;www.icainlpml.com 邮箱: ainlpmlsub-conf.com ●会议简介 2024年人工智能、自然语言处理与机器学习国际会议将邀请全球人…

Jackson 2.x 系列【24】Spring Web 集成

有道无术&#xff0c;术尚可求&#xff0c;有术无道&#xff0c;止于术。 本系列Jackson 版本 2.17.0 源码地址&#xff1a;https://gitee.com/pearl-organization/study-jaskson-demo 文章目录 1. 前言2. Spring Web3. Jackson2ObjectMapperBuilder4. Jackson2ObjectMapperFa…

比例控制器H5773282、H8135950、H3390627、H6079948

名称&#xff1a;BEUEC数字比例放大器、伺服比例控制器、伺服比例阀放大板&#xff0c;订货代号&#xff1a;H5773282、H8135950、H3390627、H6079948、H6108848、H6700353、H8851035、H1688388、H9549313、H3264103、H1182967&#xff0c;输入指令可选10V、4-20mA&#xff0c;…

Session缓存、Hibernate处理对象的状态了解

Session接口 Session接口是Hibernate向应用程序提供的操纵数据库的最主要的接口&#xff0c;它提供了基本的保存&#xff0c;更新&#xff0c;删除和查询的方法。 Session是有一个缓存, 又叫Hibernate的一级缓存 session缓存是由一系列的Java集合构成的。当一个对象被加入到…

[大模型]Atom-7B-Chat 接入langchain搭建知识库助手

Atom-7B-Chat 接入langchain搭建知识库助手 环境准备 在autodl平台中租一个3090等24G显存的显卡机器&#xff0c;如下图所示镜像选择PyTorch–>2.0.0–>3.8(ubuntu20.04)–>11.8 接下来打开刚刚租用服务器的JupyterLab&#xff0c;并且打开其中的终端开始环境配置…

Linux 网络测速

1.开发背景 网络测速&#xff0c;为了测试开发板的网络速度是否达标的通用测试方法 2.开发需求 搭建 iperf3 &#xff0c;在 ubuntu 下安装服务端&#xff0c;在板卡上安装客户端&#xff0c;服务端和客户端互发 3.开发环境 ubuntu20.04 嵌入式开发板&#xff08;debian 千…

LeetCode_丑数

题目&#xff1a; 题解&#xff1a; 由题&#xff0c;我们知道丑数大于0&#xff0c;丑数都可以写成2*2*...*2*3*3...*3*5*5...*5&#xff0c;有了这个基础就很好写代码了。 用三个while循环将前面的2 3 5全部除掉如果这个数是丑数&#xff0c;最后n是等于1的&#xff0c;反之…

C++_第五周做题总结_构造与析构

id:31 A.Point&#xff08;类与构造&#xff09; 题目描述 下面是一个平面上的点的类定义&#xff0c;请在类外实现它的所有方法&#xff0c;并生成点测试它。 class Point {double x, y; public:Point(); // 缺省构造函数&#xff0c;给x,y分别赋值为0Point(double x_value…

nvm node.js的安装

说明&#xff1a;部分但不全面的记录 因为过程中没有截图&#xff0c;仅用于自己的学习与总结 过程中借鉴的优秀博客 可以参考 1,npm install 或者npm init vuelatest报错 2&#xff0c;了解后 发现是nvm使用的版本较低&#xff0c;于是涉及nvm卸载 重新下载最新版本的nvm 2…

激光雷达初识

一、实车激光雷达 一般在车顶位置: 二、激光雷达组成 激光收发器模块:发射激光器VCSEL+接收模块采用了SiPM(硅基光电倍增管)或者APD,一个激光器发生失效的情况,其他仍可正常工作 扫描模块:水平视场和的垂直视场的扫描,128个阵列的VCSEL激光器负责 信号处理模块:信号处…

深入理解 C++ 中的 KeyFrame 和 KeyFrame*:对象与指针的选择与管理

本文详细讨论了在 C 编程中 KeyFrame 类及其指针 KeyFrame* 的用法、区别与联系。通过探索两者的内存管理、生命周期及使用场景&#xff0c;本文旨在帮助开发者更好地理解何时以及如何选择使用对象或指针&#xff0c;从而提高代码的效率和安全性。 在 C 中&#xff0c;KeyFrame…

【电控笔记2.4】前馈技术

2.4前馈技术 前馈可以减轻控制器的负担

画板探秘系列:创意画笔第一期

前言 我目前在维护一款功能强大的开源创意画板。这个画板集成了多种创意画笔&#xff0c;可以让用户体验到全新的绘画效果。无论是在移动端还是PC端&#xff0c;都能享受到较好的交互体验和效果展示。并且此项目拥有许多强大的辅助绘画功能&#xff0c;包括但不限于前进后退、…