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

测试:

相关推荐
難釋懷5 小时前
Redis数据结构-Set结构
数据结构·redis·bootstrap
DianSan_ERP5 小时前
如何通过抖店订单接口实现订单状态管理与履约自动化?
运维·自动化
b***25116 小时前
18650电池点焊机:电阻焊技术如何决定电池组的成败|深圳比斯特自动化
运维·自动化
原来是猿6 小时前
网络计算器:理解序列化与反序列化(中)
linux·运维·服务器·网络·tcp/ip
前端老曹6 小时前
Docker 从入门到放弃:完整指南
运维·docker·容器
知识领航员7 小时前
蘑兔AI音乐深度实测:功能拆解、实测表现与适用场景
java·c语言·c++·人工智能·python·算法·github
AOwhisky7 小时前
虚拟化技术学习笔记
linux·运维·笔记·学习·虚拟化技术
rabbit_pro8 小时前
Docker compose部署Ollama使用模型
linux·运维·docker
如何原谅奋力过但无声8 小时前
【灵神高频面试题合集06-08】反转链表、快慢指针(环形链表/重排链表)、前后指针(删除链表/链表去重)
数据结构·python·算法·leetcode·链表
平行侠8 小时前
037插入排序 - 整理扑克牌的算法
数据结构·算法