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

测试:

相关推荐
j_xxx404_11 分钟前
万字长文爆肝:彻底弄懂Linux文件系统(Ext2),从Inode、Block到Dentry核心机制全解析
linux·运维·服务器
2401_8414956414 分钟前
Linux C++ TCP 服务端经典的监听骨架
linux·网络·c++·网络编程·ip·tcp·服务端
春栀怡铃声15 分钟前
【C++修仙录02】筑基篇:类和对象(中)
c++
Zn_lunar21 分钟前
autodl tizi+codex cli
运维·服务器·网络
@insist12327 分钟前
网络工程师-实战配置篇(一):深入 BGP 与 VRRP,构建高可靠网络
服务器·网络·php·网络工程师·软件水平考试
楼田莉子33 分钟前
同步/异步日志系统:日志器管理器模块\全局接口\性能测试
linux·服务器·开发语言·c++·后端·设计模式
故事和你9136 分钟前
洛谷-数据结构-1-3-集合3
数据结构·c++·算法·leetcode·贪心算法·动态规划·图论
鹅是开哥37 分钟前
XXL-Job Docker 部署中“登录无响应”的排查与解决
运维·docker·容器
奇妙之二进制39 分钟前
zmq源码分析之io_thread_t
linux·服务器
逻极40 分钟前
MySQL 从入门到精通:一个老 DBA 的实战心法
运维·数据库·mysql·从入门到精通·mysql从入门到精通