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

Linux 进程间通信进阶:管道与共享内存详解

综述由AI生成深入探讨了 Linux 环境下进程间通信(IPC)的两种核心机制:管道与共享内存。首先分析了匿名管道与命名管道的特性及适用场景,强调了单向通信与同步机制。随后重点讲解了共享内存的原理,包括 shmget、shmat、shmdt 等系统调用的使用,以及键值(key)的唯一性管理。文章通过 C++ 封装示例展示了如何安全地创建、挂载和销毁共享内存,并指出了共享内存速度快但缺乏内置同步保护的优缺点,建议在实战中配合信号量使用以确保数据安全。

不知所云发布于 2026/3/27更新于 2026/6/215 浏览
Linux 进程间通信进阶:管道与共享内存详解

进程间通信概述

本质与目的

进程间通信(IPC, Inter-Process Communication)指的是两个或多个进程之间进行信息传递的过程。由于进程具有独立性,一个进程的数据直接发送给另一个进程并非易事。

虽然父子进程在 fork 后能访问父进程的全局变量,但这不算严格的 IPC,因为这种关系是单向的且不可持续。真正的 IPC 需要满足以下核心需求:

  • 数据传输:一个进程将数据发送给另一个进程。
  • 资源共享:多个进程共享同一份资源。
  • 通知事件:进程间发送消息通知状态变化(如子进程终止)。
  • 进程控制:控制进程拦截并监控另一进程的执行状态。

实现 IPC 的本质前提是让不同的进程看到同一份资源,这通常需要操作系统提供系统调用支持。

发展标准

相对于网络协议的标准严格性,IPC 标准的发展经历了一个过程。System V 标准是早期的重要规范,定义了共享内存、消息队列和信号量等机制。尽管部分机制在现代应用中逐渐被网络通信替代,但理解其原理对于掌握底层系统编程依然至关重要。

管道通信

匿名管道特点

匿名管道基于文件描述符,具有以下显著特点:

  1. 单向通信:设计之初仅允许单向数据传输。
  2. 血缘限制:通常用于具有亲缘关系的进程(如父子进程)。
  3. 生命周期:管道随进程退出而关闭,文件的生命周期依附于进程。
  4. 同步机制:内部实现了进程间的同步。
  5. 字节流:面向字节流,读写次数不匹配可能导致阻塞或数据丢失。

常见场景分析

  • 写慢读快:以写端节奏为准,读端等待数据就绪。
  • 写快读慢:读端一次性读取所有写入数据。
  • 写端关闭:若写端未写且关闭,读端会收到 EOF。
  • 读端关闭:若读端关闭,写端继续写入会触发 SIGPIPE 信号。

命名管道

命名管道(FIFO)解决了匿名管道只能用于有亲缘关系进程的局限,允许毫不相干的进程通过文件系统路径进行通信。

关键细节
  • 创建方式:使用 mkfifo 创建管道文件。
  • 打开行为:读端打开时若没有写端,会阻塞;反之亦然。这天然形成了一种同步机制。
  • 字符串处理:不需要像 C 语言字符串那样手动添加 \0 结束符,按字节读写即可。
  • 权限管理:需设置合适的文件权限(如 0666)。
代码示例

服务器端 (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");
        if (errno != EEXIST) exit(1);
    }
    
    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

运行顺序需注意:先启动服务端(阻塞等待连接),再启动客户端。

共享内存

共享内存是进程间通信中速度最快的方式,因为它避免了内核与用户空间之间的数据拷贝。

原理与地址空间

共享内存的核心在于让不同的进程映射到同一块物理内存区域。操作系统通过页表将虚拟地址映射到物理地址,使得多个进程的虚拟地址空间可以指向同一物理页。

在进程地址空间布局中,共享内存通常位于堆和栈之间的共享区。动态库也是利用这一机制实现代码共享的。

关键系统调用

  1. shmget: 创建或获取共享内存标识符。
    • 需要指定键值(key)、大小和权限。
    • 键值通常由 ftok 生成,确保唯一性。
  2. shmat: 将共享内存挂接到当前进程的虚拟地址空间。
    • 返回一个指针,后续可直接通过指针访问数据。
    • 无需系统调用即可读写,这是其高效的原因。
  3. shmdt: 解除共享内存的映射(去关联)。
  4. shmctl: 控制共享内存,包括删除(IPC_RMID)和查询属性(IPC_STAT)。

