‎ദ്ദിᵔ.˛.ᵔ₎头像
linux 进程间通信封面图

linux 进程间通信

管道知识总结

管道是 Unix/Linux 中最古老的进程间通信(IPC)方式之一,本质是内核中的一块缓冲区,通过文件描述符暴露给进程,使用方式与文件一致,体现了“Linux 一切皆文件”的思想。管道分为匿名管道和命名管道。


1. 管道的基本概念

  • 定义:从一个进程连接到另一个进程的数据流。
  • 典型命令:who | wc -l,who 进程的标准输出写入管道,wc 进程从管道读取。
  • 分类:
    • 匿名管道:pipe
    • 命名管道:FIFO,mkfifo

2. 匿名管道

2.1 创建与使用

#include <unistd.h>
int pipe(int fd[2]);
  • fd[0]:读端
  • fd[1]:写端
  • 成功返回 0,失败返回错误码。

2.2 原理

  • 父进程调用 pipe 创建管道,得到两个文件描述符。
  • fork 后子进程继承这两个描述符,父子进程可通过管道通信。
  • 通常父进程关闭读端、子进程关闭写端,形成单向数据流。
  • 站在文件描述符角度:管道对应两个 struct file,一个读、一个写。
  • 站在内核角度:管道有数据页、读操作、写操作,使用方式与文件一致。

2.3 代码示例

从键盘读数据,写入管道,再从管道读出写到屏幕:

int fds[2];
char buf[100];
pipe(fds);
while (fgets(buf, 100, stdin)) {
    write(fds[1], buf, strlen(buf));
    memset(buf, 0, sizeof(buf));
    int len = read(fds[0], buf, 100);
    write(1, buf, len);
}

2.4 用 fork 共享管道

  • 父进程创建管道 pipe(fds)。
  • fork 出子进程。
  • 父进程关闭 fd[0],子进程关闭 fd[1],即可实现父写子读或父读子写。

2.5 进程池应用

利用匿名管道可以编写进程池:

  • 主进程创建多个子进程,每个子进程对应一个管道。
  • 子进程通过 dup2(pipefd[0], 0) 将管道读端重定向到标准输入,然后循环 read(0, &cmd, sizeof(cmd)) 读取任务。
  • 父进程保存每个管道的写端,通过轮询方式派发任务。
  • 清理时父进程关闭所有写端,waitpid 回收子进程。
  • 相关文件:Channel.hpp、ProcessPool.hpp、Task.hpp、Main.cc、Makefile。

3. 命名管道(FIFO)

3.1 为什么需要命名管道

匿名管道只能用于具有共同祖先的进程。命名管道可以在不相关的进程之间交换数据。

3.2 创建

命令行:

mkfifo filename

程序内:

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

3.3 匿名管道与命名管道的区别

对比项匿名管道命名管道
创建方式pipe 创建并打开mkfifo 创建,open 打开
使用范围亲缘进程任意进程
语义相同相同

3.4 打开规则

  • 以读方式打开 FIFO:
    • O_NONBLOCK 禁用:阻塞直到有进程以写方式打开。
    • O_NONBLOCK 启用:立刻返回成功。
  • 以写方式打开 FIFO:
    • O_NONBLOCK 禁用:阻塞直到有进程以读方式打开。
    • O_NONBLOCK 启用:立刻返回失败,错误码 ENXIO。

3.5 实例

  1. 文件拷贝:一个进程读文件写入命名管道,另一个进程读管道写入目标文件。
  2. server & client 通信:
    • server:mkfifo("mypipe", 0644),open("mypipe", O_RDONLY),循环读并打印。
    • client:open("mypipe", O_WRONLY),读键盘输入写入管道。
    • 运行结果:server 等待,client 输入后 server 打印,client 退出后 server 读到 0 退出。

4. 管道读写规则

场景O_NONBLOCK 禁用O_NONBLOCK 启用
没有数据可读read 阻塞,直到有数据read 返回 -1,errno = EAGAIN
管道满write 阻塞,直到有进程读走数据write 返回 -1,errno = EAGAIN
所有写端关闭read 返回 0同左
所有读端关闭write 产生 SIGPIPE,可能导致进程退出同左
  • 写入数据量 ≤ PIPE_BUF:Linux 保证写入原子性。
  • 写入数据量 > PIPE_BUF:不保证原子性。

5. 管道特点

  1. 只能用于具有共同祖先的进程之间通信。
  2. 提供流式服务。
  3. 生命周期随进程,进程退出管道释放。
  4. 内核会对管道操作进行同步与互斥。
  5. 管道是半双工的,数据只能单向流动;需要双向通信时,需建立两个管道。
  6. 验证管道通信的四种情况:
    • 读正常 && 写满
    • 写正常 && 读空
    • 写关闭 && 读正常
    • 读关闭 && 写正常

6. 管道与共享内存配合

共享内存是最快的 IPC 形式,但没有同步与互斥,会带来并发问题。可以借助管道实现访问控制:

  • 创建 FIFO 作为同步机制。
  • server 端等待 Wait(fd),client 写入共享内存后 Signal(fd)。
  • server 收到信号后读取共享内存。
  • 这样让进程执行产生一定顺序性,但注意这只是一个演示,实际需更完善的同步机制。

7. 内核如何管理 IPC 资源

Linux 内核用 struct ipc_ids、struct ipc_id_ary、struct kern_ipc_perm 等结构统一管理 System V IPC 资源:

  • ipc_ids:管理 IPC 标识符。
  • ipc_id_ary:保存 kern_ipc_perm 指针数组。
  • kern_ipc_perm:权限、key、uid、gid、mode 等。
  • 具体资源:
    • 共享内存:struct shmid_kernel
    • 消息队列:struct msg_queue
    • 信号量:struct sem_array

这些 IPC 资源生命周期随内核,必须显式删除,否则不会自动清除。


8. 小结

  • 管道是 Linux 中基于文件描述符的进程间通信机制。
  • 匿名管道用于亲缘进程,命名管道用于任意进程。
  • 管道读写有阻塞/非阻塞规则,写端全关 read 返回 0,读端全关 write 触发 SIGPIPE。
  • 管道可用于进程池、minishell 管道实现、共享内存访问控制等场景。
  • 理解管道要抓住两点:文件描述符视角和内核缓冲区视角。

转载自 CSDN-专业IT技术社区

原文链接:https://blog.csdn.net/Diopeng_out/article/details/166590984

文章来源转载

评论

评论列表

微信小程序
QQ小程序

关于作者

点赞数:0
关注数:0
粉丝:0
文章:0
关注标签:0
加入于:--