管道(Pipe)是一种在 Unix/Linux 等操作系统中**用于进程间通信的机制**。它可以用于在两个相关的进程之间传递数据,实现简单的数据流通信。管道分为匿名管道和命名管道(FIFO)两种。
管道的本质其实就是内核中的一块内存(或者叫内核缓冲区),这块缓冲区中的数据存储在一个环形队列中,因为管道在内核里边,因此我们不能直接对其进行任何操作。
管道特点:
管道对应的内核缓冲区大小是固定的,默认为4k(也就是队列最大能存储4k数据)
管道分为两部分:读端和写端(队列的两端),数据从写端进入管道,从读端流出管道。
管道中的数据只能读一次,做一次读操作之后数据也就没有了(读数据相当于出队列)。
管道是单工的:数据只能单向流动, 数据从写端流向读端。
对管道的操作(读、写)默认是阻塞的
读和写都是通过Linux文件IO函数实现的:
// 读管道
ssize_t read(int fd, void *buf, size_t count);
// 写管道的函数
ssize_t write(int fd, const void *buf, size_t count);
原理:
在上图中假设父进程通过一系列操作可以通过文件描述符表中的文件描述符fd3写管道,通过fd4读管道,然后再**通过 fork() 创建出子进程,那么在父进程中被分配的文件描述符 fd3, fd4也就被拷贝到子进程中,子进程通过 fd3可以将数据写入到内核的管道中,通过fd4将数据从管道中读出来。**也就能够进行数据的通信
匿名管道是一种只能在相关的进程之间使用的管道。通常,匿名管道通过 pipe
系统调用创建,其特点包括:
#include <unistd.h>
// 创建一个匿名的管道, 得到两个可用的文件描述符
int pipe(int pipefd[2]);
参数:传出参数,需要传递一个整形数组的地址,数组大小为 2,也就是说最终会传出两个元素
返回值:成功返回 0,失败返回 -1
以下是一个简单的 C 语言示例,演示了父子进程通过匿名管道进行通信:
#include <stdio.h>
#include <unistd.h>
int main(){
int pipefd[2];
char buffer[20];
// 创建管道
if(pipe(pipefd) == -1){
perror("pipe");
return -1;
}
pid_t pid = fork();
if(pid == -1){
perror("fork");
return 1;
}
else if(pid == 0) { // 子进程
// 关闭写入端
close(pipefd[1]);
read(pipefd[0], buffer, sizeof(buffer));
close(pipefd[0]);
printf("Child Process: Received message - %s\n", buffer);
} else { // 父进程
close(pipefd[0]); // 关闭读取段
write(pipefd[1], "Hello, child", 13);
close(pipefd[1]);
printf("Parent Process: Sent message\n");
}
return 0;
}
命名管道是一种在无关的进程之间进行通信的管道,它通过文件系统中的特殊文件来实现。特点包括:
以下是一个简单的 shell 命令示例,演示了命名管道的使用:
# 创建命名管道
mkfifo myfifo
# 在一个终端中写入数据
echo "Hello, FIFO!" > myfifo
# 在另一个终端中读取数据
cat myfifo
# 删除命名管道
rm myfifo
总的来说,管道是一种简单而有效的进程间通信方式,特别适用于相关进程之间的通信。
关于管道不管是有名的还是匿名的,在进行读写的时候,它们表现出的行为是一致的,下面是对其读写行为的总结:
读管道,需要根据写端的状态进行分析:
写管道,需要根据读端的状态进行分析: