[Linux]共享内存

共享内存

共享内存允许两个或多个进程访问同一块物理内存空间,就好像它们对这块内存拥有共同的读写权限一样。这块共享的内存区域由操作系统内核负责管理和维护,进程通过特定的系统调用将其映射到自己的虚拟地址空间中,之后便可以像访问普通内存一样对其进行读写操作,从而实现进程间的数据共享。

相关接口

创建共享内存

  1. 函数原型:int shmget(key_t key, size_t size, int shmflg);

    参数:

    • key:是一个整数值,用于标识共享内存段。通常可以使用ftok()函数来生成一个唯一的key值。不同的进程可以通过相同的key值来获取或创建同一个共享内存段。
    • size:指定了要创建或获取的共享内存段的大小,以字节为单位。
    • shmflg:是一组标志位,用于控制shmget()函数的行为。常用的取值如下:
      • IPC_CREAT:如果共享内存段不存在,则创建一个新的共享内存段;如果已经存在,则返回已存在共享内存段的标识符(shmid)。
      • IPC_EXEL:与IPC_CREAT一起使用时,如果共享内存段已经存在,则shmget()函数会返回错误。这个标志主要用于确保创建的是一个全新的、独一无二的共享内存段,避免多个进程同时创建同名的共享内存段而导致冲突。
      • 权限值:用于设置共享内存段的访问权限,与文件权限设置相同。

    返回值:成功返回共享内存标识符,失败返回-1。

  2. 函数原型:key_t ftok(const char *pathname, int proj_id);

    参数:

    • pathname:是一个有效的文件路径名。ftok()函数会根据这个文件的索引节点号(inode)来生成键值的一部分。通常,可以选择一个系统中存在的且不会被轻易删除或移动的文件,一般使用当前目录下的某个文件或一个已知的配置文件等。
    • proj_id:是一个字符型的项目标识符,它与文件路径名结合起来生成最终的键值。其取值范围是 0 到 255 之间的整数。

    返回值:成功返回一个有效的键值(key_t 类型),失败返回-1。

删除共享内存

在命令行中通过ipcs -m来查看共享内存相关信息。

若是在命令行中可以用ipcrm -m [shmid]来进行删除。

函数原型:int shmctl(int shmid, int cmd, struct shmid_ds *buf);

参数:

  • shmid:共享内存段的标识符
  • cmd:决定了shmctl()函数具体要对共享内存段执行何种操作。常用的命令如下:
    • IPC_RMID:用于删除共享内存段。当所有的映射都解除时,才会正真进行删除。
    • IPC_STAT:用于获取共享内存段的当前状态信息,并将这些信息存储到buf所指向的struct shmid_ds结构体中。
    • IPC_SET:用于设置共享内存段的某些属性,这些属性通常存储在buf所指向的struct shmid_ds结构体中。
  • buf:是一个指向struct shmid_ds结构体的指针,通常设置为NULL

返回值:成功返回0,失败返回-1。

映射共享内存到进程地址空间(关联)

函数原型:void *shmat(int shmid, const void *shmaddr, int shmflg);

参数:

  • shmaddr:指定共享内存段连接到进程地址空间的起始地址。通常设置为NULL,表示由系统自动选择一个合适的地址来连接共享内存段。
  • shmflg:是一组标志位,用于控制共享内存段的连接方式和权限等。通常设置为0,表示以读写方式连接共享内存段,且不进行地址舍入操作,这是默认的连接方式。

返回值:成功返回一个指向共享内存段连接到进程地址空间的起始地址的指针(与malloc的返回值差不多)。失败返回-1。

撤销共享内存映射(去关联)

函数原型:int shmdt(const void *shmaddr);

参数:

  • shmaddr:是之前通过shmat()函数连接共享内存段时返回的地址指针。

返回值:成功返回0,失败返回-1。

shmid 和 key

创建共享内存的时候,我们通过key值来保证共享内存在系统中的唯一性。它只要确保它是唯一的就行了,具体的值其实并不重要。那么key在哪里呢?共享内存=物理内存块+共享内存的相关属性,既然key是用来标识共享内存的唯一性的,那么在共享内存相关属性的结构体中一定会有key这个属性。如下图:

