您的位置:首页 > 财经 > 金融 > 深入理解Linux管道

深入理解Linux管道

2024/11/17 10:33:06 来源:https://blog.csdn.net/m0_74091159/article/details/142268154  浏览:    关键词:深入理解Linux管道

在Linux系统中,管道(Pipes)是一种强大的进程间通信(IPC)机制。通过管道,多个命令可以串联起来执行,前一个命令的输出作为下一个命令的输入,这种操作常见于Linux命令行中的流水线式操作。然而,管道不仅限于命令行使用,还可在编程中用于进程间通信。管道可以分为两类:匿名管道有名管道(FIFO,First In First Out)。

1. 管道的基本概念

Linux中的管道是操作系统用于在进程之间传递数据的机制。一个进程可以通过管道将其标准输出(stdout)传递给另一个进程作为标准输入(stdin)。这种方法常用于命令行操作,例如:

ls | grep "txt"

这个命令将 ls 命令的输出传递给 grep,从而筛选出包含"txt"的文件。

2. 匿名管道(Anonymous Pipe)

2.1 什么是匿名管道?

匿名管道是最常见的管道类型,通常用于父子进程之间的数据传输。它在创建时只存在于内存中,无法通过文件系统访问。由于其匿名性,匿名管道只能用于具有共同祖先的进程之间,通常由一个进程创建后与其子进程通信。

2.2 使用匿名管道

在编程中,匿名管道通常通过系统调用 pipe() 创建。该函数创建一对文件描述符,一个用于读取数据,另一个用于写入数据。

int pipefd[2]; pipe(pipefd);

pipefd[0] 是管道的读端,pipefd[1] 是写端。父进程可以通过写端向管道发送数据,而子进程则可以通过读端读取这些数据。

示例代码:

#include <stdio.h>
#include <unistd.h>int main() {int pipefd[2];pid_t pid;char buffer[100];// 创建匿名管道if (pipe(pipefd) == -1) {perror("pipe");return 1;}pid = fork();if (pid == -1) {perror("fork");return 1;}if (pid == 0) { // 子进程close(pipefd[1]); // 关闭写端read(pipefd[0], buffer, sizeof(buffer)); // 从管道读取数据printf("Child process read: %s\n", buffer);close(pipefd[0]);} else { // 父进程close(pipefd[0]); // 关闭读端write(pipefd[1], "Hello from parent", 17); // 向管道写入数据close(pipefd[1]);}return 0;
}
2.3 特性和限制
  • 单向通信:匿名管道只允许单向数据流动,一个进程写入,另一个进程读取。
  • 父子进程间通信:匿名管道主要用于具有共同祖先的进程,例如父子进程。
  • 进程生命周期:匿名管道的生命周期与创建它的进程相关,进程终止时,管道也随之销毁。

3. 有名管道(Named Pipe, FIFO)

3.1 什么是有名管道?

有名管道(FIFO, First In First Out)是一种存在于文件系统中的管道,它可以在不相关的进程之间实现通信。有名管道通过文件系统的路径名来标识,因而不同的进程只要知道该路径,就可以通过读写该管道文件进行通信。

3.2 创建和使用有名管道

有名管道可以使用 mkfifo 命令或系统调用 mkfifo() 创建。与匿名管道不同,有名管道可以在系统的任何地方被访问,且可以用于完全不相关的进程之间的通信。

创建有名管道

在命令行中,可以通过 mkfifo 命令创建一个有名管道:

mkfifo mypipe

在C语言中,mkfifo() 可以用于创建一个有名管道:

#include <sys/types.h>

#include <sys/stat.h>

int status = mkfifo("/tmp/myfifo", 0666); // 创建有名管道

3.3 使用有名管道

有名管道可以通过普通的文件I/O操作来使用。一个进程可以打开管道文件进行写操作,而另一个进程可以打开它进行读操作。

命令行中的使用示例:

# 在一个终端中,写入有名管道

echo "Hello, world" > mypipe

# 在另一个终端中,读取管道

cat < mypipe

C语言中的使用示例:

