Linux 进程间通信--命名管道

Linux 进程间通信--命名管道

七月 20, 2025 次阅读

mkfifo 命令

mkfifo 是 Linux/Unix 系统中用于创建命名管道(named pipe,也称为 FIFO)的命令。命名管道是一种特殊的文件类型,允许不相关的进程通过文件系统进行通信。

基本语法

mkfifo [选项] 管道名

主要选项

  • -m MODE--mode=MODE:设置管道的权限模式(类似 chmod)
  • -Z:设置 SELinux 安全上下文(在 SELinux 启用的系统上)

使用示例

1. 创建基本命名管道

mkfifo mypipe

这会在当前目录创建一个名为 “mypipe” 的命名管道。

2. 创建带有特定权限的管道

mkfifo -m 644 mypipe

这会创建一个权限为 644(所有者可读写,其他人只读)的命名管道。

3. 使用命名管道进行进程通信

在终端1中:

cat > mypipe

在终端2中:

cat < mypipe

此时在终端1中输入的内容会出现在终端2中。

4. 结合其他命令使用

mkfifo mypipe
tar -czf mypipe directory/ &
tar -xzf mypipe -C destination/

这通过命名管道将一个目录压缩并直接解压到另一个位置,而不生成中间文件。

工作原理

命名管道与匿名管道(|)类似,但有几点关键区别:

  1. 命名管道在文件系统中有一个名称,不相关的进程可以通过这个名称访问它
  2. 命名管道会一直存在,直到被显式删除(使用 rm
  3. 读写操作是阻塞的 - 读操作会等待有数据写入,写操作会等待有进程开始读取

实际应用场景

  1. 进程间通信:允许不相关的进程交换数据
  2. 日志处理:多个进程可以向同一个管道写入日志,由一个中心进程处理
  3. 流处理:在不创建临时文件的情况下传递大量数据
  4. 网络编程:有时用于本地客户端和服务器之间的通信

注意事项

  1. 命名管道不存储数据 - 它只是连接读写进程的通道
  2. 如果所有写端关闭,读端会收到 EOF
  3. 如果没有读端,写操作会阻塞直到有读端打开管道
  4. 命名管道可以多次打开,允许多个读写者

mkfifo 函数

mkfifo是C语言中用于创建命名管道(FIFO)的系统函数,它是在POSIX标准中定义的,可以在<sys/stat.h>头文件中找到。

函数原型

#include <sys/types.h>
#include <sys/stat.h>

int mkfifo(const char *pathname, mode_t mode);

参数说明

  1. pathname:要创建的FIFO文件的路径名
  2. mode:指定FIFO的权限模式,通常使用八进制表示(如0666)

返回值

  • 成功时返回0
  • 失败时返回-1,并设置errno来指示错误

常见错误码(errno)

  • EACCES:路径中的某个目录不允许搜索(执行)权限
  • EEXIST:指定的pathname已经存在
  • ENAMETOOLONG:路径名过长
  • ENOENT:路径中的某个目录不存在
  • ENOSPC:包含该文件的设备没有空间了
  • ENOTDIR:路径中的某个部分不是目录
  • EROFS:指定的文件位于只读文件系统上

基本用法示例

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>

int main() {
    const char *fifo_path = "/tmp/my_fifo";
    
    // 创建命名管道,权限设置为可读可写
    if (mkfifo(fifo_path, 0666) == -1) {
        perror("mkfifo");
        exit(EXIT_FAILURE);
    }
    
    printf("FIFO created at %s\n", fifo_path);
    
    // 这里可以添加对FIFO的读写操作...
    
    // 使用完后删除FIFO
    unlink(fifo_path);
    
    return 0;
}

FIFO的读写操作

创建FIFO后,可以使用标准文件I/O函数进行读写:

写入示例

int fd = open(fifo_path, O_WRONLY);
write(fd, "Hello, FIFO!", 12);
close(fd);

读取示例

int fd = open(fifo_path, O_RDONLY);
char buf[256];
read(fd, buf, sizeof(buf));
close(fd);

重要特性

  1. 阻塞行为

    • 打开一个FIFO进行读取(O_RDONLY)会阻塞,直到有进程打开同一个FIFO进行写入
    • 打开一个FIFO进行写入(O_WRONLY)会阻塞,直到有进程打开同一个FIFO进行读取
  2. 非阻塞模式

    • 可以通过O_NONBLOCK标志改变阻塞行为
int fd = open(fifo_path, O_RDONLY | O_NONBLOCK);
  1. 数据传递
    • FIFO中的数据是字节流,没有消息边界
    • 多个进程可以同时写入同一个FIFO,但这样可能会导致数据交错

注意事项

  1. FIFO在文件系统中有一个名称,但不会实际存储数据
  2. 当所有写端关闭后,读端会读到EOF
  3. 如果没有读端,写操作会阻塞(除非使用非阻塞模式)
  4. FIFO的数据是先进先出的,读取顺序与写入顺序一致
  5. 使用完毕后应该删除FIFO文件

应用示例

下面通过 mkfifo 函数创建一个命名管道用来实现两个无亲属关系的进程之间的通信

// common.hpp
#ifndef _COMMON_HPP_
#define _COMMON_HPP_ 1

#include <iostream>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <cstring>

const char *file_name = "myfifo";
const int mode = 0664;
const int MAX_SIZE = 1024;

enum global_error{
    mkfifo_error = 1,
    unlink_error,
    write_error,
    read_error,
    open_error,
};

// 该函数用于初始化命名管道以及 RAII 清理命名管道
class InitPipe{
public:
    InitPipe()
    {
        if(mkfifo(file_name, mode) == -1)
        {
            perror("InitPipe(): mkfifo error");
            exit(global_error::mkfifo_error);
        }
    }
    ~InitPipe()
    {
        if(unlink(file_name) == -1)
        {
            perror("~InitPipe(): unlink error");
            exit(global_error::unlink_error);
        }
    }
};

#endif

服务端代码

// server.cc
#include "common.hpp"
using namespace std;

int main()
{
    InitPipe init;
    int fd = open(file_name, O_RDONLY);
    if(fd == -1)
    {
        perror("server.o: open error");
        exit(global_error::open_error);
    }
    char inbuffer[MAX_SIZE];
    while(true)
    {
        int n = read(fd, inbuffer, MAX_SIZE - 1);
        if(n == -1)
        {
            perror("server.o: read error");
            exit(global_error::read_error);
        }
        inbuffer[n] = 0;
        if(strcmp(inbuffer, "quit") == 0)
        {
            break;
        }
        cout << "server get information: " << inbuffer << endl;
    }
    return 0;
}

客户端代码

#include "common.hpp"
using namespace std;


int main()
{
    int fd = open(file_name, O_WRONLY);
    string outbuffer;
    while(true)
    {
        cout << "Please Enter: ";
        cin >> outbuffer;
        int n = write(fd, outbuffer.c_str(), outbuffer.size());
        if(n == -1)
        {
            perror("client.o: write error");
            exit(global_error::write_error);
        }
        if(outbuffer == "quit")
        {
            cout << "bye" << endl;
            return 0;
        }
    }
    return 0;
}

可以观察到,数据被正常接受处理:

# client
╭─ljx@VM-16-15-debian ~/linux_review/fifo
╰─➤  ./client.o
Please Enter: 123
Please Enter: 321
Please Enter: 135
Please Enter: quit
bye

#server
╭─ljx@VM-16-15-debian ~/linux_review/fifo  
╰─➤  ./server.o
server get information: 123
server get information: 321
server get information: 135