管道(PIPE)
1. 基本概念
Linux 管道(Pipe)是一种用于在进程之间进行进程间通信的技术。管道是一个先进先出(FIFO)的数据结构,用于将一个进程的输出作为另一个进程的输入。管道可以通过调用 pipe()
系统调用创建,并通过使用 I/O 函数(如 read()
和 write()
)进行读写操作。Linux 管道可以用于实现复杂的进程间通信机制,如管道线(pipeline)、命令行管道(command line pipe)等。
常说的管道通常指的是无名管道(PIPE)和有名管道(FIFO)。
2. 无名管道(PIPE)
2.1 PIPE 的特征
- 没有名字,无法使用 ·open()`。
- 只能用于亲缘进程间(父子进程、兄弟进程、祖孙进程)通信。
- 半双工工作模式:读写端分开。
- 写入操作不具有原子性,因此只能用于一对一的简单通信。
- 不能使用
lseek()
来定位。
2.2 PIPE 创建
在 Linux 系统中 pipe()
函数用于创建一个管道。函数原型为:
int pipe(int pipefd[2]);
该函数接受一个参数:
pipefd
:指向管道的整型数组的指针。如果创建成功,则该函数返回 0。否则,返回一个非 0 值。
该函数会创建一个管道,将两个文件描述符存储在 pipefd
数组中。第一个文件描述符用于读取管道,第二个文件描述符用于写入管道。这两个文件描述符可以用来通过管道进行进程间通信。
2.3 示例代码
下面的示例代码实现了父子进程间 pipe 管道的通信:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
int main(int argc, char *argv[])
{
// 管道文件描述符
int pipefd[2];
// 创建管道
if (pipe(pipefd) < 0) {
perror("pipe() fail");
exit(EXIT_FAILURE);
}
// 创建子进程
pid_t pid = fork();
if (pid < 0) {
perror("fork() fail");
exit(EXIT_FAILURE);
}
// 子进程
if (pid == 0) {
// 关闭读取管道的文件描述符
close(pipefd[0]);
// 写入管道
const char *message = "Hello parent!\n";
write(pipefd[1], message, strlen(message));
// 关闭写入管道的文件描述符
close(pipefd[1]);
}
// 父进程
if (pid > 0) {
// 关闭写入管道的文件描述符
close(pipefd[1]);
// 读取管道
char buf[30];
bzero(buf, 30);
read(pipefd[0], buf, sizeof(buf));
printf("from child: %s", buf);
// 关闭读取管道的文件描述符
close(pipefd[0]);
}
return 0;
}
注意:父进程必须先创建PIPE,然后再创建子进程,这样子进程才能继承父进程已经产生的PIPE的文件描述符。
3. 有名管道(FIFO)
3.1 FIFO 的特征
- 有名字,存储于普通文件系统之中。
- 任何具有相应权限的进程都可以使用open()来获取FIFO的文件描述符。
- 跟普通文件一样:使用统一的
read( )/write()
来读写。 - 跟普通文件不同:不能使用lseek( )来定位。
- 具有写入原子性,支持多写者同时进行写操作而数据不会互相践踏。
- First In First Out,最先被写入FIFO的数据,最先被读出来。
3.2 FIFO 的创建
在 Linux 系统中,mkfifo()
函数用于创建一个命名管道。函数原型为:
int mkfifo(const char *pathname, mode_t mode);
该函数接受两个参数:
pathname
:命名管道的路径。mode
:命名管道的权限。如果创建成功,则该函数返回 0。否则,返回一个非 0 值。
该函数会创建一个命名管道,它与普通的管道类似,但是它有一个文件名,可以通过文件名来访问它。命名管道可以让不同进程间通过文件系统路径进行通信。
3.3 示例代码
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
//管道文件只能在tmp路径下或者家目录生成
#define FIFO "/tmp/myfifo"
int main(int argc, char *argv[])
{
// 创建一个FIFO文件
if (access(FIFO, F_OK)) {
if (mkfifo(FIFO, 0666) == -1) {
perror("mkfifo error");
exit(1);
}
}
// 打开FIFO文件
int fd = open(FIFO, O_WRONLY|O_RDONLY);
if (fd == -1) {
perror("open error");
exit(1);
}
// 往FIFO文件中写数据
const char *msg = "Hello from process 1\n";
int n = write(fd, msg, strlen(msg));
printf("%d bytes write fifo\n", n);
// 读取FIFO文件中的数据
char buf[20];
bzero(buf, 20);
read(fd, buf, 20);
printf("from FIFO: %s\n", msg);
sleep(1);
// 关闭FIFO文件
close(fd);
return 0;
}