跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客我的书AI学习GitHub 精选镜像AI工具UI配色美学关于
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
C++

Linux 进程间通信进阶:管道与共享内存实战

Linux 进程间通信是系统编程的核心内容,主要解决独立进程间的数据传输与资源同步问题。深入探讨匿名管道、命名管道及进程池的实现细节,并重点解析 System V 共享内存机制。通过 shmget、shmat 等系统调用,展示如何构建高效的跨进程数据通道,同时涵盖关键陷阱与最佳实践,适合系统编程学习者参考。

花里胡哨发布于 2026/3/23更新于 2026/10/2595 浏览
Linux 进程间通信进阶:管道与共享内存实战

Linux 进程间通信进阶

1. 准备阶段:进程间通信的概念

1.1 本质前提

进程间通信(IPC)指的是两个或多个进程进行信息相互传递的过程。我们知道进程具有独立性,一个进程想把自己的数据发送给另一个进程,在默认情况下是比较困难的。

虽然父进程的全局变量子进程可以看到,但这不算严格的进程间通信:

  • 只能单向(父到子),不是相互的。
  • 不能持续,后续更改后子进程无法感知。

核心原则:一个进程挂掉不会影响另外的进程,即使是父子进程也一样。

要让不同的进程看到同一份资源,操作系统必然要提供系统调用。

1.2 为什么需要 IPC

  • 数据传输:一个进程需要将数据发送给另一个进程。
  • 资源共享:多个进程之间共享同样的资源。
  • 通知事件:一个进程向另一个或一组进程发送消息,通知发生了某种事件。
  • 进程控制:某些进程希望完全控制另一个进程的执行(如调试进程)。

1.3 怎么办

进程间通信的本质前提是:先让不同的进程看到同一份资源!后续大部分时候都是想办法建立这种共享资源的访问路径。

2. 进程间通信标准与发展

相对于网络标准,系统标准的发展过程较为漫长。System V 是进程间通信的一个重要定制标准,定义了共享内存、消息队列、信号量等机制的使用规范。

3. 管道的特点和情况总结

3.1 五种特点

  1. 单向性:设计之初只允许单项数据通信,基于文件实现。
  2. 血缘关系:通常用于具有血缘关系的进程(如父子进程)之间。
  3. 生命周期:管道本质是文件,打开它的进程退出,文件也会被系统自动关闭。
  4. 同步机制:管道内部实现了进程间的同步。
  5. 字节流:面向字节流,读写次数可能不匹配。

3.2 常见场景

  • 写端慢读端快:以慢节奏为准,读端等待。
  • 写端快读端慢:读端一次性读取所有数据。
  • 写端关闭:读端会收到 EOF。
  • 读端关闭:写端继续写入可能会收到 SIGPIPE。

4. 进程池

4.1 常见问题与修复

在实现进程池时,常遇到僵尸进程问题。如果只退出了一个子进程而没有回收所有子进程,会导致资源泄漏。

修复方案:确保在主进程中遍历所有子进程并调用 wait() 回收,同时正确关闭写端文件描述符。

4.2 示例:创建 10 个子进程

通过循环创建子进程,每个子进程处理特定任务,主进程负责管理生命周期。

5. 命名管道(FIFO)

5.1 基本原理

命名管道允许毫不相干的两个进程之间进行通信。客户端不需要建立管道,只需打开已存在的 FIFO 文件。

5.2 关键细节

  • 阻塞行为:首次打开时,读端会在 open 阻塞,直到有写端打开;反之亦然。这实现了读写同步。
  • 字符串处理:不需要像 C 语言那样手动添加 \0,使用 std::string 或 write 指定长度即可。
  • 权限设置:创建时需指定权限(如 )。
0666

5.3 代码示例

服务器端 (server.cc)
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>

#define FIFO_NAME "fifo"

int main() {
    // 创建 FIFO(如果已存在则忽略错误)
    if (mkfifo(FIFO_NAME, 0666) == -1) {
        perror("mkfifo");
    }
    printf("Server waiting for client...\n");
    
    int fd = open(FIFO_NAME, O_RDONLY);
    if (fd == -1) {
        perror("open");
        exit(1);
    }
    printf("Client connected.\n");
    
    char buf[1024];
    ssize_t n;
    while ((n = read(fd, buf, sizeof(buf) - 1)) > 0) {
        buf[n] = '\0';
        printf("client say# %s", buf);
        fflush(stdout);
    }
    if (n == -1) perror("read");
    close(fd);
    unlink(FIFO_NAME); // 可选:删除 FIFO
    return 0;
}
客户端 (client.cc)
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>

