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

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

深入探讨了 Linux 下的进程间通信机制,重点讲解了管道和共享内存的实现原理与代码实践。内容涵盖管道的基本特性、命名管道的服务端与客户端通信流程,以及共享内存的创建、挂载、读写和清理全过程。通过 C++ 封装示例,展示了如何使用 shmget、shmat 等系统调用管理共享内存段,并强调了内存对齐、同步保护及资源回收的重要性。文章旨在帮助开发者理解 IPC 底层逻辑,掌握高效的数据交换方案。

念念不忘发布于 2026/3/28更新于 2026/10/2484 浏览
Linux 进程间通信进阶:管道与共享内存实战

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

1. 进程间通信基础概念

1.1 本质与前提

进程间通信(IPC)指的是两个或多个进程进行信息传递的过程。由于进程具有独立性,一个进程的数据无法直接访问另一个进程的内存空间。父进程的全局变量对子进程不可见,或者即使可见也无法持续同步。

要让不同的进程看到同一份资源,操作系统必须提供系统调用作为桥梁。这是 IPC 的本质前提。

1.2 通信目的

  • 数据传输:将一个进程的数据发送给另一个进程。
  • 资源共享:多个进程共享同一块资源。
  • 通知事件:发送消息通知其他进程状态变化(如进程终止)。
  • 进程控制:完全控制另一个进程的执行(如调试器拦截异常)。

2. 管道通信

2.1 管道特点

管道是内核中实现的一种单向数据流通道,具有以下特点:

  1. 单向性:设计之初只允许单项数据通信。
  2. 血缘关系:通常用于具有父子关系的进程之间。
  3. 生命周期:管道随进程退出而关闭,文件描述符的生命周期依附于进程。
  4. 同步机制:内部实现了进程间的同步。
  5. 字节流:面向字节流,读写次数可能不匹配。

2.2 常见场景

  • 写端慢读端快:以慢节奏为准,读端等待数据就绪。
  • 写端快读端慢:读端一次性读取所有写入的数据。
  • 写端关闭:若写端 close(wfd),读端会收到 EOF。
  • 读端关闭:若读端 close(rfd),写端继续写入会导致 SIGPIPE 信号。

3. 进程池示例

在构建进程池时,需要注意资源回收。如果只退出了一个子进程而未回收所有僵尸进程,会导致资源泄漏。

修复方法是在主进程中遍历并回收所有子进程的状态。以下是创建 10 个子进程的简化逻辑:

// 伪代码逻辑示意
for (int i = 0; i < process_number; ++i) {
    pid_t pid = fork();
    if (pid == 0) {
        // 子进程执行任务
        task_function(i);
        exit(0);
    }
}
// 等待所有子进程结束
while (wait(NULL) > 0); 

4. 命名管道(FIFO)

命名管道允许毫不相干的进程之间进行通信,它本质上是一个特殊的文件。

4.1 服务端与客户端

服务端不需要预先建立管道,只需打开已存在的 FIFO 文件。客户端同样通过文件名连接。

服务器端 (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);
    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

4.2 运行细节

先启动服务端,再启动客户端。服务端在 open 时会阻塞,直到有客户端打开写端;反之亦然。这种机制天然保证了读写双方的同步。

4.3 应用场景

  • 匿名管道:常用于竖线操作符 | 连接的命令。
  • 命名管道:适合无亲缘关系进程的文件拷贝或简单服务交互。

5. 共享内存

共享内存是 IPC 中速度最快的方式,因为它避免了数据的多次拷贝。

5.1 原理

共享内存将物理内存映射到多个进程的虚拟地址空间。不同进程通过各自的页表指向同一物理页,从而直接读写同一块内存区域。

5.2 关键系统调用

  • shmget: 创建或获取共享内存段标识符。
  • shmat: 将共享内存挂接到当前进程的地址空间。
  • shmdt: 解除挂接(去关联)。
  • shmctl: 控制共享内存,包括删除 (IPC_RMID)。

5.3 键值 (Key) 管理

共享内存需要一个唯一的标识符。用户通常使用 ftok 函数结合文件路径和项目 ID 生成键值,确保不同进程能定位到同一块内存。

