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 8192

void 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 WCOREDUMP
            WCOREDUMP(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);
    }
    else
        print_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;
}

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【YOLOV5 入门】——Pyside6/PyQt5可视化UI界面后端逻辑

声明&#xff1a;笔记是做项目时根据B站博主视频学习时自己编写&#xff0c;请勿随意转载&#xff01; 一、环境安装 VScode/Pycharm终端进入虚拟环境后&#xff0c;输入下面代码安装pyside6&#xff0c;若用的Pycharm作为集成开发环境&#xff0c;也下载个pyqt5&#xff1a; …

移动Web学习07-适配单位vw/vh哔哩哔哩移动端vw单位适配案例

1.1、VW相对单位 前面我们已经学习了rem单位 &#xff0c;他是一个相对单位、相对于HTML表格字号大小 VW/VH也是一个相对单位&#xff0c;他是相对于视口的尺寸计算结果 VW&#xff1a;viewport width VH: viewport height <meta name"viewport" content"…

C语言之探秘:访问结构体空指针与结构体空指针的地址的区别(九十三)

简介&#xff1a; CSDN博客专家&#xff0c;专注Android/Linux系统&#xff0c;分享多mic语音方案、音视频、编解码等技术&#xff0c;与大家一起成长&#xff01; 优质专栏&#xff1a;Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 优质专栏&#xff1a;多媒…

华为HarmonyOS 4.2公测升级计划扩展至15款新机型

华为近日宣布&#xff0c;HarmonyOS 4.2操作系统的公测升级计划将扩展到包括华为P50系列在内的15款设备。这一更新旨在为用户提供更优化的系统性能和增强的功能。 参与此次公测的机型包括华为P50、华为P50 Pro及其典藏版、华为P50E、华为P50 Pocket及其艺术定制版、华为nova系…

学习STM32第十四天

软件SPI读写W25Q64 一、简介 对W25Q64模块进行读写操作时&#xff0c;输出引脚配置为推挽输出&#xff0c;输入引脚配置为浮空或上拉输入。时钟、主机输出和片选都是输出引脚&#xff0c;主机输入是输入引脚。SPI协议是通过命令和数据进行通信&#xff0c;在硬件中使用移位寄…

将自己的项目上传至Git

一、安装Git 官网:Git (git-scm.com) 二、注册gitee 官网:工作台 - Gitee.com 进入“我的”出现以下界面 三、创建仓库 点击加号&#xff0c;新建仓库 根据自己的需求取名&#xff0c;描述仓库&#xff0c;开源还是私有&#xff0c;点击创建即可&#xff0c;点击我的即可…

每日一题 — 找到字符串中所有字母异位词

解法一&#xff1a;暴力枚举 解法二&#xff1a;滑动窗口hash表优化 定义left和right为起始坐标0&#xff0c;right向后遍历&#xff0c;并加入到哈希表中&#xff0c;然后也要记录下来每次进入哈希表的有效字符&#xff08;与目标字符串中相同的字符&#xff09;的个数且这个滑…

C++修炼之路之list模拟实现--C++中的双向循环链表

目录 引言 一&#xff1a;STL源代码中关于list的成员变量的介绍 二&#xff1a;模拟实现list 1.基本结构 2.普通迭代器 const迭代器的结合 3.构造拷贝构造析构赋值重载 清空 4.inserterase头尾插入删除 5.打印不同数据类型的数据《使用模板加容器来完成》 三&#xf…

SQLite、MySQL 和 PostgreSQL 数据库速度比较(本文阐述时间很早比较,不具有最新参考性)(二十五)

返回&#xff1a;SQLite—系列文章目录 上一篇&#xff1a;用于 SQLite 的异步 I/O 模块&#xff08;二十四&#xff09; 下一篇&#xff1a;SQLite—系列文章目录 注意&#xff1a;本文档非常非常旧。它描述了速度比较 SQLite、MySQL 和 PostgreSQL 的古老版本。 这里…

学习一门语言的方法和套路(B站转述)

