当前位置: 首页 > news >正文

Linux学习日记12:无名通道与有名通道

一、前言

        之前我们学习了Linux进程的相关知识,了解了什么是进程以及进程控制相关的内容,今天我们来学习进程间通信其中的两种基础方式——无名通道与有名通道。

二、IPC简介

        IPC 是进程间通信的缩写,它是指多个进程之间进行数据交换和同步的机制。由于每个进程都有自己独立的虚拟地址空间一个进程通常无法直接访问另一个进程的数据,因此操作系统必须提供专门的机制来 facilitated 这种通信。比如:数据交换: A 进程采集的传感器数据传给 B 进程处理;操作同步: A 进程完成任务后,通知 B 进程开始工作;资源共享:比如多个进程共用一块内存区域,提高效率。

三、无名管道—— 最简单的 “单向通道”

3.1、简介

        在 Linux 操作系统中,无名管道(Unnamed Pipe) 是最基础、最古老的进程间通信(IPC)机制之一,主要用于解决具有亲缘关系的进程(如父子进程、兄弟进程)之间的单向数据传递问题。它的设计简单直接,是理解 Linux 进程通信的基础。

3.2、本质与创建

        无名管道本质是内核中的一段临时缓冲区(环形队列),由内核管理,没有对应的文件系统路径(因此称为 “无名”)。进程通过文件描述符操作这段缓冲区,实现数据读写。如下图所示:

        

        创建方式: pipe()系统调用:

        通过pipe()函数创建无名管道,函数原型如下:

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

        其中参数pipefd是一个长度为 2 的数组,用于存储管道的两个文件描述符:

        pipefd[0]:管道的读端(只能读取数据);

        pipefd[1]:管道的写(只能写入数据);

        返回值:成功返回 0,失败返回 - 1。

3.3、无名通道的特点

1、半双工通信:数据只能单向流动:写端写入的数据,只能从读端读取,无法双向同时传输。若需双向通信,需创建两个无名管道(一个负责 A→B,一个负责 B→A)。

2、亲缘关系限制:仅能在有共同祖先的进程(如父子、兄弟、祖孙进程)间使用。原因是:管道创建后,只有通过fork()复制文件描述符,才能让子进程继承管道的读写端(非亲缘进程无法获取管道的文件描述符)。

3、生命周期与进程绑定:无名管道随创建它的进程(及子进程)的退出而自动释放,没有持久化存储(不占用磁盘空间,仅存在于内存中)。

4、字节流传输:管道中的数据是无结构的字节流(类似文件),没有 “消息边界”。读取时需应用程序自行处理数据拆分(例如约定换行符为分隔符)。

5、有限缓冲区:管道的内核缓冲区大小固定(通常为 4KB,可通过fcntl()或ulimit()查看或者调整)。若缓冲区满,写操作会阻塞;若缓冲区空,读操作会阻塞(除非设置非阻塞模式)。

6、读写端的依赖关系:执行读操作时,如果缓存区有数据的话,正常读,并返回读出的字节数;如果缓存区没有数据(写端被全部关闭),读端读取时会返回 0(类似读到文件末尾EOF);如果没有全部关闭(如写操作停止两秒再写),读端会阻塞两秒后再读。执行写操作时,如果读端全部关闭,则管道会破裂,进程会被终止(内核给当前进程发送信号SIGPIPE-13,默认处理动作),如果读端没全部关闭,会有两种情况:一种是缓冲区写满了,另一种是缓冲区没写满,前者会使写段阻塞,后者会让写段继续写,直到写满阻塞。

3.4、工作流程

1、父进程调用pipe()创建管道,得到 pipefd[0](读)和 pipefd[1](写);

2、父进程调用fork()创建子进程,子进程继承这两个文件描述符;

3、双方关闭不需要的端口(例如父进程写、子进程读,则父进程关闭pipefd[0],子进程关闭 pipefd[1]);

4、父进程通过write( pipefd[1],  data,len)写入数据;

5、子进程通过read(pipefd[0], buf, len)读取数据;

6、通信结束后,双方关闭剩余的文件描述符。

