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

linux系统中进程通信之管道

一、进程间通信概述

进程间通信简介

Linux 下的进程间通信方式基本上继承自 UNIX 平台。
在发展历史上,AT&T 贝尔实验室和**加州大学伯克利分校(UCB)**是两大贡献者:

  • AT&T:主要改进和扩展 UNIX 早期的通信方式,形成了 System V IPC

  • UCB(伯克利):突破单机限制,引入了 Socket 通信机制

目前 Linux 同时继承了这两种方式的优点。

进程间通信分类和特点

  • 1.管道(Pipe / FIFO)

    • 无名管道:用于具有亲缘关系(父子进程)的通信。

    • 命名管道(FIFO):既可用于有亲缘关系进程,也可用于无亲缘关系进程。

    • 特点:适合一对一通信,数据以字节流方式传输。

  • 2.信号(Signal)

    • 特点:异步通信机制,软件层模拟中断,用于通知进程某个事件的发生(类似 STM32 中断)。

    • 应用:进程控制,如暂停、终止、继续运行等。

  • 3.消息队列(Message Queue)

    • 特点:内核维护消息链表,支持多个消息存放,允许有序、按权限的读写操作。

    • 优势:克服了管道不便管理的问题,数据更灵活。

  • 4.共享内存(Shared Memory)

    • 特点:多个进程可直接访问同一块内存空间,数据传输效率最高。

    • 注意:需要配合信号量/互斥锁实现同步,避免数据冲突。

    • 应用:大数据量交互。

  • 5.信号量(Semaphore)

    • 特点:不用于传输数据,主要用于进程/线程间的同步与互斥(类似锁机制)。

    • 应用:控制共享资源访问,避免竞争条件。

  • 6.套接字(Socket)

    • 特点:最通用的 IPC 机制,可用于本机进程通信,也支持不同机器之间的网络通信。

    • 应用:分布式系统、客户端-服务器模型。

二、无名管道

无名管道的概述

无名管道(pipe)是最早的进程间通信方式之一,主要用于具有亲缘关系的进程(父子进程)之间

管道的基本概念

  • 管道可以类比为一个“水管”,只有 一个入口(写端)一个出口(读端)

  • 数据从写端写入,从读端读出,遵循 先进先出(FIFO) 原则。

  • 管道本质上是由 内核创建并维护的一段缓存(内存空间)

特点

  • 1.亲缘关系限制

    • 无名管道只能用于 父子进程兄弟进程 之间通信。

    • 这是因为 pipe() 创建的文件描述符是通过 fork() 继承给子进程的。

  • 2.半双工通信

    • 无名管道是 单向传输,即固定一端写、一端读。

    • 若要实现双向通信,需要建立两条管道。

  • 3.存在于内存的特殊文件

    • 无名管道由内核创建,仅存在于 内存 中,不会出现在文件系统里。

    • 进程通过 read/write 系统调用对其进行读写操作。

  • 4.没有文件名和文件节点

    • 与命名管道(FIFO)不同,无名管道没有路径名,不能通过路径访问,只能通过文件描述符使用。

  • 5.阻塞特性

    • 读阻塞:如果管道中没有数据,读操作会阻塞,直到有数据写入。

    • 写阻塞:管道有缓存区(通常 4KB/64KB),当缓存写满时,写操作会阻塞,直到有数据被读出。

  • 6.先进先出(FIFO)队列结构

    • 管道内部数据遵循 先进先出(First In First Out)规则。

    • 一旦数据被读取,就会从管道中移除,不会重复读取。


使用场景

1. 单进程内读写(自我通信,意义不大)

对应第一张图:

  • 进程 A 自己写入数据,再自己读取数据。

  • 实际开发中这种方式很少用,一般用于测试。

2. 父子进程之间通信

对应第二张图:

  • 父进程和子进程通过 pipe() 系统调用共享同一条管道。

  • 常见模式:

    • 父进程写,子进程读。

    • 或者子进程写,父进程读。


📌 总结一句话:
无名管道就是一个由内核维护的缓存区,适合父子进程之间通过“写端写、读端读”的方式进行单向通信。

无名管道的相关函数

pipe() —— 创建无名管道

  • 头文件

    #include <unistd.h>
  • 函数原型

    int pipe(int pipefd[2]);
    

  • 参数说明

    • pipefd[0] → 管道的 读端口(用于读取数据)。

    • pipefd[1] → 管道的 写端口(用于写入数据)。

  • 返回值

    • 0 :成功创建管道。

    • -1 :失败(errno 被设置,常见错误是内存不足或进程文件描述符用尽)。

  • 功能

    • 在内核中创建一条无名管道。

    • 通过 pipefd 数组返回两个文件描述符,进程可以用它们进行 read()write() 操作。

  • 注意事项

    • 通常先调用 pipe() 创建管道,再调用 fork() 创建子进程。

    • 这样父子进程就能通过同一条管道通信。

无名管道的使用实例

父进程写,子进程读

