首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >【嵌入式Linux应用开发基础】进程间通信(1):管道

【嵌入式Linux应用开发基础】进程间通信(1):管道

作者头像
byte轻骑兵
发布2026-01-21 15:32:38
发布2026-01-21 15:32:38
3160
举报

Linux进程间通信(Inter-Process Communication,IPC)是指不同进程间进行数据交换和信息传递的机制。它允许多个进程共享资源、同步执行、模块化设计及数据传递。常见的IPC方式包括管道、消息队列、共享内存、信号量、套接字等。这些方式各有特点,适用于不同的应用场景,是实现进程间高效协作的关键。本篇主要学习管道部分。

一、管道的基本概念

  • 管道是一种半双工的通信方式,数据只能单向流动,即数据从管道的一端写入,从另一端读出。通常把写入数据的一端称为写端(write end),读出数据的一端称为读端(read end)
  • 管道可以用于具有亲缘关系的进程之间的通信,比如父子进程,也可以在没有亲缘关系的进程间通过命名管道(FIFO)实现通信。

在 Linux 系统中,管道本质上是一种特殊的文件,它在内存中开辟了一段缓存空间来存储数据,遵循先进先出(FIFO)的原则,就像一个队列,先写入管道的数据会先被读出。

二、管道的工作原理

①创建管道:在Linux中,可以使用pipe()系统调用来创建一个管道。pipe()函数接受一个指向int类型数组的指针作为参数,该数组将包含两个文件描述符:一个用于读(fd[0]),另一个用于写(fd[1])

②数据传递

  • 当一个进程向管道的写端写入数据时,操作系统会将数据放入一个内核缓冲区中。
  • 另一个进程可以从管道的读端读取数据,操作系统会从内核缓冲区中取出数据并传递给该进程。

③同步与阻塞

  • 写端阻塞:如果管道的缓冲区已满,且写端进程继续尝试写入数据,则写操作将被阻塞,直到缓冲区中有足够的空间为止。
  • 读端阻塞:如果管道的缓冲区为空,且读端进程尝试读取数据,则读操作将被阻塞,直到缓冲区中有数据为止。
  • 可以通过关闭不再使用的文件描述符来解除阻塞状态。

④生命周期:管道的生命周期随进程而存在。当所有使用管道的文件描述符都被关闭时,管道将被销毁。

⑤管道关闭:当进程不再需要使用管道时,应该关闭相应的文件描述符。当所有指向管道的文件描述符都被关闭后,管道所占用的内核资源会被释放。

⑥使用示例:在命令行中,可以使用管道符号|将多个命令连接起来,使得一个命令的输出直接作为另一个命令的输入。例如,ls | grep "txt"命令会列出当前目录下的所有文件,并通过grep命令筛选出包含"txt"字符串的文件名。

三、管道的类型

3.1. 匿名管道(Anonymous Pipe)

  • 原理与特点:匿名管道只能用于具有亲缘关系的进程之间,比如父子进程。它在创建时没有名字,生命周期与创建它的进程相关联。当创建匿名管道的进程结束时,管道也会随之被销毁。
  • 创建与使用:在 C 语言中,可以使用pipe()系统调用创建匿名管道。pipe()函数的原型为:
代码语言:javascript
复制
int pipe(int pipefd[2]);
  • 函数会返回两个文件描述符,存储在pipefd数组中,pipefd[0]指向管道的读端,pipefd[1]指向管道的写端。
  • 如果创建成功,pipe()函数返回 0;
  • 若失败,则返回 - 1,并设置errno以指示错误原因。

下面是一个简单的父子进程通过匿名管道通信的示例代码:

代码语言:javascript
复制
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>

int main() {
    int pipe_fd[2];
    pid_t pid;
    char buffer[1024];

    // 创建匿名管道
    if (pipe(pipe_fd) == -1) {
        perror("pipe");
        return 1;
    }

    // 创建子进程
    pid = fork();
    if (pid == -1) {
        perror("fork");
        return 1;
    } else if (pid == 0) {
        // 子进程关闭读端
        close(pipe_fd[0]);
        // 向管道写端写入数据
        write(pipe_fd[1], "Hello, parent!", 14);
        // 关闭写端
        close(pipe_fd[1]);
    } else {
        // 父进程关闭写端
        close(pipe_fd[1]);
        // 从管道读端读取数据
        ssize_t bytes_read = read(pipe_fd[0], buffer, sizeof(buffer));
        if (bytes_read > 0) {
            buffer[bytes_read] = '\0';
            printf("Parent received: %s\n", buffer);
        }
        // 关闭读端
        close(pipe_fd[0]);
    }
    return 0;
}

首先通过pipe()创建匿名管道,然后使用fork()创建子进程。子进程关闭读端,向写端写入数据;父进程关闭写端,从读端读取数据。通过这种方式,实现了父子进程之间的通信。

3.2. 命名管道(Named Pipe,FIFO)

  • 原理与特点:命名管道也叫 FIFO(First - In - First - Out),它突破了匿名管道只能用于亲缘关系进程间通信的限制。FIFO 在文件系统中以特殊文件的形式存在,有自己的文件名,不同进程只要能访问该文件,就可以通过它进行通信。命名管道的生命周期独立于创建它的进程,直到被显式删除。
  • 创建与使用:在 C 语言中,可以使用mkfifo()函数创建命名管道。mkfifo()函数原型为:
代码语言:javascript
复制
int mkfifo(const char *pathname, mode_t mode);

其中pathname是命名管道的路径名,mode指定管道的权限。创建成功时返回 0,失败返回 - 1。

以下是一个使用命名管道进行通信的示例代码:

代码语言:javascript
复制
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <unistd.h>
#include <string.h>

#define FIFO_NAME "my_fifo"

int main() {
    int fd;
    char buffer[1024];

    // 创建命名管道
    if (mkfifo(FIFO_NAME, 0666) == -1 && errno!= EEXIST) {
        perror("mkfifo");
        return 1;
    }

    // 打开命名管道进行读操作
    fd = open(FIFO_NAME, O_RDONLY);
    if (fd == -1) {
        perror("open");
        return 1;
    }

    // 从命名管道读取数据
    ssize_t bytes_read = read(fd, buffer, sizeof(buffer));
    if (bytes_read > 0) {
        buffer[bytes_read] = '\0';
        printf("Received: %s\n", buffer);
    }

    // 关闭文件描述符
    close(fd);

    // 删除命名管道
    if (unlink(FIFO_NAME) == -1) {
        perror("unlink");
        return 1;
    }

    return 0;
}

先使用mkfifo()创建名为my_fifo的命名管道,然后使用open()打开管道进行读操作,从管道中读取数据并打印,最后使用unlink()删除命名管道。

四、管道的读写规则

4.1. 匿名管道的读写规则

①读规则

  • 管道无数据时:当管道中没有数据可读时,读操作会阻塞,即读进程会暂停执行,直到管道中有数据可供读取或者管道的写端被关闭。若所有写端都已关闭,读操作将返回 0,表示读到了文件末尾。
  • 管道有数据时:读操作会按照先进先出(FIFO)的顺序从管道中读取数据。若读请求的字节数小于等于管道中当前的数据量,读操作会成功读取请求的字节数。若读请求的字节数大于管道中当前的数据量,读操作会读取管道中现有的所有数据。

②写规则

  • 管道读端打开时:只要管道的读端处于打开状态,写操作就会尝试将数据写入管道。若管道的缓冲区有足够空间,写操作会立即完成,将数据写入管道缓冲区,并返回实际写入的字节数。若管道缓冲区已满,写操作会阻塞,直到管道中的数据被读走,腾出足够空间。
  • 管道读端关闭时:如果管道的所有读端都已关闭,此时再进行写操作,操作系统会向写进程发送 SIGPIPE 信号,通常情况下,进程会收到该信号而异常终止。

4.2. 命名管道的读写规则

①读规则

  • 管道无数据且写端未打开时:如果命名管道中没有数据,并且所有写端都没有打开,读操作的行为取决于打开管道的方式。若以阻塞方式打开(默认方式),读操作会阻塞,直到有写进程打开管道并写入数据或者有信号中断读操作。若以非阻塞方式打开(使用 O_NONBLOCK 标志),读操作不会阻塞,而是立即返回 - 1,同时将 errno 设置为 EAGAIN 或 EWOULDBLOCK,表示暂时没有数据可读。
  • 管道有数据时:与匿名管道类似,读操作会按照 FIFO 顺序从管道中读取数据,根据读请求的字节数和管道中数据量的情况,决定实际读取的字节数。

②写规则

  • 管道读端打开时:只要命名管道的读端有进程打开,写操作就会尝试将数据写入管道。若管道缓冲区有空间,写操作会立即完成并返回实际写入的字节数;若缓冲区已满,写操作的阻塞行为与匿名管道类似,取决于管道是否以非阻塞方式打开。
  • 管道读端未打开时:如果命名管道的读端没有任何进程打开,以阻塞方式打开管道的写操作会阻塞,直到有读进程打开管道。以非阻塞方式打开管道的写操作则会立即返回 - 1,同时将 errno 设置为 ENXIO,表示没有连接的设备。

、管道的优缺点

