Linux应用开发-10-管道
管道(Shell 命令 |)
管道(Pipe)是 Linux 进程间通信(IPC)的一种机制。它允许数据从一个进程的输出端*,直接“流向”另一个进程的输入端。
对比于信号:信号(Signal)也是一种通信方式,但它只能传递一个简单的“信号值”。管道的优势在于,它可以传输大量的、连续的“数据流”(一整篇文章或一个命令的所有输出结果)。
命令ps -aux | grep root
ps -aux:这个进程本应将其输出(所有进程列表)打印到屏幕(标准输出)。
grep root:这个进程本应等待用户从键盘输入(标准输入)。
| (管道):| 符号在这里扮演了“接管”的角色。它将 ps 命令的标准输出,直接连接(重定向)到了 grep 命令的标准输入。
结果:grep 会读取从管道传来的每一行(即每个进程信息),如果某一行包含 “root” 字符串,grep 就会把这一行打印出来。
命名管道 (FIFO) - 匿名管道 (|),进程间通信(IPC)
- 匿名管道 (|):没有名字,不存在于文件系统中。最常见的形态就是我们在 shell
操作中最常用的“|”,它的特点是只能在父子进程中使用。随进程的结束而自动消失。 - 命名管道 (FIFO):有名字,是一个真实存在于磁盘上的文件(file 命令会显示其类型为
fifo)。允许任何知道其路径名的进程进行通信,即使它们没有任何关系。永久存在于文件系统中,除非手动 rm 删除它。
pipe() (匿名管道) - 用于父子进程通信
pipe() 函数用于创建一个匿名的、单向的数据通道。它在内核的内存中创建,没有任何文件系统路径,因此只能被“继承”了它的文件描述符的进程(即 fork() 出来的子进程)访问。包含于unistd.h头文件
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h> // for pipe(), fork(), read(), write(), close()
#include <sys/wait.h> // for wait()
#include <string.h>int main(void)
{int pipefd[2]; // pipefd[0] 是读, pipefd[1] 是写pid_t pid;char buffer[100];// (1) 在 fork() 之前创建管道// 这样父子进程才能共享这两个文件描述符if (pipe(pipefd) == -1) {perror("pipe 创建失败");exit(1);}// (2) 创建子进程pid = fork();if (pid < 0) {perror("fork 失败");exit(1);}if (pid == 0) {// --- 子进程 (读取者) ---printf("(子进程: %d) 已启动...\n", getpid());// (3) 子进程不写,所以必须关闭“写”端close(pipefd[1]);// (4) 从管道的“读”端读取数据// 如果管道为空,read() 会在此处阻塞(等待)int n = read(pipefd[0], buffer, sizeof(buffer));if (n > 0) {buffer[n] = '\0'; // 确保字符串结束printf("(子进程) 收到消息: '%s'\n", buffer);}// (5) 关闭“读”端close(pipefd[0]);exit(0);} else {// --- 父进程 (写入者) ---printf("(父进程: %d) 已启动,将向管道写入...\n", getpid());// (3) 父进程不读,所以必须关闭“读”端close(pipefd[0]); const char *msg = "Hello, Linux!";// (4) 向管道的“写”端写入数据write(pipefd[1], msg, strlen(msg));// (5) 关闭“写”端// 关闭写端是必须的!// 只有当所有“写”端都关闭后,子进程的 read() 才能读到 EOF(0) 并结束等待close(pipefd[1]); // (6) 等待子进程结束,回收资源wait(NULL);printf("(父进程) 子进程已退出,程序结束。\n");}return 0;
}

mkfifo() (命名管道/FIFO) - 用于任意进程通信
fifo (First-In, First-Out)在文件系统中有名字(路径)。mkfifo() 函数就是用来创建这个文件的。一旦创建,任何知道这个文件路径的进程都可以用 open() 打开它来进行读写,完全不需要它们之间有父子关系。
-
fifo_reader.c (读取者)
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/stat.h> #include <fcntl.h> // for open() #include <string.h> #include <errno.h> // for errno#define FIFO_PATH "/tmp/myfifo" // 定义管道的路径int main(void) {int fd;char buffer[100];// (1) 创建 FIFO 文件, 0666 是权限// 我们检查 EEXIST (文件已存在) 错误,如果已存在则不是问题if (mkfifo(FIFO_PATH, 0666) < 0 && errno != EEXIST) {perror("mkfifo 创建失败");exit(1);}printf("(读取者) 正在打开 FIFO... (将会阻塞,直到写入者打开)\n");// (2) 以只读方式打开 FIFO// 关键:open() 会在此处阻塞(暂停),// 直到有另一个进程以“写”方式打开同一个 FIFOfd = open(FIFO_PATH, O_RDONLY);if (fd == -1) {perror("open 失败");exit(1);}printf("(读取者) FIFO 已打开,等待数据...\n");// (3) 从 FIFO 读取数据// 关键:read() 也会在此处阻塞,直到有数据被写入int n = read(fd, buffer, sizeof(buffer));if (n > 0) {buffer[n] = '\0';printf("(读取者) 收到消息: '%s'\n", buffer);}// (4) 关闭并删除close(fd);unlink(FIFO_PATH); // 清理 FIFO 文件return 0; } -
fifo_writer.c (写入者)
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/stat.h> #include <fcntl.h> // for open() #include <string.h>#define FIFO_PATH "/tmp/myfifo" // 必须和读取者使用相同的路径int main(void) {int fd;const char *msg = "Hello from the other process!";printf("(写入者) 正在打开 FIFO... (将会阻塞,直到读取者打开)\n");// (1) 以只写方式打开 FIFO// 关键:open() 也会在此处阻塞(暂停),O_WRONLY读写模式默认暂停// 直到有另一个进程以“读”方式打开同一个 FIFO,fd = open(FIFO_PATH, O_WRONLY);if (fd == -1) {perror("open 失败");exit(1);}printf("(写入者) FIFO 已打开,正在写入数据...\n");// (2) 写入数据write(fd, msg, strlen(msg));// (3) 关闭close(fd);printf("(写入者) 数据已写入,退出。\n");return 0; }
读取者”等待数据休眠:fifo_reader 进程调用 read(),发现管道是空的。内核将其标记为“正在等待此 FIFO 上的数据”并使其进入 Sleep 状态。
“写入者”写入数据:另一个 fifo_writer 进程调用 write(),成功将数据写入了管道。
“读取者”被唤醒:内核在数据写入后,立刻检查到“fifo_reader 进程正在等这个数据!”内核“唤醒” fifo_reader 进程(将其放回 CPU 的“可运行”队列)。read() 函数终于可以从管道中复制数据到 buffer 中,然后返回,您的程序继续向下执行 printf。