视频链接 up虽然长相英(ping)俊(ping)&#xff0c;但是讲的干活&#xff0c;没恰饭。 学习流程&#xff1a; 1.快速阅读&#xff0c;掌握概况 2.深入细节内容 例如&#xff1a;java (JDBC)、html 、netty 不管三七二十一&#xff0c;先了解套路&#xff0c;再深入研究。 高…

安装CUDNN详细过程

cuDNN&#xff08;CUDA Deep Neural Network library&#xff09;是由NVIDIA开发的深度学习GPU加速库。 cuDNN包含了许多针对神经网络操作进行高度优化的函数&#xff0c;旨在使深度学习框架能够在NVIDIA的GPU上实现最佳性能&#xff0c;这个库提供了高效计算和加速&#xff0c…

牛客网刷题 :BC50 你是天才吗

描述 据说智商140以上者称为天才&#xff0c;KiKi想知道他自己是不是天才&#xff0c;请帮他编程判断。输入一个整数表示一个人的智商&#xff0c;如果大于等于140&#xff0c;则表明他是一个天才&#xff0c;输出“Genius”。 输入描述&#xff1a; 多组输入&#xff0c;每…

在 PyCharm 中使用系统安装的 Python 和 Anaconda 的 Python什么区别

virtualenv environment &#xff1a; virtualenv 是一个用于创建独立 Python 环境的工具。它可以在同一个系统上创建多个相互独立的 Python 环境&#xff0c;每个环境都有自己的 Python 解释器和包库&#xff0c;从而可以实现不同项目之间的依赖隔离和版本控制。coda environm…

vue快速入门(二十五)本地存储与初始化使用

注释很详细&#xff0c;直接上代码 上一篇 新增内容 本地获取数据数据存储到本地 源码 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial…

2024蓝桥杯——宝石问题

先展示题目 声明 以下代码仅是我的个人看法&#xff0c;在自己考试过程中的优化版&#xff0c;本人考试就踩了很多坑&#xff0c;我会—一列举出来。代码可能很多&#xff0c;但是总体时间复杂度不高只有0(N) 函数里面的动态数组我没有写开辟判断和free&#xff0c;这里我忽略…

频率域滤波基础(离散傅里叶变换使用填充的缺陷)

本来是个很简单的问题&#xff0c;作者硬是写的这么复杂&#xff0c;翻译还搞错了。重点是我发现作者真正有用的东西没讲到&#xff0c;比如相位和谱如何影响图像。连个转换公式都没有&#xff0c;我只能说作者是在混字数。 首先看关于中心对称是什么意思&#xff1f;我木太明白…

MySql 视图 存储过程 触发器

文章目录 视图数据库对象视图的理解创建、查看、更新、删除 存储过程和存储函数概述分类存储过程的创建和调用存储函数的创建和调用存储过程和存储函数的对比存储过程和存储函数的查看、修改、删除 变量GLOBAL 与 SESSION 变量的使用会话用户变量和局部变量的使用 定义条件与处…

【机器学习300问】70、向量化技术来计算神经网络时维度如何确保正确?

一、向量化技术在进行神经网络计算时的优势 向量化是一种优化技术&#xff0c;通过使用数组操作代替for循环&#xff0c;可以大大提高代码的性能和效率。在深度学习中尤其明显&#xff0c;可以提高计算效率、简化代码、优化内存使用。 二、如何确保计算时维度是正确的&#xf…

中标了,Trojan/Hijack.v木马病毒怎么解决?

火绒只是提示有病毒木马&#xff0c;并未解决。 经过不断尝试.。。。。。。 往下拉找到 Internet选项 连接 – 局域网设置 把前面的勾选取消 发现以上办法网络上出现的搜索注册表关键字等办法都无法解决。。。 解决方法一&#xff1a; 电脑进入安全模式&#xff0c;然后进…

【vue】v-model 双向数据绑定

:value&#xff1a;单向数据绑定v-model&#xff1a;双向数据绑定 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0">…