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

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

综述由AI生成深入探讨了 Linux 环境下进程间通信(IPC)的核心机制,重点讲解了管道(匿名与命名)及共享内存的实现原理与实战代码。内容涵盖 IPC 概念、System V 标准背景、管道特性分析、进程池构建中的 Bug 修复,以及共享内存的 shmget/shmat/shmctl 系统调用详解。通过封装 Shm 类展示如何安全地创建、挂接和清理共享内存,并强调了同步机制缺失时的风险及应对策略。文章旨在帮助开发者理解底层通信模型,提升系统编程能力。

kaikai发布于 2026/3/20更新于 2026/6/517 浏览
Linux 进程间通信进阶:管道与共享内存实战

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

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

什么是进程间通信?

进程间通信(IPC)指的是两个或多个进程进行信息相互传递的过程。我们知道进程具有独立性,每个进程拥有独立的内核数据结构、代码和数据空间。

一个进程想把自己的数据发送给另一个进程,直接操作对方内存是不允许的。虽然父进程的全局变量在子进程创建时可见,但这不算 IPC,因为:

  • 只能单向传递(父->子),无法持续交互。
  • 后续更改后,子进程无法感知。

核心前提: 要让不同的进程看到同一份资源,操作系统必须提供系统调用支持。

为什么要进行 IPC?

主要目的包括:

  • 数据传输:一个进程需要将数据发送给另一个进程。
  • 资源共享:多个进程共享同样的资源。
  • 通知事件:通知其他进程发生了某种事件(如进程终止)。
  • 进程控制:完全控制另一个进程的执行(如调试器拦截异常)。

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

相对于网络标准,IPC 的系统标准发展过程较为漫长。System V 是早期重要的定制标准之一,定义了共享内存、消息队列和信号量的接口规范。

虽然部分 System V 机制在现代应用中逐渐被包装或替代,但理解其原理对于掌握底层系统编程依然至关重要。

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

管道是进程间通信最基础的方式之一。

五种特点

  1. 单向性:设计之初只允许单项数据通信,基于文件实现。
  2. 血缘关系:通常用于具有父子关系的进程之间。
  3. 生命周期:本质是文件,打开它的进程退出,文件句柄关闭,生命周期随进程结束。
  4. 同步机制:内部实现了进程间的同步。
  5. 字节流:面向字节流,读写次数不匹配可能导致数据丢失或阻塞。

常见场景

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

4. 进程池实战

进程池通过复用一组进程来处理任务,避免频繁创建销毁的开销。

常见问题与修复

在实现进程池时,常遇到子进程未全部回收的问题。如果只退出了一个子进程,其余僵尸进程依然存在。

解决方案: 确保主进程循环等待所有子进程退出,并正确关闭写端文件描述符。

// 伪代码逻辑示意
for (int i = 0; i < process_number; ++i) {
    wait(NULL); // 回收所有子进程
}
close(wfd); // 关闭写端,通知子进程退出

创建 10 个子进程示例

通过 fork 创建固定数量的子进程,利用管道进行任务分发。注意处理 waitpid 和信号掩码,防止意外中断。

5. 命名管道

匿名管道受限于父子进程关系,而命名管道(FIFO)允许毫不相干的进程通信。

关键细节

  • 客户端不需要建立管道:只需打开已存在的 FIFO 文件。
  • 阻塞行为:首次打开读端时,如果没有写端,open 会阻塞;反之亦然。这天然形成了一种同步机制。
  • 无需 C 语言 \0:读写的是原始字节流,不像字符串函数那样依赖结尾符。
  • 权限设置:使用 mkfifo 创建时需指定权限(如 0666)。

服务端与客户端代码结构

server.cc (服务端)

服务端先启动,打开 FIFO 进行读写。

#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() {
    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);
    }
    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);
    }
    
    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

运行顺序

必须先启动服务器(打开读端),再启动客户端(打开写端),否则客户端的 open 会一直阻塞。

6. 共享内存

共享内存是速度最快的 IPC 方式,因为它避免了数据拷贝,直接映射到进程地址空间。

原理

通过页表映射,让不同进程看到同一块物理内存。动态库就是典型的共享区应用。

  • 用户空间访问:创建和删除需要系统调用,但读写共享内存时,用户可以直接通过指针访问,无需再次陷入内核。
  • 地址布局:共享内存位于堆栈之间,具体位置取决于虚拟地址空间的分配策略。

核心系统调用

  1. shmget: 创建或获取共享内存标识符。需要 Key 值来唯一标识。
  2. shmat: 将共享内存挂接到当前进程的虚拟地址空间。
  3. shmdt: 解除挂接(去关联)。
  4. shmctl: 控制共享内存,如删除 (IPC_RMID)。