5.1. 优点

  • 简单易用:管道的使用相对简单,创建和操作管道的系统调用较为直观,不需要复杂的配置和初始化过程。在 C 语言中,使用pipe()函数就可以轻松创建一个匿名管道,进程可以方便地通过管道进行数据传输,对于简单的进程间通信场景,能够快速实现数据的传递和共享。
  • 数据传输高效:管道在内核中实现了数据的缓冲机制,数据可以在管道的缓冲区中暂存,使得数据的发送和接收更加平滑。数据从写端写入后,读端可以及时读取,不需要额外的同步机制来保证数据的完整性和顺序性,在进程间传输大量数据时,能够提供较高的传输效率。
  • 自带同步与互斥机制:管道本身具有一定的同步和互斥特性。当管道缓冲区满时,写操作会阻塞,直到读端读取数据腾出空间;当管道缓冲区为空时,读操作会阻塞,直到写端写入数据。这种机制保证了数据的正确读写顺序,避免了数据竞争和冲突,使得进程间的数据交互更加稳定和可靠。
  • 父子进程通信方便:在创建子进程时,父进程可以通过管道将数据传递给子进程,或者子进程将处理结果通过管道返回给父进程。这种方式为父子进程之间的通信提供了一种便捷的途径,例如在 Shell 脚本中,经常会利用管道来实现父子进程之间的命令执行和结果传递。

5.2. 缺点

  • 半双工通信限制:管道通常是半双工的,即数据只能在一个方向上流动。如果需要实现双向通信,就需要创建两个管道,这会增加程序的复杂性和资源开销。
  • 只能用于亲缘关系进程:匿名管道只能用于具有亲缘关系的进程之间,如父子进程,这限制了它的应用范围。虽然命名管道可以用于无亲缘关系的进程,但它需要在文件系统中创建一个特殊的文件,会带来一定的复杂性和安全性问题。
  • 数据缓存大小有限:管道的缓冲区大小是有限的,不同的系统对管道缓冲区大小的默认值有所不同。如果进程写入管道的数据量超过了缓冲区的容量,写操作就会阻塞,直到读端及时读取数据腾出空间。这可能会导致写进程长时间等待,影响程序的性能和响应速度。
  • 没有数据边界:管道中的数据是无边界的字节流,读端无法区分数据的边界和不同的消息。这就要求应用程序在设计时,需要自己定义数据的格式和边界标识,以便正确地解析和处理数据,增加了应用程序开发的难度和复杂性。

综上所述,在嵌入式Linux应用开发中,管道作为进程间通信的基础方式意义重大。它分为匿名管道和命名管道,前者用于亲缘进程,创建简单,通过`pipe()`函数实现;后者突破进程关系限制,在文件系统以特殊文件存在,用`mkfifo()`创建。管道读写遵循特定规则,虽有半双工、缓存有限等不足,但胜在简单高效,是实现进程间数据传递的重要工具。

六、常见问题

6.1. 什么是管道(Pipe)?匿名管道和命名管道(FIFO)的区别是什么?

  • 管道是Linux中一种半双工的进程间通信(IPC)方式,数据单向流动。
  • 匿名管道(Anonymous Pipe)
    • 仅用于有亲缘关系的进程(如父子进程)。
    • 通过pipe()系统调用创建,返回两个文件描述符:fd[0](读端)和fd[1](写端)。
    • 生命周期随进程结束。
  • 命名管道(Named Pipe/FIFO)
    • 通过mkfifo命令或mkfifo()函数创建,存在于文件系统中(如/tmp/myfifo)。
    • 允许无亲缘关系的进程通信。
    • 需显式删除(unlink())或随系统重启消失。
代码语言:javascript
复制
// 匿名管道示例
int fd[2];
pipe(fd);  // 创建管道
if (fork() == 0) { 
    close(fd[0]);  // 子进程关闭读端
    write(fd[1], "Hello", 6);
} else {
    close(fd[1]);  // 父进程关闭写端
    char buf[6];
    read(fd[0], buf, 6);
}

6.2. 为什么写管道时进程会被阻塞?如何避免?

阻塞场景

  • 当管道缓冲区已满时,写操作(write())会阻塞。
  • 读端未打开时,写操作会触发SIGPIPE信号(默认终止进程)。

解决方案

使用fcntl()设置非阻塞模式:

代码语言:javascript
复制
fcntl(fd, F_SETFL, O_NONBLOCK);

多路复用(如select()/poll())监控管道状态。

确保读端及时读取数据。

