《Linux系统编程》Linux 命名管道 FIFO 详解:突破亲缘限制的跨进程通信实现

🔥小叶-duck个人主页

❄️个人专栏《Data-Structure-Learning》《C++入门到进阶&自我学习过程记录》

《Linux操作系统从入门到实践》《Qt从入门到实践》

《算法题讲解指南》--优选算法

《算法题讲解指南》--递归、搜索与回溯算法

《算法题讲解指南》--动态规划算法

未择之路,不须回头
已择之路,纵是荆棘遍野,亦作花海遨游


目录

前言:

上篇补充(进程池的实现)

[一. 命名管道核心概念:什么是 FIFO?](#一. 命名管道核心概念:什么是 FIFO?)

[1.1 命名管道的定义](#1.1 命名管道的定义)

[1.2 命名管道的核心特性](#1.2 命名管道的核心特性)

[1.3 命名管道和匿名管道的区别与联系](#1.3 命名管道和匿名管道的区别与联系)

[二. 命名管道的创建方式](#二. 命名管道的创建方式)

[2.1 命令行创建(mkfifo 指令)](#2.1 命令行创建(mkfifo 指令))

[2.2 代码创建(mkfifo 函数)](#2.2 代码创建(mkfifo 函数))

[三. 命名管道的打开规则(重点)](#三. 命名管道的打开规则(重点))

[四. 命名管道实战案例](#四. 命名管道实战案例)

[4.1 案例 1:命名管道实现 Server-Client 通信](#4.1 案例 1:命名管道实现 Server-Client 通信)

前置准备(Makefile)

[4.1.1 版本一](#4.1.1 版本一)

[4.1.2 版本二(优化)](#4.1.2 版本二(优化))

comm.h:

服务端程序(server.cc):

客户端程序(client.cc):

[4.2 案例 2:命名管道实现文件拷贝](#4.2 案例 2:命名管道实现文件拷贝)

写端程序(file_writer.c):

读端程序(file_reader.c):

编译与运行:

[五. 命名管道使用总结](#五. 命名管道使用总结)

结束语


前言:

在 Linux 各类 IPC 通信方案里,匿名管道虽能完成父子、兄弟这类同源进程的数据交互,却受血缘绑定约束,无法用于独立无关进程通信。命名管道 FIFO 作为其衍生拓展,依托文件系统生成实体管道文件,凭借唯一文件名作为通信入口,摆脱了亲缘进程的使用局限,让任意进程都能依托文件路径完成数据收发。本文围绕 FIFO 的定义、创建接口、底层通信逻辑搭配实操代码,完整梳理非亲缘进程借助命名管道实现 IPC 的整套逻辑。

上篇补充(进程池的实现)

在上篇文章的最后我们利用匿名管道实现了一个简单的进程池,虽然执行过程没有任何问题,但是在代码上其实有一个比较隐蔽的问题:

也就是退出进程池的时候,我们之前的做法是先将全部管道的写端全部关闭之后,再等待全部子进程。

学习了前面知识的应该就会发现一个问题:最开始的管道先关闭就会导致对应的子进程Work函数结束返回,进而导致子进程结束,但是我们没有立即对其进行等待(因为还要关闭后续管道),使其变成僵尸进程,这其实是不应该的。

所以我们需要对这里进行优化。

那这里就会有人说了,我们直接关闭一个管道就等待,再关闭一个管道再等待不就行了,那我们就按照这种想法修改一下:

我们发现进程卡住了,这是什么情况呢?为什么我们修改成关闭管道就执行等待,就变成了这种情况?下面的图进行了解释:

那我们怎么解决这个问题呢?这里提供了两种方法:

解决方法一:逆序关闭管道

其实通过上面的图我们就会发现一个特点:就是越早创建的管道被指向的次数越多,而越晚创建的管道被指向的次数越少(因为越后面的子进程所拷贝的写端越多),而最后一个管道只有父进程一个写端指向,那么我们反向往上进行关闭本就行了吗?

因为我们关闭了最后一个管道的写端则管道就关闭了,则对应的子进程就会退出,那么上一个管道原本是被父进程和这个子进程同时指向,此时就只有父进程了,再进行关闭写端则管道也就关闭了,以此类推。

解决方法二:让子进程指向管道的写端提前关闭(只让父进程指向管道写端)

我们会发现上面的解决方法的确解决了阻塞问题,但是我们会发现其实底层进程的指向依旧没变,依旧是最早创建的管道被多次指向,这其实不符合进程池的要求,我们是希望一个管道就只有父进程和对应的子进程进行连通,而没有其他进程的干预,所以我们需要解决这个问题。

那怎么解决呢?其实我们会发现:我们对管道写端的关闭,无非就是让其引用计数减到0,而而我们就是需要想办法在不断创建子进程的过程中,所有管道的写端的引用计数始终为1,也就是只让父进程指向,那怎么做呢?

父进程for循环关闭自己的对应管道的写端,是让所有管道写端的引用计数减1,那子进程也for循环关闭管道的写端,不也能让管道写端的引用计数减1吗?所以我们的解决方法就是在子进程中for循环关闭管道的写端(使其引用计数减1,保证所有的管道写端引用计数始终为1)

这样我们所有的管道的写端也就只有父进程指向了,那么我们也就可以顺序关闭管道了。

到此,我们也就成功解决了这个问题了。

一. 命名管道核心概念:什么是 FIFO?

1.1 命名管道的定义

命名管道 (Named Pipe),又称 FIFO (First In First Out),是一种特殊的文件系统对 象(类型为 p),其核心本质与匿名管道一致 ------ 内核中的一块缓冲区 ,但通过文件路径作为标识,让任意进程都能通过该路径访问管道,实现跨进程通信。

与匿名管道相比,命名管道的核心差异的是 "命名":

  • 匿名管道:无文件路径,仅通过 pipe() 创建的文件描述符在亲缘进程间共享;
  • 命名管道:有明确的文件路径(如/tmp/myfifo),任意进程可通过open()打开该路径,实现通信。

1.2 命名管道的核心特性

  • 跨进程通信:支持无亲缘关系的进程(如两个独立的应用程序)通信,突破匿名管道的亲缘限制;
  • 半双工通信:数据单向流动,如需双向通信需创建两个命名管道;
  • 基于文件操作 :遵循 Linux "一切皆文件" 思想,通过 open()/read()/write()/close() 等标准文件接口操作;
  • 生命周期随内核 :命名管道创建后,即使创建进程退出,管道文件仍存在于文件系统中,需手动删除(unlink()或rm命令);
  • 同步与互斥 :内核自动保证管道操作的同步(如读阻塞、写阻塞 )和互斥(同一时间仅允许一个进程写)。

1.3 命名管道和匿名管道的区别与联系

  • 匿名管道pipe函数创建并打开。
  • 命名管道mkfifo 函数创建,打开用 open
  • FIFO(命名管道)与pipe(匿名管道)之间唯一的区别在它们创建与打开的方式不同,⼀但这些工作完成之后,它们具有相同的语义。
  • 为了更清晰地理解命名管道的定位,整理两者的核心差异更详细的对比如下表所示
特性 匿名管道 命名管道 (FIFO)
定义 一种半双工通信通道,通常用于具有亲缘关系的进程之间(如父子进程)。 一种特殊的文件(存在于文件系统中),允许无亲缘关系的进程间通信。
创建方式 通过 pipe() 系统调用创建,返回两个文件描述符(读端和写端)。 通过 mkfifo()mknod() 创建,在文件系统中具有路径名。
标识 无名,仅通过文件描述符引用。 有名,通过文件系统中的路径名标识。
进程关系 仅适用于有共同祖先(如父子进程)的进程。 适用于任意进程,无论是否有亲缘关系。
通信方向 单向(半双工),数据只能从一个方向流动。 通常也是单向(半双工),但可通过打开两个管道实现双向通信。
持久性 随进程存在而存在,所有相关进程关闭管道后自动销毁。 随文件系统存在,可显式删除(unlink),即使没有进程打开也不会消失。
打开方式 无需显式打开,创建时直接获得文件描述符。 必须像普通文件一样用 open() 打开,使用路径名。
数据模型 字节流,无消息边界。 字节流,无消息边界。
阻塞行为 默认阻塞:读空管道或写满管道会使进程阻塞(可设置非阻塞)。 默认阻塞:读空 FIFO 或写满 FIFO 会使进程阻塞(可设置非阻塞)。
典型应用场景 父子进程间的简单通信,如命令管道 在 shell 中的使用
系统限制 容量通常有限(如 64KB),取决于系统实现。 容量通常有限,类似匿名管道,但受文件系统影响。
对比维度 匿名管道(pipe) 命名管道(FIFO)
通信范围 仅亲缘进程(父子、兄弟) 任意进程(无亲缘关系)
创建方式 pipe()系统调用 mkfifo()函数或 mkfifo 命令
标识方式 文件描述符(fd 0、fd 1 文件系统路径(如./myfifo)
生命周期 随进程(所有进程关闭描述符后释放) 随内核(需手动unlink()删除)
打开方式 无需open(),创建即打开 需通过open()打开路径
核心用途 亲缘进程间简单通信 无关联进程间通信(如 C/S 模型)

联系:

  1. 通信机制 :两者都是操作系统提供的进程间通信(IPC)方式,基于内核缓冲区实现数据传输。
  2. 数据特性:都提供可靠的字节流服务,数据写入和读取的顺序一致,无消息边界。
  3. 行为相似 :默认情况下,读写操作具有相似的阻塞语义(读空阻塞、写满阻塞),并支持非阻塞标志。
  4. 单向性:本质上都是单向通信管道(半双工),若要双向通信需创建两个管道。
  5. 原子性 :在 PIPE_BUF限制内,写入操作具有原子性(多个进程同时写时数据不会交错)。

二. 命名管道的创建方式

命名管道有两种创建方式:命令行创建代码创建 ,本质都是在文件系统 中生成一个FIFO 类型的文件

2.1 命令行创建(mkfifo 指令)

直接通过 mkfifo 指令 创建命名管道,语法简单,适合快速测试:

bash 复制代码
# 创建名为myfifo的命名管道
mkfifo myfifo

# 查看管道文件(类型标识为p)
ls -l myfifo

我们可以发现,这里创建的管道文件(最前面的标识是p开头),就算我们是不停的在往里面进行写操作,但是它的文件大小是一直不变的,验证了我们上面图中所说的它不是一个普通文件。另外当我们直接终止读端的时候,这个时候的情况就相当于读端关闭,写端还在一起写,所以操作系统直接杀死了我们的进程。

2.2 代码创建(mkfifo 函数)

通过mkfifo() 系统调用 在代码中创建命名管道,需指定管道路径和权限,原型如下:

cpp 复制代码
#include <sys/stat.h>
#include <sys/types.h>

// 参数:pathname-管道路径;mode-权限(如0644)
int mkfifo(const char *pathname, mode_t mode);

代码示例(创建命名管道):

cpp 复制代码
#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <errno.h>

#define FIFO_PATH "./myfifo" //路径

int main() 
{
    // 创建命名管道,权限0644(所有者读+写,其他读)
    int ret = mkfifo(FIFO_PATH, 0644);
    if (ret == -1) 
    {
        // 若管道已存在,errno为EEXIST,可忽略该错误
        if (errno != EEXIST) 
        {
            perror("mkfifo error");
            return 1;
        }
        printf("命名管道已存在\n");
    } 
    else 
    {
        printf("命名管道创建成功\n");
    }
    return 0;
}

三. 命名管道的打开规则(重点)

命名管道的打开(open())行为与普通文件不同,普通文读端或者写端随时都可以打开文件 ;而命名管道的核心是**"读端与写端的同步"** ------仅当管道的读端和写端同时被打开后,通信才能正常进行,具体规则如下:

打开方式 行为描述
读方式打开(O_RDONLY) - 若管道无写端打开:阻塞,直到有进程以写方式打开该管道;- 若指定 O_NONBLOCK:不阻塞,直接返回成功
写方式打开(O_WRONLY) - 若管道无读端打开:阻塞,直到有进程以读方式打开该管道;- 若指定 O_NONBLOCK:不阻塞,返回失败(errno=ENXIO)
读写方式打开(O_RDWR) 不阻塞,直接打开(同时具备读和写权限,可实现单向通信的 "自我循环")

注意 :实际开发中,建议读端以O_RDONLY 打开,写端以O_WRONLY打开,避免使用O_RDWR(可能导致通信逻辑混乱)。

四. 命名管道实战案例

4.1 案例 1:命名管道实现 Server-Client 通信

实现一个简单的 C/S 模型:

  • server.cc 作为服务端监听管道,接收客户端消息并打印;
  • client.cc 作为客户端 ,向管道发送消息,实现双向交互。

前置准备(Makefile)

  • makefile
bash 复制代码
.PHONY:all
all:client server
client:client.cc
	g++ -o $@ $^ -std=c++11
server:server.cc
	g++ -o $@ $^ -std=c++11

.PHONY:clean
clean:
	rm -f client server

4.1.1 版本一

服务端程序(server.cc):

cpp 复制代码
#include <iostream>
#include "comm.hpp"
#include <string>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>

// server负责创建命名管道和读取数据
int main()
{
    umask(0); // 默认umask为0002,修改umask为0,保证权限设置一一对应
    // 1、创建命名管道fifo
    // int mkfifo(const char *pathname, mode_t mode);
    int n = mkfifo(FIFO_FILE, 0666); // 第二个参数就是传权限位mode
    if (n < 0)
    {
        std::cerr << "mkfifo error" << std::endl;
        return 1;
    }
    std::cout << "mkfifo success" << std::endl;

    //管道不同于普通文件的一个特性:
    //打开文件,write方没有执行open的时候,如果先调度read方,则read方就会在open内部进行阻塞
    //直到有write方把管道文件打开,read方的open才会返回!
    //(判断read方还是write方:由open的第二个参数flags决定)
    //而普通文件只要文件被创建了,如何一方都可以随时打开

    // 2. 打开管道文件(阻塞等待客户端连接)
    // 打开命名管道
    int fd = open(FIFO_FILE, O_RDONLY);
    if (fd < 0)
    {
        std::cout << "server open fifo failed" << std::endl;
    }
    else
    {
        std::cout << "server open fifo success" << std::endl;
    }

    // 读取管道数据read
    while (true)
    {
        char buffer[1024];
        int number = read(fd, buffer, sizeof(buffer) - 1);
        if(number > 0)
        {
            buffer[number] = 0; //结尾带上\0形成字符串
            std::cout << "Client say# " << buffer << std::endl;
        }
        else if(number == 0)
        {
            //写端退出->读端read返回0,读到文件末尾,也需要进行退出
            std::cout << "Client quit! me too!" << std::endl;
            break; 
        }
        else
        {
            std::cerr << "read error" << std::endl;
            break;
        }
    }

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

    // 系统调用:删除管道unlink
    // int unlink(const char *pathname);
    int dele = unlink(FIFO_FILE);
    if (dele == 0)
    {
        std::cout << "remove mkfifo success" << std::endl;
    }
    else
    {
        std::cout << "remove mkfifo failed" << std::endl;
    }
    return 0;
}

客户端程序(client.cc):

cpp 复制代码
#include <iostream>
#include "comm.hpp"
#include <string>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>

// client负责写入数据
int main()
{
    // 1. 以写方式打开管道(阻塞,直到服务端打开读端)
    // 打开命名管道fifo
    int fd = open(FIFO_FILE, O_WRONLY);
    if (fd < 0)
    {
        std::cout << "client open fifo failed" << std::endl;
    }
    std::cout << "client open fifo success" << std::endl;

    // 2. 循环输入并发送消息给服务端
    //获取信息
    pid_t id = getpid(); //获取当前进程pid
    int cnt = 1;
    // 写入数据
    while (true)
    {
        std::cout << "Please enter# ";
        std::string message;
        getline(std::cin, message); //按行读取字符串
        message += (", message number: " + std::to_string(cnt++) + " [" + std::to_string(id) + "]");
        //写入message到管道fifo
        write(fd, message.c_str(), message.size());
    }

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

comm.h:

cpp 复制代码
#pragma once

#define FIFO_FILE "fifo"

编译与运行:

必须先打开服务端再打开客服端

原因是我们是在服务端创建的管道,只有管道存在的情况下,一端打开管道才会进行阻塞状态,等待另一端打开管道;而如果本身管道就不存在,直接open打开则会返回-1打开失败。

4.1.2 版本二(优化)

comm.h:
cpp 复制代码
#pragma once
#include <iostream>
#include <string>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <cstdio>

// #define FIFO_FILE "fifo"
// 错误处理宏
#define ERR_EXIT(m)         \
    do                      \
    {                       \
        perror(m);          \
        exit(EXIT_FAILURE); \
    } while (0) // \->续行符,实际为一行代码

// 管道的创建和删除
class NameFifo
{
public:
    NameFifo(const std::string path, const std::string name)
        : _path(path), _name(name)
    {
        // 创建管道
        _fifoname = path + "/" + name;
        umask(0); // 默认umask为0002,修改umask为0,保证权限设置一一对应
        // 1、创建命名管道fifo
        // int mkfifo(const char *pathname, mode_t mode);
        int n = mkfifo(_fifoname.c_str(), 0666); // 第二个参数就是传权限位mode
        if (n < 0)
        {
            ERR_EXIT("mkfifo");
        }
        else
        {
            std::cout << "mkfifo success" << std::endl;
        }
    }

    ~NameFifo()
    {
        // 删除管道
        //  系统调用:删除管道unlink
        // int unlink(const char *pathname);
        int dele = unlink(_fifoname.c_str());
        if (dele == 0)
        {
            std::cout << "remove mkfifo success" << std::endl;
        }
        else
        {
            std::cout << "remove mkfifo failed" << std::endl;
        }
    }

private:
    std::string _path;
    std::string _name;
    std::string _fifoname;
};

// 读写文件的操作
class FileOper
{
public:
    FileOper(const std::string path, const std::string name)
        : _path(path), _name(name), _fd(-1)
    {
        _fifoname = path + "/" + name;
    }

    void OpenforRead()
    {
        // 管道不同于普通文件的一个特性:
        // 打开文件,write方没有执行open的时候,如果先调度read方,则read方就会在open内部进行阻塞
        // 直到有write方把管道文件打开,read方的open才会返回!
        //(判断read方还是write方:由open的第二个参数flags决定)
        // 而普通文件只要文件被创建了,如何一方都可以随时打开

        // 2. 以读的方式打开管道文件(阻塞等待客户端连接)
        // 打开命名管道
        _fd = open(_fifoname.c_str(), O_RDONLY);
        if (_fd < 0)
        {
            ERR_EXIT("open_read");
        }
        else
        {
            std::cout << "server open fifo success" << std::endl;
        }
    }

    void OpenforWrite()
    {
        // 1. 以写方式打开管道(阻塞,直到服务端打开读端)
        // 打开命名管道fifo
        _fd = open(_fifoname.c_str(), O_WRONLY);
        if (_fd < 0)
        {
            ERR_EXIT("open_write");
        }
        else
        {
            std::cout << "client open fifo success" << std::endl;
        }
    }

    void Read()
    {
        // 读取管道数据read
        while (true)
        {
            char buffer[1024];
            int number = read(_fd, buffer, sizeof(buffer) - 1);
            if (number > 0)
            {
                buffer[number] = 0; // 结尾带上\0形成字符串
                std::cout << "Client say# " << buffer << std::endl;
            }
            else if (number == 0)
            {
                // 写端退出->读端read返回0,读到文件末尾,也需要进行退出
                std::cout << "Client quit! me too!" << std::endl;
                break;
            }
            else
            {
                ERR_EXIT("read");
            }
        }
    }

    void Write()
    {
        // 2. 循环输入并发送消息给服务端
        // 获取信息
        pid_t id = getpid(); // 获取当前进程pid
        int cnt = 1;

        // 向管道写入数据
        while (true)
        {
            std::cout << "Please enter# ";
            std::string message;
            getline(std::cin, message); // 按行读取字符串
            message += (", message number: " + std::to_string(cnt++) + " [" + std::to_string(id) + "]");
            // 写入message到管道fifo
            write(_fd, message.c_str(), message.size());
        }
    }

    // 关闭文件描述符
    void Close()
    {
        if (_fd > 0)
        {
            close(_fd);
        }
    }

private:
    std::string _path;
    std::string _name;
    std::string _fifoname;
    int _fd;
};
服务端程序(server.cc):
cpp 复制代码
#include "comm.hpp"

// server负责创建命名管道和读取数据
int main()
{
    //创建管道
    NameFifo fifo(".", "fifo");

    //以读的方式打开管道
    FileOper readerfile(".", "fifo");
    readerfile.OpenforRead();

    //向管道读取数据
    readerfile.Read();

    //关闭文件描述符
    readerfile.Close();

    //进程结束自动调用NameFifo析构函数,删除管道
    return 0;
}
客户端程序(client.cc):
cpp 复制代码
#include "comm.hpp"

// client负责写入数据
int main()
{
    // 以写的方式打开管道
    FileOper writerfile(".", "fifo");
    writerfile.OpenforWrite();

    // 向管道写入数据
    writerfile.Write();

    // 关闭文件描述符
    writerfile.Close();
    
    return 0;
}

4.2 案例 2:命名管道实现文件拷贝

通过两个程序配合,实现文件拷贝功能:

  • **file_writer.cc:**读取本地文件,将内容写入命名管道;
  • **file_reader.cc:**从命名管道读取内容,写入目标文件。
写端程序(file_writer.c):
cpp 复制代码
#include <iostream>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <string>

// 错误处理宏
#define ERR_EXIT(m)         \
    do                      \
    {                       \
        perror(m);          \
        exit(EXIT_FAILURE); \
    } while (0)

int main()
{
    // 1. 创建命名管道
    umask(0);
    int n = mkfifo("tp", 0666);
    if (n < 0)
    {
        ERR_EXIT("mkfifo error");
    }

    // 2. 打开已存在的本地文件(待拷贝的源文件abc)
    int infd = open("abc", O_RDONLY);
    if (infd < 0) 
    {
        ERR_EXIT("open source file error");
    }

    // 3. 以写方式打开命名管道(阻塞,直到读端打开)
    int outfd = open("tp", O_WRONLY);
    if (outfd < 0) 
    {
        ERR_EXIT("open fifo error");
    }

    // 4. 读取源文件内容,写入管道
    while(true)
    {
        char buffer[1024];
        int n = read(infd, buffer, 1024);
        if(n == 0) break;
        write(outfd, buffer, n);
    }

    // 5. 关闭文件描述符
    close(infd);
    close(outfd);
    printf("文件内容写入管道完成\n");

    return 0;
}
读端程序(file_reader.c):
cpp 复制代码
#include <iostream>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <string>

// 错误处理宏
#define ERR_EXIT(m)         \
    do                      \
    {                       \
        perror(m);          \
        exit(EXIT_FAILURE); \
    } while (0)

int main()
{
    umask(0);
    // 1. 以读方式打开命名管道(阻塞,直到写端打开)
    int infd = open("tp", O_RDONLY);
    if (infd < 0)
    {
        ERR_EXIT("open fifo error");
    }

    // 2. 创建目标文件(拷贝后的文件abc.bak)
    int outfd = open("abc_bak", O_WRONLY | O_CREAT | O_TRUNC, 0666);
    if (outfd < 0)
    {
        ERR_EXIT("open target file error");
    }

    // 3. 从管道读取内容,写入目标文件
    while(true)
    {
        char buffer[1024];
        int n = read(infd, buffer, 1024);
        if(n == 0) break;
        write(outfd, buffer, n);
    }

    // 4. 关闭文件描述符,删除命名管道
    close(infd);
    close(outfd);
    unlink("tp"); // 手动删除管道文件
    printf("文件拷贝完成\n");
    return 0;
}
编译与运行:

makefile:

cpp 复制代码
.PHONY:all
all:file_reader file_writer
file_reader:file_reader.cc
	g++ -o $@ $^ -std=c++11
file_writer:file_writer.cc
	g++ -o $@ $^ -std=c++11

.PHONY:clean
clean:
	rm -f file_reader file_writer

运行结果:

  • 写端读取abc文件内容,写入命名管道tp
  • 读端从tp读取内容,写入abc.bak
  • 拷贝完成后,读端删除管道文件,两个程序退出。

五. 命名管道使用总结

  • 必须手动删除管道文件 :命名管道创建后会残留于文件系统,若不删除,下次创建会报错(errno=EEXIST),建议在通信结束后用unlink()删除
  • 避免单进程同时读写 :虽然可通过O_RDWR打开管道实现单进程读写,但会破坏半双工特性,容易导致数据混乱;
  • 处理阻塞场景 :读端未打开时写端会阻塞,写端未打开时读端会阻塞,若需非阻塞操作,可在open() 时添加O_NONBLOCK标志;
  • 数据完整性保证 :当写入数据量 ≤ PIPE_BUF(默认 4096 字节)时,内核保证写入原子性;超过则不保证,需在应用层处理分包;
  • 权限设置合理:创建管道时权限需开放给通信进程(如 0664 允许同组进程访问),避免因权限不足导致open()失败。

总结 :命名管道(FIFO)是匿名管道的重要扩展,其核心价值在于突破了亲缘进程的限制,通过文件系统路径实现任意进程间的通信,且沿用了 Linux 标准的文件操作接口,上手成本低。

  • 命名管道是带文件路径的内核缓冲区,支持跨进程通信
  • 需通过mkfifo()mkfifo命令创建,open()时需遵循读 / 写端同步规则;
  • 实战中可实现文件拷贝、C/S 通信等场景,配合**read()/write()**即可完成数据传输;
  • 与匿名管道相比,命名管道的核心优势是通信范围无限制,生命周期随内核

结束语

到这里,关于命名管道(FIFO) 的核心知识、原理、实战与优化就全部讲解完毕了。我们从修复匿名管道进程池的隐蔽问题出发,一步步认识了命名管道的本质 ------带文件路径的内核缓冲区,它打破了亲缘进程的限制,让任意无关进程都能安全通信。从概念、创建、打开规则,到可直接运行的服务端 / 客户端通信、文件拷贝实战,再到面向对象封装优化,我们完整走完了命名管道的学习与实践全流程。

相关推荐
文青小兵1 小时前
Linux云计算——docker镜像(三)
linux·docker·云计算
爱和冰阔落1 小时前
【Linux系统编程】环境变量深度解析——从 fork 继承到 export 内建命令,两张表打通进程上下文
linux·c++·环境变量·系统调用
Dlrb12111 小时前
数据结构-内核链表
linux·数据结构·链表·内核链表·inline·容器宏
zzzsde1 小时前
【Linux】线程同步和互斥(5):线程池的实现&&线程安全
linux·运维·服务器·开发语言·算法·安全
不吃土豆的马铃薯2 小时前
高性能服务器程序框架详解(包括Reactor,有限状态机等)
linux·服务器·开发语言·网络·c++
bucenggaibian2 小时前
搭建CMD编译C语言环境
linux·c语言·windows
程序猿乐锅2 小时前
Linux常用命令详解:目录、文件、压缩、编辑与查找
linux·运维·服务器
Shadow(⊙o⊙)2 小时前
库的制作与原理1.0,库打包,协作,目标文件.o、ELF格式。
linux·运维·服务器·开发语言
文青小兵2 小时前
Linux云计算——docker 网络和部分挂载(二)
linux·docker·云计算