Key 值的选择

Key 值用于在内核中唯一标识共享内存段。可以使用 ftok 函数结合文件路径和项目 ID 生成,避免硬编码冲突。

key_t GetKey() {
    key_t k = ftok("/home", 0x6666);
    if (k < 0) {
        perror("ftok");
    }
    return k;
}

代码演示:封装类 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;

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

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

private:
    key_t GetKey() {
        _key = ftok(proj_name.c_str(), proj_id);
        return _key;
    }

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

    key_t _key;
    int _shmid;
    int _size;
    void* _start;
};
#endif

Reader.cc 与 Writer.cc

一方负责创建,另一方负责获取。不能双方都创建,否则无法建立通信信道。

// Reader.cc
#include "Shm.hpp"
#include <iostream>

int main() {
    Shm shm;
    shm.Create();
    sleep(3);
    shm.Attach();
    // 此处可添加读写逻辑
    shm.Detach();
    shm.Delete();
    return 0;
}

// Writer.cc
#include "Shm.hpp"
#include <iostream>

int main() {
    Shm shm;
    shm.Get(); // 仅获取,不创建
    shm.Attach();
    // 写入数据
    shm.Detach();
    return 0;
}

注意事项

  1. 大小对齐:建议设置为 4096 的整数倍,否则底层会向上对齐申请,造成浪费。
  2. 保护机制:共享内存本身没有锁,多进程同时读写需配合信号量使用。
  3. 清理残留:程序异常退出可能导致共享内存残留,可使用 ipcs -m 查看,ipcrm -m 删除。

7. 总结

  • 管道:适合父子进程,简单但功能有限。
  • 命名管道:适合任意进程,有文件系统入口。
  • 共享内存:速度最快,但需自行处理同步问题。

在实际开发中,根据性能需求和进程关系选择合适的 IPC 方式,并注意资源的生命周期管理。

目录

  1. Linux 进程间通信进阶:管道与共享内存实战
  2. 1. 准备阶段:进程间通信的概念
  3. 什么是进程间通信?
  4. 为什么要进行 IPC?
  5. 2. 进程间通信标准与发展
  6. 3. 管道的特点和情况总结
  7. 五种特点
  8. 常见场景
  9. 4. 进程池实战
  10. 常见问题与修复
  11. 创建 10 个子进程示例
  12. 5. 命名管道
  13. 关键细节
  14. 服务端与客户端代码结构
  15. server.cc (服务端)
  16. client.cc (客户端)
  17. Makefile
  18. 运行顺序
  19. 6. 共享内存
  20. 原理
  21. 核心系统调用
  22. Key 值的选择
  23. 代码演示:封装类 Shm.hpp
  24. Reader.cc 与 Writer.cc
  25. 注意事项
  26. 7. 总结
  • 💰 8折买阿里云服务器限时8折了解详情
  • Magick API 一键接入全球大模型注册送1000万token查看
  • 🤖 一键搭建Deepseek满血版了解详情
  • 一键打造专属AI 智能体了解详情
极客日志微信公众号二维码

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

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

更多推荐文章

查看全部
  • 基于 Spring Cloud Alibaba 与 Nacos 的微服务负载均衡实践
  • 低代码构建智能培训管理系统,实现全流程数字化管控
  • VS Code 内置聊天与 GitHub Copilot Chat 的区别及汉化设置
  • 基于 Java+SpringBoot+UniApp 的待办事项提醒微信小程序设计与实现
  • Kafka 简介、核心原理与典型使用场景
  • AI 绘画风格融合技术指南与参数配置
  • Python 基于关键词爬取小红书笔记数据脚本
  • AI 与存储结合:智能存储实践与挑战
  • MySQL 复制表:结构、数据及索引的完整复制
  • OpenClaw 入门指南:构建本地化私人 AI Agent
  • DeepSeek 系列版本演进:从 V1 到 R1 的核心差异与选型指南
  • 云电脑部署 DeepSeek 横向对比:ToDesk、顺网云与海马云性能测试
  • AIGC 个性化与定制化内容生成:技术与应用
  • Spring Boot Web 后端开发核心注解
  • LeetCode 热题 100 快速通关指南与核心算法模板
  • 使用 LLaMA Factory 定制 AI 绘画提示词生成器
  • Springer Nature 高影响力图书合集:专业应用计算与计算机科学
  • Ubuntu 24.04 LTS 安装 NVIDIA 显卡驱动、CUDA 12.5 及 Docker 容器工具包
  • ChatGLM3 大模型本地化部署、应用开发与微调实战指南
  • 构建基于 Rust 与 GLM-5 的高性能 AI 翻译 CLI 工具

相关免费在线工具

  • 加密/解密文本

    使用加密算法(如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