Linux基础知识点(四-管道)

news2025/1/18 12:04:06

目录

一、管道的基本概念

二、管道的分类

2.1 匿名管道PIPE

2.2 命名管道FIFO

三、pipe()函数 

3.1 pipe()函数介绍

3.2 pipe()函数的使用 

 四、fifo()函数

4.1 fifo()函数介绍

4.2 命名管道创建方式 

五、管道读写特点 


一、管道的基本概念

生活中的管道,例如水管的作用是什么?水通过水管从一端流向另一端,那么进程间通信是不是可以模仿这种“流向”的关系呢? 答案是可以的,数据可以从一个进程流向另一个进程,那么一个进程产生数据,然后通过管道发送给另一个进程, 另一个进程从管道的另一端读取出数据,这样一来就实现了进程间的通信了。

在终端中输入 "ps -aux | grep root" 

"ps -aux" 命令是列出当前的进程,grep命令是一种强大的文本搜索工具,它能使用正则表达式搜索文本,那么ps与grep命令之间的 " | " 符号是什么呢? 它其实是一个管道,将ps命令输出的数据通过管道流向grep,其实在这里就打开了两个进程, ps命令本应该在终端输出信息的,但是它通过管道将输出的信息作为grep命令的输入信息, 然后通过搜索之后将合适的信息显示出来,这样子就形成了我们在终端看到的信息。

对于shell命令来说,命令的连接是通过管道字符来完成的,只需要使用 " | " 字符进行连接即可。

那么我们对这个”ps -aux | grep root”命令进行详细的分析,它实际上就是执行以下过程:

  • shell负责安排两个命令的标准输入和标准输出。

  • ps的标准输入来自终端鼠标、键盘等。

  • ps的标准输出传递给grep,作为grep的标准输入。

  • grep的标准输出连接到终端,即输出到显示器屏幕,最终我们看到grep的输出结果。

shell所做的工作实际上是对标准输入和标准输出流进行了重新连接,在ps命令与grep之间建立了数据管道,示意图如下:

其实,管道本质上也是一个文件,上图的过程可以看作是ps进程将输出的内容写入管道中, grep进程从管道中读取数据,可以把它抽象成一个可读写的文件。 遵循了Linux中“一切皆文件”的设计思想,它借助VFS(虚拟文件系统)给应用程序提供操作接口,实现了管道的功能。

不过还是要注意的是:虽然管道的实现形态上是文件,但是管道本身并不占用磁盘或者其他外部存储的空间, 它占用的是内存空间,因此Linux上的管道就是一个操作方式为文件的内存缓冲区而已。

二、管道的分类

2.1 匿名管道PIPE

匿名管道(PIPE)是一种特殊的文件,但虽然它是一种文件,却没有名字, 因此一般进程无法使用open()来获取他的描述符。匿名管道最常见的形态就是我们在shell操作中最常用的" | "。它的特点是只能在父子进程中使用, 父进程在产生子进程前必须打开一个管道文件,然后fork产生子进程, 这样子进程通过拷贝父进程的进程地址空间获得同一个管道文件的描述符, 以达到使用同一个管道通信的目的。此时除了父子进程外,没人知道这个管道文件的描述符, 所以通过这个管道中的信息无法传递给其他进程。另外,匿名管道不同于一般文件的显著之处是:它有两个文件描述符,而不是一个,一个只能用来读, 另一个只能用来写,这就是所谓的“半双工”通信方式,而且它对写操作不做任何保护, 即:假如有多个进程或线程同时对匿名管道进行写操作,那么这些数据很有可能会相互阻塞。 最后,匿名管道不能使用lseek()来进行所谓的定位, 因为他们的数据不像普通文件那样按块的方式存放在诸如硬盘、flash等块设备上。