#define FIFO_NAME "fifo"

int main() {
    printf("Please Enter@ ");
    fflush(stdout);
    
    char msg[1024];
    if (fgets(msg, sizeof(msg), stdin) == NULL) {
        perror("fgets");
        exit(1);
    }
    
    // 以只写方式打开 FIFO(会阻塞直到有读者)
    int fd = open(FIFO_NAME, O_WRONLY);
    if (fd == -1) {
        perror("open");
        exit(1);
    }
    write(fd, msg, strlen(msg));
    close(fd);
    return 0;
}
Makefile
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

运行顺序:先启动服务端,再启动客户端。

6. 共享内存

6.1 原理

共享内存允许不同的进程映射同一块物理内存区域。通过虚拟地址对物理内存进行读写,页表将虚拟地址映射到同一物理页。

优势:

  • 无需内核拷贝,速度最快。
  • 用户空间直接访问,无需频繁系统调用。

6.2 准备工作

6.2.1 Header Only 模式

为了减少文件数量,简化开发,可以使用 .hpp 头源混合模式,将声明和定义放在同一个文件中。

6.2.2 shmget 与 Key 值

创建共享内存返回标识符(shmid)。为了唯一标识,需要使用键值(key)。

  • ftok:通过文件路径和项目 ID 生成 key,保证唯一性。
  • shmget:创建或获取共享内存段。

6.3 代码书写与封装

我们将共享内存操作封装为类,方便复用。

Shm.hpp
#ifndef __SHM_HPP
#define __SHM_HPP
#include <iostream>
#include <cstdio>
#include <unistd.h>
#include <sys/shm.h>
#include <string>

const std::string proj_name = "/home";
const int proj_id = 0x6666;
const int g_size = 4096;

static std::string ToHex(int data) {
    char hex[64];
    snprintf(hex, sizeof(hex), "0x%x", data);
    return hex;
}

class Shm {
public:
    Shm(int size = g_size) : _shmid(-1), _size(size), _key(0) {}
    ~Shm() {}

private:
    key_t GetKey() {
        _key = ftok(proj_name.c_str(), proj_id);
        if (_key < 0) perror("ftok");
        return _key;
    }

    bool CreateCoreHelper(int flags) {
        key_t k = GetKey();
        _shmid = shmget(k, _size, flags);
        if (_shmid < 0) { perror("shmget"); return false; }
        return true;
    }

public:
    bool Create() { return CreateCoreHelper(IPC_CREAT | IPC_EXCL | 0666); }
    bool Get() { return CreateCoreHelper(IPC_CREAT); }
    bool Delete() {
        int n = shmctl(_shmid, IPC_RMID, NULL);
        return n < 0 ? false : true;
    }
    void GetShmAttr() {
        struct shmid_ds ds;
        int n = shmctl(_shmid, IPC_STAT, &ds);
        if (n < 0) { perror("shmctl"); return; }
        std::cout << "pid: " << getpid() << std::endl;
        std::cout << ds.shm_cpid << std::endl;
        std::cout << ds.shm_segsz << std::endl;
        std::cout << ToHex(ds.shm_perm.__key) << std::endl;
    }
    void* Attach() { return shmat(_shmid, nullptr, 0); }
    void Detach() { shmdt(_start); }
    void Debug() {
        std::cout << "key: " << ToHex(_key) << std::endl;
        std::cout << "shmid: " << _shmid << std::endl;
    }

private:
    key_t _key;
    int _shmid;
    int _size;
    void* _start;
};

struct buffer_t {
    int count;
    char buffer[26 * 2];
};
#endif
Reader.cc (获取方)
#include "Shm.hpp"
#include <iostream>
#include <string>
#include <unistd.h>

int main() {
    Shm shm;
    shm.Get();
    shm.Debug();
    void* addr = shm.Attach();
    buffer_t* shm_addr = (buffer_t*)addr;
    int old = shm_addr->count;
    while (true) {
        if (old != shm_addr->count) {
            std::cout << "count : " << shm_addr->count << std::endl;
            std::cout << "data : " << shm_addr->buffer << std::endl;
            old = shm_addr->count;
        }
        usleep(50000);
        if (shm_addr->count >= 26) break;
    }
    shm.Detach();
    shm.Delete();
    return 0;
}
Writer.cc (创建方)
#include "Shm.hpp"
#include <iostream>
#include <string>
#include <string.h>

