《Linux系统编程》16.进程间通信-共享内存

💡Yupureki:个人主页

✨个人专栏:《C++》 《算法》《Linux系统编程》《高并发内存池》《MySQL数据库》

《个人在线OJ平台》


🌸Yupureki🌸的简介:


目录

[1. System V 共享内存](#1. System V 共享内存)

[1.1 什么是共享内存](#1.1 什么是共享内存)

[1.2 共享内存的原理](#1.2 共享内存的原理)

[1.3 接口介绍](#1.3 接口介绍)

[1.3.1 创建或获取共享内存:shmget()](#1.3.1 创建或获取共享内存:shmget())

[1.3.2 附加到进程地址空间:shmat()](#1.3.2 附加到进程地址空间:shmat())

[1.3.3 分离:shmdt()](#1.3.3 分离:shmdt())

[1.3.4 控制:shmctl()](#1.3.4 控制:shmctl())

[1.4 C++封装共享内存接口](#1.4 C++封装共享内存接口)

[1.5 测试用例](#1.5 测试用例)


1. System V 共享内存

1.1 什么是共享内存

共享内存是Linux中效率最高 的进程间通信方式,它允许多个进程将同一块物理内存区域映射到各自的虚拟地址空间中,使得数据不需要经过内核拷贝,直接像访问普通内存一样读写,从而获得极快的传输速度。但正因如此,它需要配合同步机制(如信号量)来避免数据竞争。

1.2 共享内存的原理

共享内存的本质是内存映射

  1. 物理内存:内核在物理内存中分配一块区域(可连续也可不连续)。

  2. 映射:通过页表将这块物理内存映射到多个进程的虚拟地址空间。每个进程访问自己虚拟地址空间中的共享区域,最终指向同一块物理内存。

  3. 零拷贝:数据从写进程的用户空间直接放入共享内存,读进程直接从共享内存读取,无需经过内核缓冲区。这比管道、消息队列等需要两次数据拷贝(用户→内核→用户)的方式高效得多。

  4. 持久性:共享内存的生命周期通常与内核对象绑定,即使所有进程解除映射,内存区域仍然存在(直到显式删除或系统重启)。

由于多个进程可以同时读写共享内存,必须使用信号量互斥锁原子操作来保证数据一致性。

1.3 接口介绍

Linux提供两套共享内存 API:

  • System V 共享内存(传统,经典)

  • POSIX 共享内存 (较新,更简洁,常与 mmap 配合)

这里我们先介绍System V版

1.3.1 创建或获取共享内存:shmget()

cpp 复制代码
#include <sys/shm.h>

int shmget(key_t key, size_t size, int shmflg);
  • key:IPC 键值,可以用 IPC_PRIVATE(私有)或 ftok() 生成。

  • size:共享内存大小(字节),通常向上取整到页大小(4KB)。

  • shmflg:权限标志(如 0666)与 IPC_CREAT(若不存在则创建)。

  • 返回值:共享内存标识符(shmid),失败返回 -1。

生成唯一的key值:ftok()

cpp 复制代码
#include <sys/ipc.h>

key_t ftok(const char *pathname, int proj_id);
  • pathname:路径名称(自定义,但该路径一定要存在)
  • proj_id:id值(自定义,一般为十六进制数)

1.3.2 附加到进程地址空间:shmat()

cpp 复制代码
void *shmat(int shmid, const void *shmaddr, int shmflg);
  • shmidshmget 返回的标识符。

  • shmaddr:指定映射地址,通常设为 NULL 由系统选择。

  • shmflgSHM_RDONLY(只读)或 0(读写)。

  • 返回值:映射后的虚拟地址,失败返回 (void *)-1

1.3.3 分离:shmdt()

cpp 复制代码
int shmdt(const void *shmaddr);
  • 从进程地址空间解除映射并不删除共享内存本身。

1.3.4 控制:shmctl()

cpp 复制代码
int shmctl(int shmid, int cmd, struct shmid_ds *buf);

常用命令:

  • IPC_STAT:获取状态信息。

  • IPC_SET:设置权限等。

  • IPC_RMID标记删除(当所有进程分离后真正销毁)。

1.4 C++封装共享内存接口

comm.hpp:

cpp 复制代码
#pragma one

#include <iostream>
#include <string>
#include <cstring>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <unistd.h>

#define PATHNAME "." //默认路径
#define ID 0x6666    //默认id

//用户类别:
//creater创造共享内存并使用,一般是服务器
//user使用已存在的共享内存,一般是客户端
#define CREATER "creater"
#define USER "user"

#define MAXNUM 4096

#define ERR_EXIT(m)         \
    do                      \
    {                       \
        perror(m);          \
        exit(EXIT_FAILURE); \
    } while (0)

class Shm
{
public:
    Shm(std::string path = PATHNAME, int id = ID, std::string user = USER)
    {
        _key = ftok(path.c_str(), id);//ftok生成唯一的key
        if (_key == -1)
        {
            ERR_EXIT("ftok");
            exit(1);
        }
        _user = user;
        if (_user == CREATER)//creater生成并使用共享内存
            create(IPC_CREAT | IPC_EXCL | 0666);
        else//用户只使用共享内存
            create(IPC_CREAT | 0666);
    }

    bool attach()//将共享内存映射到虚拟地址空间中
    {
        mem = (char *)shmat(_shm_id, NULL, 0);
        if (mem == (void *)-1)
        {
            ERR_EXIT("shmat");
            exit(1);
        }
        std::cout << "attach success" << std::endl;
        return true;
    }

    bool detach()//断连共享内存
    {
        if (shmdt(mem) == -1)
        {
            ERR_EXIT("shmdt");
            exit(1);
        }
        if (_user == CREATER)
        {
            int n = shmctl(_shm_id, IPC_RMID, NULL);
            if (n < 0)
            {
                ERR_EXIT("shmctl");
                exit(1);
            }
            std::cout << "detach success" << std::endl;
            return true;
        }
        return false;
    }

    int get_key()
    {
        return _key;
    }

    int get_shm_id()
    {
        return _shm_id;
    }

    ~Shm()
    {
        detach();
    }

    char *get_mem()
    {
        return mem;
    }

private:
    void create(int flag)
    {
        _shm_id = shmget(_key, MAXNUM, flag);
        if (_shm_id == -1)
        {
            ERR_EXIT("shmget");
            exit(1);
        }
    }
    key_t _key;
    int _shm_id;
    std::string _user;
    char *mem;
};

1.5 测试用例

server.cpp:

cpp 复制代码
#include "comm.hpp"

int main()
{
    Shm shm(PATHNAME,ID,CREATER);
    std::cout<<"shm id: "<<shm.get_shm_id()<<std::endl;
    shm.attach();
    char * mem = shm.get_mem();
    while(true)
    {
        if(strcmp(mem,"exit") == 0)
            break;
        std::cout<<"client input: "<<mem<<std::endl;
        sleep(1);
    }
    return 0;
}

client.cpp:

cpp 复制代码
#include "comm.hpp"

int main()
{
    Shm shm(PATHNAME,ID,USER);
    std::cout<<"shm id: "<<shm.get_shm_id()<<std::endl;
    shm.attach();
    char *mem = shm.get_mem();
    while(true)
    {
        fgets(mem,MAXNUM,stdin);
        mem[strlen(mem) - 1] = '\0';
        if(strcmp(mem,"exit") == 0) 
            break;
        std::cout<<"client input: "<<mem<<std::endl;
    }
    return 0;
}

测试:

相关推荐
嵌入式小能手2 小时前
飞凌嵌入式ElfBoard-环境变量之添加修改环境变量setenv
服务器·前端·javascript
Allen_LVyingbo2 小时前
自进化医疗智能体:动态记忆与持续运行的Python架构编程(上)
数据结构·python·架构·动态规划·健康医疗
看山是山_Lau2 小时前
如何封装和定义一个函数
c语言·开发语言·c++·笔记
小小工匠2 小时前
Linux - ARP Cache:从 `ip neigh` 到交换机转发,一次讲透主机路由表、ARP 缓存与 MAC 表
linux·tcp/ip·缓存
代码探秘者2 小时前
【算法篇】5.链表
java·数据结构·人工智能·python·算法·spring·链表
ayaya_mana2 小时前
NPS 内网穿透,二次开源版新增多种连接协议(含 P2P 配置)
linux·运维·服务器·网络协议·内网穿透·p2p·nps
weixin_307779132 小时前
2025年中国研究生数学建模竞赛C题:围岩裂隙精准识别与三维模型重构
c语言·数学建模·重构
倔强的胖蚂蚁2 小时前
openEuler 24.03 LTS SP3 使用指南
运维·云原生
枫桥骤雨2 小时前
Ubuntu配置XRDP远程桌面
linux·运维·ubuntu·xrdp