总结来说,匿名管道有以下的特征:

  • 没有名字,因此不能使用open()函数打开,但可以使用close()函数关闭。

  • 只提供单向通信(即半双工通信),也就是说,两个进程都能访问这个文件,假设进程1往文件内写东西, 那么进程2就只能读取文件的内容。

  • 只能用于具有血缘关系的进程间通信,通常用于父子进程建通信 。

  • 管道是基于字节流来通信的。

  • 依赖于文件系统,它的生命周期随着进程的结束而结束。

  • 写入操作不具有原子性,因此只能用于一对一的简单通信情形。

  • 管道也可以看成是一种特殊的文件,对于它的读写也可以使用普通的read()和write()等函数。 但是它又不是普通的文件,并不属于其他任何文件系统,并且只存在于内核的内存空间中, 因此不能使用lseek()来定位。

2.2 命名管道FIFO

命名管道(FIFO)与匿名管道(PIPE)是不同的,命名管道可以在多个无关的进程中交换数据(通信)。 匿名管道只能在”有血缘关系”的进程中进行数据交互, 这给在不相关的的进程之间交换数据带来了不方便,因此产生了命名管道,来解决不相关进程间的通信问题。

命名管道不同于无名管道之处在于它提供了一个路径名与之关联,以一个文件形式存在于文件系统中, 这样,即使与命名管道的创建进程不存在“血缘关系”的进程,只要可以访问该命名管道文件的路径, 就能够彼此通过命名管道相互通信,因为可以通过文件的形式,那么就可以调用系统中对文件的操作, 如打开(open)、读(read)、写(write)、关闭(close)等函数,虽然命名管道文件存储在文件系统中, 但数据却是存在于内存中的。

总结来说,命名管道有以下的特征:

  • 有名字,存储于普通文件系统之中。

  • 任何具有相应权限的进程都可以使用 open()来获取命名管道的文件描述符。

  • 跟普通文件一样:使用统一的 read()/write()来读写。

  • 跟普通文件不同:不能使用 lseek()来定位,原因是数据存储于内存中。

  • 具有写入原子性,支持多写者同时进行写操作而数据不会互相践踏。

  • 遵循先进先出(First In First Out)原则,最先被写入 FIFO的数据,最先被读出来。

三、pipe()函数 

3.1 pipe()函数介绍

pipe()函数用于创建一个匿名管道,一个可用于进程间通信的单向数据通道。

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

数组pipefd是用于返回两个引用管道末端的文件描述符,它是一个由两个文件描述符组成的数组的指针。pipefd[0] 指管道的读取端,pipefd[1]指向管道的写端 ,向管道的写入端写入数据将会由内核缓冲,即写入内存中,直到从管道的读取端读取数据为止, 而且数据遵循先进先出原则。pipe()函数还会返回一个int类型的变量, 如果为0则表示创建匿名管道成功,如果为-1则表示创建失败,并且设置errno。

3.2 pipe()函数的使用 