#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>int main() {int fd;char buffer[100];// 打开有名管道fd = open("/tmp/myfifo", O_RDONLY);read(fd, buffer, sizeof(buffer));printf("Read from FIFO: %s\n", buffer);close(fd);return 0;
}
3.4 特性和限制
  • 双向通信:有名管道可以在不相关的进程间实现双向通信,前提是双方都打开了管道的读写端。
  • 持久化:有名管道在文件系统中持久存在,直到被手动删除。
  • 同步问题:与匿名管道一样,有名管道的数据传输是同步的,读取操作会阻塞,直到写入数据完成。

4. 匿名管道与有名管道的区别

特性匿名管道有名管道
通信范围父子进程或兄弟进程间不相关的进程之间
管道标识不可通过文件系统访问通过路径标识,可持久化
生命周期进程结束后销毁文件系统中持久存在
数据传输方向单向可双向(需读写都打开)

5. 管道的进阶使用

5.1 多进程通信的管道

在复杂的系统中,多个进程之间可以通过多重管道来实现复杂的数据通信。例如,可以创建多个匿名管道来在父进程和多个子进程之间传递数据。

5.2 管道的阻塞与非阻塞模式

默认情况下,管道是阻塞的:写入端没有数据时,读取端会阻塞等待数据,反之亦然。可以通过 fcntl() 函数设置管道为非阻塞模式,从而避免进程阻塞。

5.3 与其他IPC机制结合使用

管道可以与其他进程间通信机制(如共享内存、消息队列等)结合使用,构建更复杂的进程通信模型。例如,使用共享内存处理大量数据,而使用管道来同步各个进程的工作。

6. 小结

Linux管道(包括匿名管道和有名管道)是实现进程间通信的核心工具。匿名管道适用于父子进程之间的快速通信,而有名管道可以跨越不相关的进程,实现更加灵活的通信需求。理解并熟练运用这两种管道,可以帮助开发人员在系统编程中高效实现进程间的数据交互。

7.思考

1. 如果所有指向管道写端的文件描述符都关闭了(管道写端的引用计数等于0),
而仍然有进程从管道的读端读数据,那么管道中剩余的数据都被读取后,
再次 read 会返回0,就像读到文件末尾一样。

2. 如果有指向管道写端的文件描述符没关闭(管道写端的引用计数大于0),
而持有管道写端的进程也没有向管道中写数据,这时有进程从管道读端读数据,
那么管道中剩余的数据都被读取后,再次 read 会阻塞,
直到管道中有数据可读了才读取数据并返回。

考虑到如下代码:

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
int main(void)
{int n;char buff[128];pid_t pid;int fd[2];if(pipe(fd)<0)    {perror("pipe");exit(0);}if((pid=fork())<0){perror("fork");exit(0);}if(pid>0){/* parent */    printf("+++++++++++++\n");close(fd[0]);        write(fd[1],"hello world",11);//sleep(5);//write(fd[1],"I am a Student",14);printf("+++++++++++++\n");}else{printf("--------------\n");//close(fd[1]);memset(buff,0,128);n = read(fd[0],buff,20);printf("buff=%s\n",buff);memset(buff,0,128);printf("read twice\n");n = read(fd[0],buff,20);printf("buff=%s\n",buff);printf("--------------\n");}return 0;
}

父进程关闭了读端口,通过写端口向pipe中写入了hello world。然后父进程结束。关闭相关文件(读写)描述符

  子进程在关闭写端口的时候,父进程结束时候,写文件描述符引用计数为0。所以子进程再次读取后返回0。子进程结束退出。

  子进程在不关闭写端口的时候,父进程结束时候,写文件描述符引用计数为1(自己的没关闭)。所以子进程再次读取时候陷入阻塞状态。

  因为父进程是在SHELL下执行的。所以当父进程结束时候,Shell进程认为命令执行结束了,于是打印Shell提示符,而子进程等待读取输入。

父进程已经结束,不会给他输入数据,而子进程本身只是为了读取而不是向管道写数据。所以子进程一直在后台运行,通过ps命令可以查看到子进程信息。

  所以,子进程只用到读端,因而把写端关闭。防止造成子进程做无用功。。。

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com