进程间通信方式-管道

发布时间:2024年01月08日

1. 概述

管道(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将数据从管道中读出来。**也就能够进行数据的通信

2. 匿名管道

匿名管道是一种只能在相关的进程之间使用的管道。通常,匿名管道通过 pipe 系统调用创建,其特点包括:

  • 单向通信: 匿名管道是单向的,可以是单向读或单向写。对于双向通信,需要创建两个管道。
  • 进程衍生关系: 通常在创建子进程时,父进程会创建管道,并将其传递给子进程。
  • 阻塞: 如果管道中没有数据可读,读取进程会被阻塞,直到有数据可用。同样,如果管道已满,写入进程会被阻塞,直到有空间可用。
  • 生命周期: 匿名管道在进程间通信结束后自动被关闭,且无法用于无关的进程之间通信。
创建函数
#include <unistd.h>
// 创建一个匿名的管道, 得到两个可用的文件描述符
int pipe(int pipefd[2]);
  • 参数:传出参数,需要传递一个整形数组的地址,数组大小为 2,也就是说最终会传出两个元素

    • pipefd[0]: 对应管道读端的文件描述符,通过它可以将数据从管道中读出
    • pipefd[1]: 对应管道写端的文件描述符,通过它可以将数据写入到管道中
  • 返回值:成功返回 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;
}

在这里插入图片描述

3. 有名管道

命名管道是一种在无关的进程之间进行通信的管道,它通过文件系统中的特殊文件来实现。特点包括:

  • 命名: 命名管道有一个关联的文件路径,可以通过文件路径在无关的进程之间进行通信。
  • 阻塞: 与匿名管道类似,命名管道也会在读取端没有数据可读或写入端已满时阻塞。
  • 持久性: 命名管道的生命周期独立于创建进程,需要显式删除。

以下是一个简单的 shell 命令示例,演示了命名管道的使用:

# 创建命名管道
mkfifo myfifo

# 在一个终端中写入数据
echo "Hello, FIFO!" > myfifo

# 在另一个终端中读取数据
cat myfifo

# 删除命名管道
rm myfifo

总的来说,管道是一种简单而有效的进程间通信方式,特别适用于相关进程之间的通信。

4. 管道的读写行为

关于管道不管是有名的还是匿名的,在进行读写的时候,它们表现出的行为是一致的,下面是对其读写行为的总结:

读管道,需要根据写端的状态进行分析:

  • 写端没有关闭 (操作管道写端的文件描述符没有被关闭)
    • 如果管道中没有数据 ==> 读阻塞, 如果管道中被写入了数据, 阻塞解除
    • 如果管道中有数据 ==> 不阻塞,管道中的数据被读完了, 再继续读管道还会阻塞
  • 写端已经关闭了 (没有可用的文件描述符可以写管道了)
    • 管道中没有数据 ==> 读端解除阻塞, read函数返回0
    • 管道中有数据 ==> read先将数据读出, 数据读完之后返回0, 不会阻塞了

写管道,需要根据读端的状态进行分析:

  • 读端没有关闭
    • 如果管道有存储的空间, 一直写数据
    • 如果管道写满了, 写操作就阻塞, 当读端将管道数据读走了, 解除阻塞继续写
  • 读端关闭了,管道破裂(异常), 进程直接退出
文章来源:https://blog.csdn.net/f593256/article/details/135459582
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。