匿名管道创建成功以后,创建该匿名管道的进程(父进程)同时掌握着管道的读取端和写入端, 但是想要父子进程间有数据交互,则需要以下操作:

  • 父进程调用pipe()函数创建匿名管道,得到两个文件描述符pipefd[0]、pipefd[1], 分别指向管道的读取端和写入端。

  • 父进程调用fork()函数启动(创建)一个子进程, 那么子进程将从父进程中继承这两个文件描述符pipefd[0]、pipefd[1], 它们指向同一匿名管道的读取端与写入端。

  • 由于匿名管道是利用环形队列实现的,数据将从写入端流入管道,从读取端流出,这样子就实现了进程间通信, 但是这个匿名管道此时有两个读取端与两个写入端。

 如果想要从父进程将数据传递给子进程,则父进程需要关闭读取端,子进程关闭写入端

 如果想要从子进程将数据传递给父进程,则父进程需要关闭写入端,子进程关闭读取端

 #include <unistd.h>
 #include <sys/types.h>
 #include <sys/wait.h>
 #include <errno.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>

 #define MAX_DATA_LEN 256
 #define DELAY_TIME 1

 int main()
 {
     pid_t pid;
     //定义一个数组pipe_fd,在创建匿名管道后通过数组返回管道的文件描述符。
     int pipe_fd[2];      
     char buf[MAX_DATA_LEN];
     const char data[] = "Pipe Test Program";
     int real_read, real_write;
     // 将buf内容全部填为0
     memset((void*)buf, 0, sizeof(buf));
     // 创建管道
     // 创建成功则得到两个文件描述符pipe_fd[0]、pipe_fd[1],否则返回-1。
     if (pipe(pipe_fd) < 0)                  
     {
         printf("pipe create error\n");
         exit(1);
     }

     // 创建子进程
     if ((pid = fork()) == 0)                
     {
         /* 子进程关闭写描述符,并通过使子进程暂停 3s 等待父进程已关闭相应的读描述符 */
         close(pipe_fd[1]);
         sleep(DELAY_TIME * 3);
         /* 子进程读取管道内容 */            
         if ((real_read = read(pipe_fd[0], buf, MAX_DATA_LEN)) > 0)
         {
             printf("%d bytes read from the pipe is '%s'\n", real_read, buf);
         }
         /* 关闭子进程读描述符 */
         close(pipe_fd[0]);                 
         exit(0);
     }
     else if (pid > 0)
     {
         /* 父进程关闭读描述符,并通过使父进程暂停 1s 等待子进程已关闭相应的写描述符 */
         close(pipe_fd[0]);                  
         sleep(DELAY_TIME);
         if((real_write = write(pipe_fd[1], data, strlen(data))) != -1)  
         {
             printf("Parent write %d bytes : '%s'\n", real_write, data);
         }
         /*关闭父进程写描述符*/
         close(pipe_fd[1]);                  
         /*收集子进程退出信息*/
         waitpid(pid, NULL, 0);              
         exit(0);
     }
 }

 四、fifo()函数

4.1 fifo()函数介绍

如果想在不相关的进程之间交换数据,我们可以用FIFO文件来完成这项工作,或者称之为命名管道。 命名管道是一种特殊类型的文件,它在文件系统中以文件名的形式存在,但它的的数据却是存储在内存中的。 我们可以在终端(命令行)上创建命名管道,也可以在程序中创建它。

4.2 命名管道创建方式 

命令行中创建命名管道 

mkfifo namefile

 在程序中创建命名管道

int mkfifo(const char * pathname,mode_t mode);

mkfifo()会根据参数pathname建立特殊的FIFO文件,而参数mode为该文件的模式与权限。

mkfifo()创建的FIFO文件其他进程都可以进行读写操作,可以使用读写一般文件的方式操作它, 如open,read,write,close等。

mode模式及权限参数说明

  • O_RDONLY:读管道。

  • O_WRONLY:写管道。

  • O_RDWR:读写管道。

  • O_NONBLOCK:非阻塞。

  • O_CREAT:如果该文件不存在,那么就创建一个新的文件,并用第三个参数为其设置权限。

  • O_EXCL:如果使用 O_CREAT 时文件存在,那么可返回错误消息。这一参数可测试文件是否存在。

函数返回值说明如下

  • 0:成功

  • EACCESS:参数 filename 所指定的目录路径无可执行的权限。

  • EEXIST:参数 filename 所指定的文件已存在。

  • ENAMETOOLONG:参数 filename 的路径名称太长。

  • ENOENT:参数 filename 包含的目录不存在。

  • ENOSPC:文件系统的剩余空间不足。

  • ENOTDIR:参数 filename 路径中的目录存在但却非真正的目录。

  • EROFS:参数 filename 指定的文件存在于只读文件系统内。

使用FIFO的过程中,当一个进程对管道进行读操作时

  • 若该管道是阻塞类型,且当前FIFO内没有数据,则对读进程而言将一直阻塞到有数据写入。

  • 若该管道是非阻塞类型,则不论FIFO内是否有数据,读进程都会立即执行读操作。 即如果FIFO内没有数据,读函数将立刻返回 0。