3.5、典型示例

        首先创建一个pipe.c文件:

        然后输入以下代码:

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>int main()
{int gd;int fd[2];gd = pipe(fd);if(gd == -1){printf("creat pipe failed!\n");exit(1);}printf("pipe1 is %d\n",fd[0]);printf("pipe2 is %d\n",fd[1]);close(fd[0]);close(fd[1]);return 0;
}

        这样我们就创建好了一个基本的无名管道了,编译并运行,结果如下:

        可以看到我们将读端和写端都打印了出来,分别为3和4,那为什么会是3和4呢?

        是因为Linux 进程启动时,会自动打开三个标准文件描述符

        当调用pipe(fd) 创建无名管道时,内核会为管道的读端(fd[0]写端(fd[1]分配最小的、未被使用的文件描述符。在上述程序中这三个默认描述符(0、1、2)已经被占用,因此内核会分配下一个可用的描述符:读端(fd[0])被分到3,写端(fd[1])被分到4。

        接着我们无名管道实现一个经典案例:ps aux | grep "bash":

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>int main()
{int gd;int fd[2];gd = pipe(fd);if(gd == -1){printf("creat pipe failed!\n");exit(1);}pid_t pid = fork();//ps auxif(pid > 0){close(fd[0]);//关闭读端dup2(fd[1],STDOUT_FILENO);//重定向到输出端execlp("ps","ps","aux",NULL);//执行程序}//grep "bash"if(pid == 0){close(fd[1]);//关闭写端dup2(fd[0],STDIN_FILENO);//重定向到输入端execlp("grep","grep","bash","--color=auto",NULL);//执行程序}printf("pipe1 is %d\n",fd[0]);printf("pipe2 is %d\n",fd[1]);close(fd[0]);close(fd[1]);return 0;
}

        使用gcc编译器进行编译,然后运行:

        可以看到成功执行了ps aux | grep "bash"这个进程。

        补:当执行ps aux | grep "bash"时,Shell 进程(父进程)会先fork()出两个子进程——ps和grep,这两个子进程互为兄弟,它们继承了 Shell 创建的无名管道,从而实现数据传递(ps的输出通过管道传给grep)。

        同时我们可以使用ulimit和fpathconf指令来查看管道缓冲区大小:

        可以看到管道(pipe)的缓冲区大小为512×8=4KB。

         然后使用fpathconf来进行查看:                可以看到也查看到管道(pipe)的缓冲区大小为4096个字节。

四、有名管道

        在 Linux 操作系统中,有名管道(FIFO)是一种进程间通信(IPC)机制,它通过文件系统中的路径名来标识,突破了无名管道的 “亲缘进程限制”,允许任意进程(无论是否有亲缘关系) 进行通信。

4.1、有名管道的本质与创建

        本质:有名管道是一种特殊的文件(在文件系统中可见,类型为p),但它并不存储数据,仅作为进程间通信的 “标识和通道”,数据仍在内存的管道缓冲区中传递。

        创建方式:

1、命令行工具:mkfifo

        格式:mkfifo[选项]管道路径

        示例:创建一个名为/usr/bin/mkfifo的有名管道:

mkfifo /usr/bin/myfifo

2、C语言函数:mkfifo()

头文件:#include <sys/stat.h>
函数原型:int mkfifo(const char *pathname, mode_t mode);

        pathname:管道的文件系统路径(如"/usr/bin/mkfifo");mode:管道的权限(如0666表示所有用户可读写);返回值:成功返回0,失败返回-1

4.2、有名管道的核心特点

1、无亲缘关系限制:只要进程能通过文件路径访问有名管道,即可通信(如不同用户的进程、系统服务与应用程序等)。

2、半双工通信:数据只能单向流动,若需双向通信,需创建两个有名管道(一个用于 A→B,一个用于 B→A)。

3、文件系统可见性:有名管道以文件(伪文件)形式存在于文件系统中(可通过ls -l 查看,类型为p),但不占用磁盘空间(数据在内存中)。

4、打开时的阻塞特性:读阻塞:当一个进程以读模式打开 FIFO 时,它会阻塞,直到有另一个进程以写模式打开同一个 FIFO。同样,如果读取时管道内没有数据,读进程也会阻塞,直到有数据写入;写阻塞:当一个进程以写模式打开 FIFO 时,它会阻塞,直到有另一个进程以读模式打开同一个 FIFO。这种阻塞行为是一种天然的进程同步机制。可以使用 O_NONBLOCK 标志在open时设置为非阻塞模式,并处理EAGAIN错误。

5、生命周期与文件系统绑定:有名管道的生命周期由文件系统管理,除非显式删除(如rm /usr/bin/mkfifo),否则会一直存在于文件系统中。

4.3、工作流程

1、先创建有名通道:命令行工具和函数均可;

2、启动读进程(会阻塞,等待写进程):./reader;

3、另开终端,启动写进程:./writer;

4、读进程会输出:read:字符串;写进程输出:write:字符串;

4.4、有名管道和无名管道的对比

维度有名管道(FIFO)无名管道(Pipe)
标识方式文件系统路径(如/tmp/myfifo无文件名,仅通过文件描述符标识
亲缘关系限制无(任意进程可通过路径访问)有(仅亲缘进程,如父子、兄弟进程)
生命周期与文件系统绑定(需手动删除)与创建它的进程生命周期绑定(进程退出则销毁)
创建方式mkfifo命令或mkfifo()函数pipe()函数
典型场景无亲缘关系的进程通信、服务 - 客户端交互父子进程间的简单数据传递

4.5、典型示例

        首先我们先创建一个mkfifo.c文件:

        然后输入以下代码:

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>int main()
{int ret;ret = mkfifo("/home/liang/myfifo",0777);if(ret == -1){perror("mkfifo failed:");printf("creat failed!\n");return -1;}printf("creat succeed!\n");

        使用gcc编译器进行编译,然后运行:

       可以看到运行成功,我们再次使用 ls -l 查看该文件的权限:

        可以看到该文件类型为p,和其他文件不一样。

        注:如果再次运行mkfifo.c文件,管道会创造失败,因为mkfifo函数的规则是:如果指定路径已经存在同名文件(无论是否是有名管道),再次调用mkfifo会直接返回 - 1(创建失败),如下图所示:

        然后创建一个读进程:

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>int main()
{int ret;int fd;int nread;char readbuf[50]={0};ret = mkfifo("/home/liang/myfifo",0777);if(ret == -1){perror("mkfifo failed:");printf("creat failed!\n");return -1;}printf("creat succeed!\n");fd = open("/home/liang/myfifo",O_RDONLY);if(fd == -1){printf("open failed!\n");return -2;}printf("open succeed!\n");nread = read(fd,readbuf,50);printf("read %d byte from fifo %s\n",nread,readbuf);close(fd);return 0;
}

        使用gcc编译器进行编译,然后运行:

        我们发现读端并没有读到数据,因为并没有写数据,所以目前是阻塞的,然后我们再创建一个写进程:

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>int main()
{int fd;char writebuf[50]={"hello world!"};fd = open("/home/liang/myfifo", O_WRONLY);if(fd == -1){printf("open failed!\n");return -1;}printf("open succeed!\n");write(fd,writebuf,strlen(writebuf));close(fd);return 0;
}

        接着打开两个终端,依次启动读进程和写进程:

        打开读进程后因为没有数据写入,目前处于阻塞状态:

        接着执行写进程后,读进程收到数据,读取所收到的数据并打印了出来。                

http://www.dtcms.com/a/611145.html

相关文章:

  • 征程 6X 常见 kernel panic 问题
  • 复盘与导出工具最新版V35.0版本更新----修复东财智能选股,预测量能,开盘啦涨停闪退,炸板数量不匹配问题
  • 招聘网站咋做珠海溢动网络科技有限公司
  • discuz 网站风格境外公司注册
  • 网站的建设原始代码哪家公司做网站便宜
  • 网站建设这块是怎么挣钱汕头建站模板搭建
  • 高水平的郑州网站建设机械网站建设哪家好
  • RAG 和微调(Fine-tuning)核心对比:通俗版 + 实操选型
  • 第四章 Agent的几种经典范式
  • 发光二极管解析
  • 从今日市场动荡看TRS收益互换与场外个股期权系统开发紧迫性
  • 安全版普通用户获取系统对象的访问权限
  • 滕州做网站的多少预订网站模板
  • 免费网站入口网站空间哪家好
  • 松江网站建设哪家好国内外网站开发技术
  • 网站设计电商首页网站平台建设意见
  • BAS16,215 硅高速开关二极管 NXP安世半导体 集成电路芯片解析
  • LangChain的核心组件Messages之初体验
  • RocketMQ代码分析——DefaultLitePullConsumer
  • 六安网站建设招聘企业电子商务网站建设规划
  • Qt开发——常见控件(1)
  • 【WSL】C盘迁移
  • 上海小企业网站建设平台天眼查企业查询
  • 建设团购网站电子商务网站开发公司
  • 1.1.1 将TIA Opennes中添加本电脑用户
  • 代码随想录 763.划分字母区间
  • 网站导航包括only网站建设分析
  • 网站建站要多少钱智慧团建网站登录平台官网
  • 基于PVLIB的光伏发电量计算模型:SAPM-Sandia模型的原理与全流程解析
  • redis 在网站开发中怎么用安阳信息港网站