既然我们使用key来标识共享内存的唯一性,那为什么还要有shmid(共享内存标识符)呢?这就有点像文件描述符fd和文件的索引节点号inode之间的关系。fd是我们来使用的属于用户级,key同样如此;而inode通常由操作系统使用属于内核级,shmid同样如此。保证了底层和上层的解耦。

示例代码

以下代码完整的演示了共享内存的通信过程。客户端向共享内存中发消息,服务器端从共享内存中读取消息。

comm.hpp

#ifndef _COMM_HPP_
#define _COMM_HPP_
#include <iostream>
#include <cstring>
#include <unistd.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>

#define PATHNAE "."
#define PROJ_ID 0x77
#define MAX_SIZE 4096

//获取key值
key_t getKey()
{
    key_t k = ftok(PATHNAE, PROJ_ID);
    if (k < 0)
    {
        std::cerr << errno << ":" << strerror(errno) << std::endl;
        exit(1);
    }
}

//通过不同的标志位来执行不同的操作
int getShmHelper(key_t k, int flags)
{
    int shmid = shmget(k, MAX_SIZE, flags);
    if (shmid < 0)
    {
        std::cerr << errno << ":" << strerror(errno) << std::endl;
        exit(2);
    }
}

//创建共享内存
int createShm(key_t k)
{
    return getShmHelper(k, IPC_CREAT | IPC_EXCL | 0600);
}

//获取共享内存
int getShm(key_t k)
{
    return getShmHelper(k, IPC_CREAT);
}

//删除共享内存
void delShm(int shmid)
{
    if (shmctl(shmid, IPC_RMID, nullptr) == -1)
    {
        std::cerr << errno << ":" << strerror(errno) << std::endl;
    }
}

//关联
void* attachShm(int shmid)
{
    void *mem = shmat(shmid, nullptr, 0);
    if ((long long)mem == -1L)
    {
        std::cerr << errno << ":" << strerror(errno) << std::endl;
        exit(3);
    }
    return mem;
}

//去关联
void detachShm(void *shmaddr)
{
    if (shmdt(shmaddr) == -1) 
    {
        std::cerr << errno << ":" << strerror(errno) << std::endl;
        exit(4);
    }
}

#endif

shm_server.cc

#include "comm.hpp"

int main()
{
    //创建共享内存
    key_t k = getKey();
    printf("key: 0x%x\n", k);
    int shmid = createShm(k);
    printf("shmid: %d\n", shmid);
    //sleep(3);

    //关联
    char *start = (char*)attachShm(shmid);
    printf("attach success, address start: %p\n", start);

    //使用
    while (true)
    {
        if (strcmp(start, "break") == 0) break;
        printf("client say: %s\n", start);
        sleep(1);
    }

    //去关联
    detachShm(start);
    printf("server: detachShm success!!!\n");
    //sleep(3);

    //删除共享内存
    delShm(shmid);
    printf("server: delShm success!!!\n");

    return 0;
}

shm_client.cc

#include "comm.hpp"

int main()
{
    //获取共享内存
    key_t k = getKey();
    int shmid = getShm(k);
	//关联
    char* start = (char*)attachShm(shmid);
    printf("client: attach success, address start: %p\n", start);

    const char *msg = "我是客户端,正在和你发消息!";
    int cnt = 0;
    pid_t pid = getpid();
    while (true)
    {
        sleep(1);
        //发送5条消息
        if (cnt == 5)
        {
            snprintf(start, MAX_SIZE, "break");
            break;
        }
        snprintf(start, MAX_SIZE, "%s[pid:%d][消息编号:%d]", msg, pid, ++cnt);

    }
	//去关联
    detachShm(start);
    printf("client: detachShm success!!!\n");

    return 0;
}

优缺点

优点:由于多个进程直接访问同一块内存,不需要像其他进程间通信方式(如管道)那样进行数据的复制和传递,因此共享内存的通信效率非常高,数据传输速度快,特别适合需要频繁共享大量数据的场景。

缺点:自身没有同步互斥机制,没有对数据做保护。

posted @   羡鱼OvO  阅读(74)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具
点击右上角即可分享
微信分享提示