使用FIFO的过程中,当一个进程对管道进行写操作时

  • 若该管道是阻塞类型,则写操作将一直阻塞到数据可以被写入。

  • 若该管道是非阻塞类型而不能写入全部数据,则写操作进行部分写入或者调用失败

 #include <sys/wait.h>
 #include <sys/types.h>
 #include <sys/stat.h>
 #include <unistd.h>
 #include <errno.h>
 #include <fcntl.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <limits.h>
 #include <string.h>

 #define MYFIFO "myfifo"    /* 命名管道文件名*/
 #define MAX_BUFFER_SIZE PIPE_BUF /* 4096 定义在于 limits.h 中*/

 void fifo_read(void)
 {
     char buff[MAX_BUFFER_SIZE];
     int fd;
     int nread;
     printf("***************** read fifo ************************\n");
     /* 判断命名管道是否已存在,若尚未创建,则以相应的权限创建*/
     if (access(MYFIFO, F_OK) == -1)                 
     {
         if ((mkfifo(MYFIFO, 0666) < 0) && (errno != EEXIST))    
         {
             printf("Cannot create fifo file\n");
             exit(1);
         }
     }
     /* 以只读阻塞方式打开命名管道 */
     fd = open(MYFIFO, O_RDONLY);               
     if (fd == -1)
     {
         printf("Open fifo file error\n");
         exit(1);
     }
     memset(buff, 0, sizeof(buff));
     if ((nread = read(fd, buff, MAX_BUFFER_SIZE)) > 0)      
     {
         printf("Read '%s' from FIFO\n", buff);
     }
     printf("***************** close fifo ************************\n");
     close(fd);                             
     exit(0);
 }

 void fifo_write(void)
 {
     int fd;
     char buff[] = "this is a fifo test demo";
     int nwrite;
     sleep(2);   //等待子进程先运行              
     /* 以只写阻塞方式打开 FIFO 管道 */
     fd = open(MYFIFO, O_WRONLY | O_CREAT, 0644);       
     if (fd == -1)
     {
         printf("Open fifo file error\n");
         exit(1);
     }
     printf("Write '%s' to FIFO\n", buff);
     /*向管道中写入字符串*/
     nwrite = write(fd, buff, MAX_BUFFER_SIZE);         
     if(wait(NULL))  //等待子进程退出
     {
         close(fd);                         
         exit(0);
     }
 }


 int main()
 {
     pid_t result;
     /*调用 fork()函数*/
     result = fork();               

     /*通过 result 的值来判断 fork()函数的返回情况,首先进行出错处理*/
     if(result == -1)
     {
         printf("Fork error\n");
     }
     else if (result == 0) /*返回值为 0 代表子进程*/
     {
         fifo_read();            
     }
     else /*返回值大于 0 代表父进程*/
     {
         fifo_write();      
     }
     return result;
 }

五、管道读写特点 

  • 当没有数据可读时
    O_NONBLOCK disable:read调用阻塞,即进程暂停执行,一直等到有数据来到为止。
    O_NONBLOCK enable:read调用返回-1,errno值为EAGAIN。
  • 当管道满的时候
    O_NONBLOCK disable: write调用阻塞,直到有进程读走数据
    O_NONBLOCK enable:调用返回-1,errno值为EAGAIN
  • 如果所有管道写端对应的文件描述符被关闭,则read返回0
  • 如果所有管道读端对应的文件描述符被关闭,则write操作会产生信号SIGPIPE,进而可能导致write进程退出
  • 当要写入的数据量不大于PIPE_BUF时,linux将保证写入的原子性。
  • 当要写入的数据量大于PIPE_BUF时,linux将不再保证写入的原子性。

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

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

相关文章

【map】【滑动窗口】【优先队列】LeetCode480滑动窗口中位数

作者推荐 动态规划 多源路径 字典树 LeetCode2977:转换字符串的最小成本 本题涉及知识点 滑动窗口 map 优先队列 题目 中位数是有序序列最中间的那个数。如果序列的长度是偶数&#xff0c;则没有最中间的数&#xff1b;此时中位数是最中间的两个数的平均数。 例如&#xf…