5.4 代码实现

这里封装了一个 Shm 类来管理共享内存的生命周期。

头文件 (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;
}

typedef struct {
    int count;
    char buffer[26 * 2];
} buffer_t;

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

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* 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;
};

#endif

写入端 (Writer.cc)

#include "Shm.hpp"
#include <iostream>
#include <string>
#include <cstring>

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_data = (buffer_t*)init.Addr();
    shm_data->count = 0;
    memset(shm_data->buffer, 0, 4096);
    
    char ch = 'A';
    for (int i = 0; i < 26 * 2; i += 2, ch++) {
        shm_data->buffer[i] = ch;
        shm_data->buffer[i + 1] = ch;
        shm_data->count++;
        usleep(7000000);
    }
    return 0;
}

读取端 (Reader.cc)

#include "Shm.hpp"
#include <iostream>
#include <string>
#include <unistd.h>

int main() {
    Shm shm;
    shm.Create();
    sleep(100);
    
    char* addr = (char*)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;
}

5.5 注意事项

  1. 大小对齐:建议设置为 4096 的整数倍,避免浪费或越界风险。
  2. 同步保护:共享内存本身没有锁机制。多进程同时读写可能导致数据竞争,需配合信号量使用。
  3. 清理机制:程序退出前务必调用 shmdt 和 shmctl 清理资源,否则共享内存会残留至重启。

6. 总结

进程间通信的核心在于让隔离的进程看到同一份资源。管道适合简单、单向的数据流,而共享内存则是高性能大数据传输的首选。在实际开发中,注意资源的生命周期管理和并发同步问题,才能构建稳定的系统架构。

目录

  1. Linux 进程间通信进阶:管道与共享内存实战
  2. 1. 进程间通信基础概念
  3. 1.1 本质与前提
  4. 1.2 通信目的
  5. 2. 管道通信
  6. 2.1 管道特点
  7. 2.2 常见场景
  8. 3. 进程池示例
  9. 4. 命名管道(FIFO)
  10. 4.1 服务端与客户端
  11. 4.2 运行细节
  12. 4.3 应用场景
  13. 5. 共享内存
  14. 5.1 原理
  15. 5.2 关键系统调用
  16. 5.3 键值 (Key) 管理
  17. 5.4 代码实现
  18. 5.5 注意事项
  19. 6. 总结

更多推荐文章

查看全部
  • Anthropic AI Agent 构建最佳实践与核心范式解析
  • 学生成绩管理系统:从需求分析到代码落地实战
  • OkHttp3 网络请求中的代理选择与路由策略详解
  • 国家精品公开课:Python 网络爬虫与数据分析实战
  • 最长有效括号:Python 动态规划解法
  • Stable Diffusion 局部重绘教程:轻松去除照片多余人物
  • 小狼毫输入法配置指南:拼音、五笔、注音方案设置
  • AI 绘画风格测试指南:5 个实用技巧掌握艺术生成
  • LIBERO 数据集:终身机器人学习与知识迁移基准
  • MySQL 主从集群原理及 Docker 实战部署
  • MCP Server 实现 Excel 表格一键生成可视化图表 HTML 报告
  • Ubuntu 下 llama.cpp 编译与性能调优实战
  • Ubuntu 后端服务彻底卸载与安全清理指南
  • 行星减速器原理、计算公式与 C++ 实战
  • Poe 平台:Quora 推出的 AI 模型整合工具
  • Trae AI 将设计稿自动生成前端代码图文指南
  • M2FP 模型在 AR 导航中的人体交互应用
  • GitHub Copilot Pro 学生免费开通与 VS Code 配置指南
  • OpenClaw 多平台快速部署指南(Mac/Windows/阿里云)
  • 大模型面试高频题整理

相关免费在线工具

  • 加密/解密文本

    使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online

  • Gemini 图片去水印

    基于开源反向 Alpha 混合算法去除 Gemini/Nano Banana 图片水印,支持批量处理与下载。 在线工具,Gemini 图片去水印在线工具,online

  • 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