当前位置: 首页 > news >正文

Linux- pipe()系统调用

管道

管道(Pipe)是一种用于进程间通信(IPC)的简单而有效的方式。在UNIX和类UNIX操作系统(如Linux)中,管道提供了一种让一个进程将其输出发送给另一个进程的输入的机制。管道通常用于数据流的单向传输。

在底层,管道其实是一个由操作系统内核维护的缓冲区。一个进程向管道的一端(写端)写入数据,而另一个进程可以从管道的另一端(读端)读取数据。

管道实现的基本思想

  1. 缓冲区管理:内核维护一个缓冲区,这个缓冲区用于存储写入管道的数据。
  2. 同步与互斥:内核还提供了同步和互斥机制,以保证数据能够安全地从一个进程传输到另一个进程。
  3. 文件描述符:从用户态的角度来看,管道其实就是一对文件描述符,其中一个用于读,另一个用于写。

pipe()

在UNIX-like操作系统中,pipe()系统调用用于创建一个新的管道,这是一种允许两个进程进行单向数据传输的IPC(进程间通信)机制。一个进程写入管道的一端,而另一个进程从管道的另一端读取。

函数原型

管道是通过以下函数原型创建的:

#include <unistd.h>
int pipe(int pipefd[2]);

参数

  • pipefd: 这是一个包含两个int型元素的数组。函数调用成功时,pipefd[0]将成为管道的读取端(read end)的文件描述符,而pipefd[1]将成为管道的写入端(write end)的文件描述符。

返回值

  • 成功:返回0。
  • 失败:返回-1,并设置errno

工作原理

  1. 数据流向:数据从pipefd[1](写端)流向pipefd[0](读端)。

  2. 阻塞和非阻塞pipe()通常是阻塞的。也就是说,读操作会阻塞,直到有数据写入;写操作也会阻塞,直到读端读取了数据。

  3. 数据缓冲:数据首先被写入内核缓冲区,然后由读操作从缓冲区中读取。

  4. 文件描述符的继承pipe()创建的文件描述符可以在fork()之后由子进程继承,这使得pipe()非常适用于父子进程或兄弟进程之间的通信。

  5. 关闭规则:当写端被关闭后,任何尝试从读端读取的操作将立即返回,读取到的数据长度为0(表示EOF)。当读端被关闭后,任何尝试写入写端的操作都将导致发送SIGPIPE信号。

应用场景

管道经常用在多进程应用中,例如shell命令中的管道操作符|,它允许一个命令的输出可以作为另一个命令的输入。

综上所述,pipe()是一种在两个进程之间简单有效地传输数据的低级IPC机制。然而,它有局限性,如仅能进行单向通信,且通常用于有共同祖先的进程之间。

示例

下面的C程序示例演示了如何使用pipe()fork()read()/write()函数来实现简单的进程间通信。这个例子中,父进程向管道中写入字符串,子进程从管道中读取并打印这个字符串。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>int main() {int pipefd[2];pid_t pid;// 创建管道if (pipe(pipefd) == -1) {perror("pipe");exit(EXIT_FAILURE);}// 创建子进程pid = fork();if (pid == -1) {perror("fork");exit(EXIT_FAILURE);}if (pid == 0) {  // 子进程char buffer[128];close(pipefd[1]);  // 关闭不需要的写端// 从管道读取数据read(pipefd[0], buffer, sizeof(buffer));printf("Child read: %s\n", buffer);close(pipefd[0]);  // 关闭读端exit(EXIT_SUCCESS);} else {  // 父进程const char *msg = "Hello, Pipe!";close(pipefd[0]);  // 关闭不需要的读端// 写入数据到管道write(pipefd[1], msg, strlen(msg) + 1);printf("Parent wrote: %s\n", msg);close(pipefd[1]);  // 关闭写端// 等待子进程结束wait(NULL);}return 0;
}

代码解析

  1. 创建管道:使用pipe(pipefd)创建一个新的管道。成功后,pipefd[0]是读端,pipefd[1]是写端。

  2. 创建子进程:使用fork()创建一个新的进程。

  3. 父进程

    • 关闭读端,因为父进程只需要写。
    • 使用write()将字符串写入管道。
    • 关闭写端。
  4. 子进程

    • 关闭写端,因为子进程只需要读。
    • 使用read()从管道中读取数据。
    • 打印读取的数据。
    • 关闭读端。
  5. 父进程等待:父进程使用wait(NULL)等待子进程结束。

这就是一个简单的使用pipe()进行进程间通信的例子。运行这个程序,应该会看到类似下面的输出:

Parent wrote: Hello, Pipe!
Child read: Hello, Pipe!

这表明父进程写入的数据成功地被子进程读出。


】当一个进程(父进程)调用fork()创建子进程时,子进程会继承父进程的文件描述符表。这意味着pipefd[0]pipefd[1]在子进程中的值将与父进程中的值相同,它们指向同一个管道。

具体地说,父进程和子进程将拥有指向同一个内核管道对象的文件描述符。这使得父子进程可以通过这个管道进行通信。

因为子进程继承了父进程的文件描述符,所以:

  • 在子进程中,pipefd[0]仍然是管道的读端。
  • 在子进程中,pipefd[1]仍然是管道的写端。

这就是为什么在创建管道和fork()之后,通常会看到一些close()调用:每个进程通常只需要管道的一端,所以会关闭不需要的那一端。这样做有助于避免潜在的死锁和资源泄漏。

例如,在上面的示例代码中:

  • 子进程关闭了写端(close(pipefd[1]);),因为它只从管道中读取数据。
  • 父进程关闭了读端(close(pipefd[0]);),因为它只向管道中写入数据。

这种做法使得管道更容易管理,并且可以确保当所有的写端或读端被关闭时,相关的操作(如read()write())能够正确地返回。

http://www.lryc.cn/news/174807.html

相关文章:

  • 数据库常用指令
  • [Studio]Manifest merger failed with multiple errors, see logs 解决方法
  • 【数据结构与算法】不就是数据结构
  • 封装一个高级查询组件
  • 代码随想录第七章 栈与队列
  • SQL Server对象类型(5)——4.5. 同义词(Synonym)
  • IP风险查询:抵御DDoS攻击和CC攻击的关键一步
  • Tune-A-Video论文阅读
  • Dataset和DataLoader用法
  • 【跟小嘉学习区块链】二、Hyperledger Fabric 架构详解
  • springboot下spring方式实现Websocket并设置session时间
  • LeetCode算法二叉树—相同的树
  • 搭建Flink集群、集群HA高可用以及配置历史服务器
  • vscode终端中打不开conda虚拟包管理
  • 【音视频】MP4封装格式
  • 环境-使用vagrant快速创建linux虚拟机
  • 10.1网站编写(Tomcat和servlet基础)
  • 10CQRS
  • DAZ To UMA⭐一.DAZ简单使用教程
  • 面试题 —— Java集合篇(23题)
  • SpringBoot2.7.14整合Swagger3.0的详细步骤及容易踩坑的地方
  • 题解:ABC321D - Set Menu
  • 什么是Progressive Web App(PWA)?它们有哪些特点?
  • MySQL的高级SQL语句
  • 基于人脸5个关键点的人脸对齐(人脸纠正)
  • vue3中两个el-select下拉框选项相互影响
  • 博弈论——反应函数
  • UE5读取json文件
  • Vue中的插槽--组件复用,内容自定义
  • 完全指南:mv命令用法、示例和注意事项 | Linux文件移动与重命名