#include <unistd.h>   // pipe, fork, read, write, close
#include <stdio.h>    // printf, perror
#include <stdlib.h>   // exit
#include <string.h>   // strlenint main() {int fd[2];   // fd[0] 读端, fd[1] 写端pid_t pid;char buf[100];// 第一步:创建无名管道if (pipe(fd) == -1) {perror("pipe error");exit(1);}// 第二步:创建子进程pid = fork();if (pid < 0) {perror("fork error");exit(1);}if (pid > 0) {  // 父进程:写数据close(fd[0]);  // 父进程不用读端,关闭const char *msg = "Hello from parent!";write(fd[1], msg, strlen(msg) + 1);  // 写入管道close(fd[1]);  // 写完关闭写端} else {  // 子进程:读数据close(fd[1]);  // 子进程不用写端,关闭read(fd[0], buf, sizeof(buf));   // 从管道读数据printf("子进程读到的数据: %s\n", buf);close(fd[0]);  // 读完关闭读端}return 0;
}

现象:

子进程写,父进程读

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>int main() {int fd[2];          // fd[0]读端,fd[1]写端pid_t pid;char buf[100];// 创建无名管道if (pipe(fd) == -1) {perror("pipe error");exit(1);}// 创建子进程pid = fork();if (pid < 0) {perror("fork error");exit(1);}if (pid == 0) {// 子进程:写数据close(fd[0]);   // 关闭读端char *msg = "Hello, father! This is child.\n";write(fd[1], msg, strlen(msg)+1); // 写数据close(fd[1]);   // 写完关闭写端exit(0);} else {// 父进程:读数据close(fd[1]);   // 关闭写端read(fd[0], buf, sizeof(buf)); // 读取数据printf("父进程读取到的数据: %s\n", buf);close(fd[0]);   // 关闭读端wait(NULL);     // 等待子进程退出}return 0;
}

现象:

注意事项

  • 通常先调用 pipe() 创建管道,再调用 fork() 创建子进程。

  • 这样父子进程就能通过同一条管道通信。

  • 父进程和子进程需分别关闭不用的端口(父关读端,子关写端)。

三、命名管道

命名管道的概述

  • 命名管道(Named Pipe / FIFO)
    命名管道是一种 特殊的文件类型,和普通文件不同,它的数据传输是 先进先出(FIFO) 的。

  • 它在文件系统中以 文件节点的形式存在(即有名字),因此可以通过路径来找到它,并用 open() 打开。

  • 命名管道允许 没有亲缘关系的进程之间 进行通信(这是与无名管道的重要区别)。

命名管道的特点

  • 1.FIFO 文件不是普通文件

    • 它是特殊文件(FIFO 文件)。

    • 文件节点存在于文件系统中,但管道的数据存储在内存中,不占用磁盘空间。

    • 因此,FIFO 文件大小始终显示为 0

  • 2.操作方式

    • 不是普通文件,只能通过 文件 I/O 函数(如 openreadwriteclose)进行操作。

  • 3.适用范围

    • 可用于 任意两个进程之间 的通信(不仅限于父子进程)。

  • 4.文件名和文件节点

    • 命名管道有文件名(路径名),需要先在文件系统中创建(例如通过 mkfifo)。

    • 进程通过该文件名找到管道并进行通信。

  • 5.阻塞特性

    • 读阻塞:如果管道中没有数据,读操作会阻塞,直到有数据写入。

    • 写阻塞:管道有一定的缓存区,如果写满了,写操作会阻塞,直到有空间可用。

  • 6.先进先出(FIFO)

    • 管道内部遵循队列规则,数据按照写入顺序排队,先写入的先读出。

  • 7.打开方式

    • O_RDONLY 打开时,默认作为 读端

    • O_WRONLY 打开时,默认作为 写端

命名管道的相关函数

1.头文件

#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>

2. 函数原型

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

3. 参数

  • pathname:要创建的 FIFO 文件的路径名(字符串)。

  • mode:指定 FIFO 文件的权限(类似 openchmod,用八进制数表示,比如 0666)。

    • 高位会受 umask 影响。

    • 常用:0666(读写权限),0777(读写执行权限)。

4. 返回值

  • 成功:返回 0

  • 失败:返回 -1,并设置 errno(比如 EEXIST 表示文件已存在)。

5. 示例代码

#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>int main() {const char *fifo_path = "myfifo";// 创建命名管道,权限0666if (mkfifo(fifo_path, 0666) == -1) {perror("mkfifo error");exit(EXIT_FAILURE);}printf("FIFO %s created successfully.\n", fifo_path);return 0;
}

命名管道的使用实例

单进程命名管道读写