springBoot整合redis做缓存

一、Redis介绍 Redis是当前比较热门的NOSQL系统之一&#xff0c;它是一个开源的使用ANSI c语言编写的key-value存储系统&#xff08;区别于MySQL的二维表格的形式存储。&#xff09;。和Memcache类似&#xff0c;但很大程度补偿了Memcache的不足。和Memcache一样&#xff0c;R…

yolov8 细胞分割数据集准备及训练

1、数据 下载:https://universe.roboflow.com/motherson-hm/5-part-diff 500来张,5个类别(嗜碱性细胞、嗜酸细胞、淋巴细胞、单核细胞、中性粒细胞) yolo 分割数据标注格式: 与检测类似,就是坐标分割有多个 2、训练 训练yaml: seg_data.yaml (与检测格式一样) …

sheng的学习笔记-卷积神经网络

源自吴恩达的深度学习课程&#xff0c;仅用于笔记&#xff0c;便于自行复习 导论 1&#xff09;什么是卷积神经网络 卷积神经网络&#xff0c;也就是convolutional neural networks &#xff08;简称CNN&#xff09;&#xff0c;使用卷积算法的神经网络&#xff0c;常用于计…

四川云汇优想教育咨询有限公司抖店开店靠谱吗

随着短视频平台的崛起&#xff0c;抖音已经成为了越来越多人展示自我、分享生活、推广产品的重要平台。因此&#xff0c;开设抖店已经成为了很多人的创业选择。如果您也有兴趣开设抖店&#xff0c;那么四川云汇优想教育咨询有限公司抖店开店服务将是您的不二之选。 四川云汇优想…

推荐五款简洁而实用的工具,值得你尝试

​ 分享快乐是生活中美好的瞬间&#xff0c;而分享简单巧妙的工具也能令我愉悦。这五款工具简洁而实用&#xff0c;值得你尝试。 1.视频播放器——Potplayer Potplayer是一款视频播放器&#xff0c;支持DXVA、CUDA和QuickSync等硬件加速技术&#xff0c;提供高效的视频播放性…

【数据结构和算法】---二叉树(2)--堆的实现和应用

目录 一、堆的概念及结构二、堆结构的实现2.1堆向下调整算法2.2堆向上调整算法2.3删除堆顶元素2.4插入元素2.5其他函数接口 三、堆结构的应用3.1堆排序3.2Top-k问题 四、堆概念及结构相关题目 一、堆的概念及结构 如果有一个数字集合&#xff0c;并把它的所有元素按完全二叉树…

2022 年全国职业院校技能大赛高职组云计算正式赛卷第二场-容器云

2022 年全国职业院校技能大赛高职组云计算赛项试卷 云计算赛项第二场-容器云 目录 2022 年全国职业院校技能大赛高职组云计算赛项试卷 【赛程名称】云计算赛项第二场-容器云 【任务 1】容器云平台搭建[5 分] 【任务 2】容器云应用部署&#xff1a; Docker Compose 编排部署[7.0…

文件操作安全之-目录穿越流量告警运营分析篇

本文从目录穿越的定义,目录穿越的多种编码流量数据包示例,目录穿越的suricata规则,目录穿越的告警分析研判,目录穿越的处置建议等几个方面阐述如何通过IDS/NDR,态势感知等流量平台的目录穿越类型的告警的线索,开展日常安全运营工作,从而挖掘有意义的安全事件。 目录穿越…

二分查找——OJ题(一)

&#x1f4d8;北尘_&#xff1a;个人主页 &#x1f30e;个人专栏:《Linux操作系统》《经典算法试题 》《C》 《数据结构与算法》 ☀️走在路上&#xff0c;不忘来时的初心 文章目录 一、二分查找1、题目讲解2、算法原理3、代码实现 二、在排序数组中查找元素的第一个和最后一个…