6.3. 如何确定管道缓冲区大小?单次写入数据超过缓冲区会怎样?

  • 通过fcntl(fd, F_GETPIPE_SZ)获取缓冲区大小(默认通常为64KB)。
  • 若写入数据超过缓冲区:
    • 阻塞模式下,write()会等待直到有空间。
    • 非阻塞模式下,write()返回EAGAIN错误。
  • 关键规则:单次write()数据量 ≤ PIPE_BUF(通常4KB)时,操作是原子性的;否则数据可能被分割。

6.4. 命名管道(FIFO)的读写端打开顺序问题

  • 问题:若读端未打开时写端尝试写入,会发生什么?
    • 默认情况下,写端open()阻塞,直到读端打开。
    • 可通过O_NONBLOCK标志避免阻塞:
代码语言:javascript
复制
int fd = open("/tmp/myfifo", O_WRONLY | O_NONBLOCK);
  • 若读端全部关闭,写操作会触发SIGPIPE信号。

6.5. 如何处理管道破裂(Broken Pipe)错误?

场景:读端已关闭,写端继续写入。

write()会返回EPIPE错误,并触发SIGPIPE信号(默认终止进程)。

解决方案

忽略SIGPIPE信号:

代码语言:javascript
复制
signal(SIGPIPE, SIG_IGN);

检查write()返回值,处理EPIPE错误:

代码语言:javascript
复制
if (write(fd, buf, len) == -1) {
    if (errno == EPIPE) {
        // 处理管道破裂
    }
}

6.6. 多进程/线程同时读写管道的同步问题

  • 匿名管道:同一时刻只能有一个读端和一个写端,多进程需协调。
  • 命名管道:多个写进程需保证数据原子性(单次写入 ≤ PIPE_BUF)。
  • 建议
    • 使用PIPE_BUF大小限制保证原子性。
    • 通过外部锁(如文件锁)或信号量同步。

6.7. 管道与Shell命令的结合使用

  • 示例:在Shell中使用管道连接命令:
代码语言:javascript
复制
mkfifo /tmp/myfifo
cat /tmp/myfifo &  # 后台启动读端
echo "Hello" > /tmp/myfifo
  • 注意:Shell重定向会隐式处理打开顺序和阻塞问题。

6.8. 如何选择管道与其他IPC方式(如消息队列、共享内存)?

  • 管道适用场景
    • 简单单向数据流。
    • 父子进程或少量数据通信。
  • 不适用场景
    • 高频大数据量(优先考虑共享内存)。
    • 复杂结构化数据(考虑消息队列或Socket)。

七、参考资料

  • 《Unix 环境高级编程(第 3 版)》
    • 作者:W. Richard Stevens、Stephen A. Rago
    • 简介:这是 Unix 和类 Unix 系统编程领域的经典著作,详细讲解了 Unix 系统的各种特性,其中对管道的创建、使用、读写规则等有深入且全面的阐述。
  • 《Linux 系统编程》
    • 作者:Robert Love
    • 简介:专注于 Linux 系统下的编程技术,对 Linux 进程间通信机制进行了细致介绍,涉及管道的底层原理、与其他 IPC 机制的对比等内容。
  • Linux 手册页
    • 获取方式:在 Linux 系统终端使用man命令,如man pipeman mkfifo查看相关内容;也可访问man7.org在线查看。
    • 简介:这是最权威的 Linux 系统调用参考资料,关于管道相关系统调用的手册页详细说明了函数原型、参数、返回值和使用示例,是学习管道编程的重要依据。
  • GNU C Library 文档
    • 获取方式:访问GNU 官方网站
    • 简介:GNU C Library 是 Linux 系统广泛使用的 C 标准库,其文档对与管道操作相关的库函数进行了详细描述。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2026-01-20,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、管道的基本概念
  • 二、管道的工作原理
  • 三、管道的类型
    • 3.1. 匿名管道(Anonymous Pipe)
    • 3.2. 命名管道(Named Pipe,FIFO)
  • 四、管道的读写规则
    • 4.1. 匿名管道的读写规则
    • 4.2. 命名管道的读写规则
  • 五、管道的优缺点
    • 5.1. 优点
    • 5.2. 缺点
  • 六、常见问题
    • 6.1. 什么是管道(Pipe)?匿名管道和命名管道(FIFO)的区别是什么?
    • 6.2. 为什么写管道时进程会被阻塞?如何避免?
    • 6.3. 如何确定管道缓冲区大小?单次写入数据超过缓冲区会怎样?
    • 6.4. 命名管道(FIFO)的读写端打开顺序问题
    • 6.5. 如何处理管道破裂(Broken Pipe)错误?
    • 6.6. 多进程/线程同时读写管道的同步问题
    • 6.7. 管道与Shell命令的结合使用
    • 6.8. 如何选择管道与其他IPC方式(如消息队列、共享内存)?
  • 七、参考资料
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档