Shm shm;

class Init {
public:
    Init() {
        shm.Get();
        addr = (char*)shm.Attach();
        std::cout << "addr: " << ToHex((long long)addr) << std::endl;
    }
    ~Init() { shm.Detach(); }
    char* Addr() { return addr; }
public:
    char* addr;
};

Init init;

int main() {
    std::cout << "test Begin..." << std::endl;
    buffer_t* shm_ptr = (buffer_t*)init.Addr();
    shm_ptr->count = 0;
    memset(shm_ptr->buffer, 0, 4096);
    char ch = 'A';
    for (int i = 0; i < 26 * 2; i += 2, ch++) {
        shm_ptr->buffer[i] = ch;
        shm_ptr->buffer[i + 1] = ch;
        shm_ptr->count++;
        sleep(1);
    }
    return 0;
}

6.4 注意事项

  1. 去关联:删除共享内存前一般要先去关联 (shmdt)。
  2. 大小对齐:建议设置为 4096 的整数倍,避免浪费或越界风险。
  3. 保护机制:共享内存本身没有自带保护,需配合信号量实现互斥。
  4. 性能:由于直接映射用户空间,无需内核拷贝,是 IPC 中速度最快的方式。

7. 结尾

本文涵盖了 Linux 下主要的进程间通信方式,从管道到共享内存,重点讲解了底层原理与代码实现。掌握这些技术对于编写高性能、高可靠性的系统程序至关重要。

目录

  1. Linux 进程间通信进阶
  2. 1. 准备阶段:进程间通信的概念
  3. 1.1 本质前提
  4. 1.2 为什么需要 IPC
  5. 1.3 怎么办
  6. 2. 进程间通信标准与发展
  7. 3. 管道的特点和情况总结
  8. 3.1 五种特点
  9. 3.2 常见场景
  10. 4. 进程池
  11. 4.1 常见问题与修复
  12. 4.2 示例:创建 10 个子进程
  13. 5. 命名管道(FIFO)
  14. 5.1 基本原理
  15. 5.2 关键细节
  16. 5.3 代码示例
  17. 服务器端 (server.cc)
  18. 客户端 (client.cc)
  19. Makefile
  20. 6. 共享内存
  21. 6.1 原理
  22. 6.2 准备工作
  23. 6.2.1 Header Only 模式
  24. 6.2.2 shmget 与 Key 值
  25. 6.3 代码书写与封装
  26. Shm.hpp
  27. Reader.cc (获取方)
  28. Writer.cc (创建方)
  29. 6.4 注意事项
  30. 7. 结尾

更多推荐文章

查看全部
  • CQRS 设计模式详解与简单示例
  • 基于STM32的智能家居安防系统设计与实现
  • 基于 Java 与 Vue 的大学生理财记账贷款系统
  • OpenClaw 安装与飞书机器人配置全流程指南
  • 鸿蒙 HarmonyOS 开发技术入门与实战指南
  • 计算机研究生就业现状分析与Python全栈学习路径
  • HarmonyOS ArkWeb 开发指南:Hybrid 应用鸿蒙化与 JSBridge
  • AVL 树原理与 C++ 实现详解
  • Spring Boot 日志使用指南:框架、级别与配置详解
  • AI 产品开发:工程化挑战与底层逻辑
  • Redis 哈希结构基本指令与原理
  • 医疗多模态大模型概述:医学影像分析与核心算法总结
  • 企业级图像AIGC技术:Seedream 4.0 模型能力与应用场景
  • 通用人工智能平台功能解析与商业化应用场景指南
  • FPGA与DSP协同通信系统设计与接口选型
  • Linux 系统下 Git 入门指南:高效管理代码库
  • CATE 条件平均处理效应估计:五大方法原理详解与 Python 实战
  • 计算机基础知识总结:网络、操作系统、数据库、C++ 与算法
  • MCP AI Copilot 集成测试体系与能力评估标准
  • OpenAI 发布首款 AI 原生浏览器 ChatGPT Atlas

相关免费在线工具

  • Base64 字符串编码/解码

    将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online

  • Base64 文件转换器

    将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online

  • Markdown转HTML

    将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online

  • HTML转Markdown

    将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online

  • JSON 压缩

    通过删除不必要的空白来缩小和压缩JSON。 在线工具,JSON 压缩在线工具,online

  • JSON美化和格式化

    将JSON字符串修饰为友好的可读格式。 在线工具,JSON美化和格式化在线工具,online