Frappe Charts:数据可视化的强大工具

一、产品简介&#xff1a; 一个简单、零依赖、响应式的 开源SVG 图表库。这个图表库无论是数据更新还是屏幕大小变化&#xff0c;都能快速响应并更新图表。数据生成和悬停查看都有舒服的交互动效&#xff0c;体验感很好。不仅支持配置颜色&#xff0c;外观定制也很方便。还支持…

Python 常用模块Logging

Python 常用模块Logging 【序言】 logging模块是专门用来做日志记录的模块 【一】日志等级 默认打印结果到终端上 CRITICAL 50 # 致命错误 ERROR 40 # 错误 WARNING 30 # 警告 INFO 20 # 消息 DEBUG 10 # 调试 NOTSET 0 # 不设置示例&#xff1a; 默认级别为…

Vue3+ElementPlus: 给点击按钮添加触发提示

一、需求 在Vue3项目中&#xff0c;有一个下载按钮&#xff0c;当鼠标悬浮在按钮上面时&#xff0c;会出现文字提示用户可以点击按钮进行数据的下载技术栈 Vue3 ElementPlusTooltip组件 ElementPlus中的Tooltip组件 &#xff0c;可用于展示鼠标 hover 时的提示信息 二、实现…

jenkins解决工具找不到的问题

--------------------------插件选择版本最好能跟服务器对上

EDM打开率突然下降的原因:深入分析并采取应对措施

在跨境电商和出海领域&#xff0c;电子邮件营销&#xff08;Email Marketing&#xff09;已成为企业营销推广及与客户互动的主要手段之一&#xff0c;在企业营销中起到主导地位。但是&#xff0c;有时我们不得不面对EDM打开率突然下降的困境。那么&#xff0c;EDM打开率下降的原…

二叉树数据结构:深入了解二叉树的概念、特性与结构

在探索栈和队列之后&#xff08;大家可以移步至我的数据结构专栏&#xff09;&#xff1a;T-rLN的数据结构专栏 我们转向了更为复杂而有趣的数据结构——二叉树。本文将引领我们进入二叉树的世界&#xff0c;从最基本的概念和结构开始&#xff0c;逐步深入了解二叉树的顺序结构…

可用于blender制作3D动画的全身动捕设备

随着动捕设备的进步&#xff0c;在3D建模和动画制作领域中&#xff0c;动捕设备被广泛应用&#xff0c;以便创建更加真实和自然的角色动画。其中&#xff0c;blender作为一款开源的3D建模和动画软件&#xff0c;搭配全身动捕设备使用&#xff0c;更加激发了用户角色动画创作灵感…

【ARMv8M Cortex-M33 系列 2 -- Cortex-M33 JLink 连接 及 JFlash 烧写介绍】

文章目录 Jlink 工具JLink 命令行示例JFlash 烧写问题Jlink 工具 J-Link 是 SEGGER 提供的一款流行的 JTAG 调试器,它支持多个平台和处理器。JLink.exe 是 J-Link 调试器的命令行接口,它允许用户通过命令行执行一系列操作,例如编程、擦除、调试等。 工具链接: https://ww…

使用VMware创建CentOS7虚拟机详细教程

创建虚拟机 首先以管理员身份运行vmware17&#xff0c;进入后点击创建虚拟机。 直接点击下一步&#xff0c;开始自定义安装。 继续点击下一步 提前将iso文件准备好&#xff0c;这里用的是centos7&#xff0c;也可以使用其他系统的文件&#xff0c;然后点击下一步。 下来就是修…

IDEA、VSCode等快速连接Github(Mac版)

问题描述 在本地书写✍️完代码后, 想要git push到Github上面, 出现延迟错误; 导致经常push不上去, 如下图所示; 解决方案 进入电脑终端; 输入下列命令; sudo vim /etc/hosts输入密码; 按下 I 键, 进行编辑操作; 将下列语句复制到空白区, 然后按下esc按键, 然后输入:wq即可…