#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <string.h>#define FIFO_PATH "myfifo.pipe"int main() {char buf[128] = {0};// 创建命名管道if (mkfifo(FIFO_PATH, 0666) == 0) {printf("命名管道 %s 创建成功\n", FIFO_PATH);}// 打开管道读写端int fd = open(FIFO_PATH, O_RDWR);if (fd == -1) {printf("打开读写端失败!\n");exit(1);}printf("%s 打开读写端成功\n", FIFO_PATH);// 往管道写数据char *msg = "Hello World";if (write(fd, msg, strlen(msg)) > 0) {printf("向 %s 成功写入: %s\n", FIFO_PATH, msg);}// 读出数据int n = read(fd, buf, sizeof(buf));if (n > 0) {printf("读取 %d 字节数据: %s\n", n, buf);} else {printf("读取失败或无数据\n");}close(fd);//删除管道remove(FIFO_PATH);return 0;}

现象:

父子进程一次对话

#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <string.h>#define FIFO_PATH "myfifo.pipe"int main() {char buf[128] = {0};// 创建命名管道if (mkfifo(FIFO_PATH, 0666) == 0) {printf("命名管道 %s 创建成功\n", FIFO_PATH);}//创建子进程pid_t pid = fork();if(pid < 0) {printf("子进程创建失败\r\n");}else if(pid > 0) {int fd = open(FIFO_PATH, O_RDWR);if (fd == -1) {printf("父进程打开读写端失败!\n");exit(1);}printf("父进程打开读写端成功\n");fgets(buf, sizeof(buf), stdin);write(fd, buf, strlen(buf));memset(buf, 0, sizeof(buf));usleep(1000);int n = read(fd, buf, sizeof(buf)-1);if (n > 0) {buf[n] = '\0';printf("父进程读到的数据为: %s\n", buf);
}memset(buf, 0, sizeof(buf));}else {int fd = open(FIFO_PATH, O_RDWR);if (fd == -1) {printf("子进程打开读写端失败!\n");exit(1);}printf("子进程打开读写端成功\n");int n = read(fd, buf, sizeof(buf)-1);if (n > 0) {buf[n] = '\0';printf("子进程读到的数据为: %s\n", buf);memset(buf, 0, sizeof(buf));usleep(1000);fgets(buf, sizeof(buf), stdin);write(fd, buf, strlen(buf));memset(buf, 0, sizeof(buf));}return 0;}
}

现象:

非亲缘关系进程通信

代码

A进程:

#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <string.h>#define FIFO_PATH "process.pipe"int main() {char buf[128] = {0};if (mkfifo(FIFO_PATH, 0666) == 0) {printf("命名管道 %s 创建成功\n", FIFO_PATH);}int fd = open(FIFO_PATH, O_RDWR);if (fd == -1) {printf("A进程打开读写端失败!\n");exit(1);}printf("A父进程打开读写端成功\n");fgets(buf, sizeof(buf), stdin);write(fd, buf, strlen(buf));return 0;
}

B进程:

#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <string.h>#define FIFO_PATH "process.pipe"int main() {char buf[128] = {0};int fd = open(FIFO_PATH, O_RDWR);if (fd == -1) {printf("B进程打开读写端失败!\n");exit(1);}printf("B父进程打开读写端成功\n");int n = read(fd, buf, sizeof(buf)-1);if (n > 0) {buf[n] = '\0';printf("B进程读到的数据为: %s\n", buf);}}

现象:

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

相关文章:

  • ip下的网站吗wordpress建站企业
  • 企业官网项目方案(Vue3+Node 全栈)
  • 最火的传奇手游网站网站文章分类
  • 算法<C++>——二分查找
  • MIDI协议与Arduino编程
  • 【开题答辩全过程】以 儿童口腔诊所私域管理系统为例,包含答辩的问题和答案
  • 什么网站做app好网站建设的后如何发布
  • 从零开始的Qt开发指南:(二)使用Qt Creator构建项目与Qt底层机制的深度解析
  • UVa 1326 Jurassic Remains
  • Readest(电子书阅读器) v0.9.91
  • Flink 优化-数据倾斜
  • 遵义网站网站建设江阴便宜做网站
  • 大模型RLHF:PPO原理与源码解读
  • Mojo变量知识点解读
  • Linux之rsyslog(2)输入输出配置
  • 整体设计 全面梳理复盘 之17 三套表制表的支持和支撑以及编程基础 之2
  • 凯文·凯利《2049:未来10000天的可能》
  • 网站百度建设高端网站设计百家号
  • ctypes.pythonapi.PyThreadState_SetAsyncExc作用详解
  • pyside6常用控件: QPushButton()按钮切换、带图片的按钮
  • Python逻辑运算符
  • MinGW下载、安装和使用教程(附安装包,适合新手)
  • lol做任务领头像网站微商城网站建设平台
  • 百日挑战——单词篇(第十二天)
  • (单调队列、ST 表)洛谷 P2216 HAOI2007 理想的正方形 / P2219 HAOI2007 修筑绿化带
  • Spark RDD 编程从驱动程序到共享变量、Shuffle 与持久化
  • 网站 面包屑网站开发工作流审批流
  • 网站建设广金手指六六十四在线建站系统
  • 排序还有分页
  • electron对于图片/视频无法加载的问题