注意事项

  • 键值冲突:IPC_CREAT | IPC_EXCL 标志用于确保新创建,避免覆盖已有的共享内存。
  • 生命周期:共享内存的生命周期属于内核,而非进程。即使所有进程退出,若未显式删除,重启前仍可能残留。
  • 对齐问题:建议大小设置为 4096 的整数倍,否则底层可能会向上对齐导致浪费。
  • 同步保护:共享内存本身不提供互斥保护。多进程同时读写可能导致数据竞争,需配合信号量使用。

封装类示例

为了简化操作,我们可以将共享内存的管理封装为 C++ 类。

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() { 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 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 << "creator_pid: " << ds.shm_cpid << std::endl;
        std::cout << "size: " << ds.shm_segsz << std::endl;
        std::cout << "key: " << ToHex(ds.shm_perm.__key) << std::endl;
    }

    void* Attach() {
        return shmat(_shmid, nullptr, 0);
    }

    bool Detach() {
        return shmdt(_start) == 0;
    }

    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

Reader.cc (接收方)

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

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

int main() {
    Shm shm;
    shm.Create();
    sleep(3); // 模拟延迟,给 Writer 时间连接
    
    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 <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;
        usleep(7000000);
        shm_data->count++;
        sleep(1);
    }
    return 0;
}

性能与总结

共享内存之所以最快,是因为它减少了数据拷贝次数。传统 IPC 可能需要两次拷贝(用户态 -> 内核态 -> 用户态),而共享内存只需一次(用户态 -> 用户态)。

但这也带来了风险:没有自带保护机制。任何挂接到该区域的进程都可以随时读写,可能导致数据覆盖。在实际生产中,必须结合信号量等同步原语来保证线程安全。

此外,清理工作不可忽视。使用 ipcs -m 查看,ipcrm -m 删除,或者在程序析构时自动调用 shmctl(..., IPC_RMID) 都是必要的维护手段。

目录

  1. 进程间通信概述
  2. 本质与目的
  3. 发展标准
  4. 管道通信
  5. 匿名管道特点
  6. 常见场景分析
  7. 命名管道
  8. 关键细节
  9. 代码示例
  10. 共享内存
  11. 原理与地址空间
  12. 关键系统调用
  13. 注意事项
  14. 封装类示例
  15. 性能与总结
  • 💰 8折买阿里云服务器限时8折了解详情
  • Magick API 一键接入全球大模型注册送1000万token查看
  • 🤖 一键搭建Deepseek满血版了解详情
  • 一键打造专属AI 智能体了解详情
极客日志微信公众号二维码

微信扫一扫,关注极客日志

微信公众号「极客日志V2」,在微信中扫描左侧二维码关注。展示文案:极客日志V2 zeeklog

更多推荐文章

查看全部
  • 单链表综合练习:删除指定节点、反转链表与查找中间节点
  • 基于 DeepFace 与 OpenCV 的情绪分析器实现
  • 基于 OpenClaw 整合 Qlib 与 RD-Agent 构建 AI 量化系统
  • Kotlin 相等性判断:结构相等与引用相等详解
  • C++ 类与对象:封装特性实现与实战应用
  • FAIR plus 机器人全产业链接会与 2026 展会前瞻
  • 推荐一位拥有七年经验的互联网运营从业者
  • JavaQuestPlayer:跨平台互动叙事游戏播放器
  • 使用 Python 将 AI 大语言模型接入个人项目
  • ClawPanel 开源智能管理面板:支持 20+ 通道与多模型配置
  • 二级 Python 考试真题及参考代码(基本操作题)
  • 滑动窗口算法实战:从入门到经典题型解析
  • Web 端即时通讯聊天的三种加密算法实现方案
  • 仓库管理系统前端开发:主区域查询与重置功能
  • 前端函数防抖详解:原理、手写实现与实战应用
  • 前端静态站点生成(SSG)技术解析
  • Git 2.50.1 Windows 64 位安装与配置指南
  • Flutter for OpenHarmony 集成 dart_openai 实现 AIGC 功能
  • RAG 检索增强生成技术:原理架构与核心模块详解
  • Go 语言构建命令行 AI 对话客户端:环境部署与核心实现

相关免费在线工具

  • 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