深度剖析 Linux 共享内存:基本概念、系统调用及代码实现

发布于:2025-05-14 ⋅ 阅读:(8) ⋅ 点赞:(0)

Linux系列文章



前言

Linux共享内存是一种高效的进程间通信(IPC)机制,允许多个进程直接访问同一块物理内存区域,避免了数据在用户空间和内核空间的多次拷贝。本篇我将通个多个模块来介绍它的特点及使用。


一、共享内存的基本概念

这里我们先对使用,简单了解,后面会结合代码介绍

我们知道进程间通讯的本质是:让不同进程,看到同一份资源!,所以我们要使用共享内存来达到通讯目的,首先要保证进程可以看到同一份资源。

在使用共享内存进行通讯时,操作系统首先会给我们在物理内存上申请一份内存空间,将这份共享空间挂接到进程地址空间(在页表中与虚拟内存建立映射关系),当进程要进行通讯时,可直接通过PCB结构体来访问共享内存,完成数据交互,而对于这份空间的申请、释放、管理,都是由操作系统来完成(进程具有独立性,具体原因上篇我们详细介绍了)。
在这里插入图片描述
通讯结束,我们要释放共享内存,首先要将共享内存与进程之间去关联,然后再释放共享内存。上面操作均有操作系统来完成,所以在写代码前,我们首先要学习系统调用接口。

二、共享内存的创建

下面我们会在介绍接口的同时,介绍共享的原理及特点

2.1 shmget()函数

#include <sys/ipc.h>
#include <sys/shm.h>
int shmget(key_t key, size_t size, int shmflg);

功能
shmget()接口让操作系统在物理内存中,申请一份空间用做共享内存。

参数

key:

  • key_t:int类型的封装
  • 使用特定的系统接口获取
  • 用来标识共享内存,在内核中具有唯一性,通讯间的进程可以通过key找到同一份资源。

size:

  • 创建共享内存的空间大小,单位为字节

shmflg:

  • 使用给定的宏进行传参
  • IPC_CREAT(单独使用时):如果你申请的共享内窜不在,就创建,存在就获取返回(是否存在通过key判断)。
  • IPC_CREAT|IPC_EXCL:如果你申请的共享内存不存在,就创建,存在就出错返回(确保申请的共享内存一定时新的,也保证了唯一性),IPC_EXCL不能单独使用。创建新共享内存时可设置权限,后面有演示。

返回值:
执行成功返回一个共享内存标识符(用户级标识符,只在我们的进程内标识资源的唯一性),否则返回-1并设置errno

2.2 ftok()函数

#include <sys/types.h>
#include <sys/ipc.h>
key_t ftok(const char *pathname, int proj_id);

功能:
使用用户提供的参数pathnameproj_id的最低8位,得到一个key值。

参数

pathname:

  • 用户提供的路径信息(没有什么特殊的)

proj_id:

  • 项目idint型整数

返回值:
执行成功返回一个key,否则返回-1并设置错误errno

2.3 shmat()函数

#include <sys/types.h>
#include <sys/shm.h>
void *shmat(int shmid, const void *shmaddr, int shmflg);

功能:
将标识符为shmid的共享内存,挂接到当前进程的进程地址空间。

参数

shmid

  • shmget接口,得到的共享内存标识符。

shmaddr:

  • 要挂接到进程的内存地址(属于共享内存区域的),如果传递nullptr函数就会在共享内存的合适页码处与进程建立映射。

shmflg:

  • 进程以什么方式挂接共享内存,这里我们只介绍0,以读写方式挂接。

返回值:
执行成功返回挂接的共享内存地址,失败返回(void *) -1并设置errno.

2.4 shmdt()函数

#include <sys/types.h>
#include <sys/shm.h>
int shmdt(const void *shmaddr);

功能: 将共享内存从调用进程的地址空间分离(去挂接)

参数
shmaddr:

  • shmat函数执行得到的地址。

返回值:
执行成功返回0,失败返回-1并设置errno.

2.5 shmctl()函数

#include <sys/ipc.h>
#include <sys/shm.h>
int shmctl(int shmid, int cmd, struct shmid_ds *buf);

功能: 控制共享内存

参数

cmd: 用于指定要对共享内存执行的操作

  • IPC_STAT:获取共享内存的状态信息,将他存储在shmid_ds类型的结构体中。
  • IPC_RMID:标记共享内存待删除。当所有进程都与共享内存分离后,系统将删除它。还有几个大家感兴趣自己了解吧

buf:

  • shmid_ds类型指针,为输出型参数,用来将内核数据结构信息拷贝带出。

struct shmid_ds是操作系统用来描述共享内存的内核结构体,操作系统通过组织并管理描述共享内存的对象,来达到对共享内存的管理,这个概念我们已经介绍了多次。

struct shmid_ds {
    struct ipc_perm shm_perm;    /* Ownership and permissions */
    size_t          shm_segsz;   /* Size of segment (bytes) */
    time_t          shm_atime;   /* Last attach time */
    time_t          shm_dtime;   /* Last detach time */
    time_t          shm_ctime;   /* Last change time */
    pid_t           shm_cpid;    /* PID of creator */
    pid_t           shm_lpid;    /* PID of last shmat(2)/shmdt(2) */
    shmatt_t        shm_nattch;  /* No. of current attaches */
    ...
};
struct ipc_perm {
    key_t          __key;    /* Key supplied to shmget(2) */
    uid_t          uid;      /* Effective UID of owner */
    gid_t          gid;      /* Effective GID of owner */
    uid_t          cuid;     /* Effective UID of creator */
    gid_t          cgid;     /* Effective GID of creator */
    unsigned short mode;     /* Permissions + SHM_DEST and
                                SHM_LOCKED flags */
    unsigned short __seq;    /* Sequence number */
};

从这个结构体定义中我们可以看到key也是存储在内核数据结构中的。

三、代码实现

接下来我们使用上面的接口,简单实现两个进程间的通信,这部分也会穿插一些知识。

3.1 进程通信头文件代码

#pragma once
//#include"log.hpp"
#include<iostream>
#include<sys/ipc.h>
#include<sys/types.h>
#include<cstdio>
#include<cstdlib>
#include<sys/shm.h>
#include<unistd.h>
#include<string.h>
using namespace std;

#define Pathname "/home/liang/myshm/"
#define Proj_id 0x664
#define size 4096

//Log log;

key_t GetKey()//获取key值
{
    key_t k=ftok(Pathname,Proj_id);
    if(k==-1)
    {
        perror("ftok");
        exit(1);
    }
    return k;
}

int GetshmMem(int shmflg)//创建共享内存
{
    key_t k=GetKey();

    int shmid=shmget(k,size,shmflg);
    if(shmid==-1)
    {
        perror("shmget");
        exit(1);
    }
    return shmid;
}

int CreatshmMem()//目的:创建新的共享内存
{
    return GetshmMem(IPC_CREAT|IPC_EXCL|0666);//目的:设置权限为0666
}

int GetshmMe()//目的:得到已将存在的共享内存
{
    return GetshmMem(IPC_CREAT);

3.2 写入共享内存进程代码

processb.cc文件

#include"commem.hpp"
//#include"log.hpp"

//extern Log log;

int main()
{
    int shmid= GetshmMe();
    char *shmaddr=(char*)shmat(shmid,nullptr,0);//将共享内存与当前进程挂接

    while(true)
    {
        cout<<"Please Enter@ ";
        fgets(shmaddr,4096,stdin);//将标准流的数据读入共享内存
    }
    return 0;
}

3.3 读取共享内存进程代码

processa.cc文件

#include"commem.hpp"
//#include"log.hpp"
//extern Log log;
struct shmid_ds s_shm;
int main()
{

    int shmid=CreatshmMem();
    char* shmaddr=(char*)shmat(shmid,nullptr,0);

    while(true)
    {
        cout<<"client say@ "<<shmaddr<<endl;//从共享内存中读取数据
        sleep(1);
    }
}

接下来我们编译,并执行processa文件,并通过ipcs -m指令查看共享内存信息。
在这里插入图片描述

可以看到,此处为新创建的共享内存的属性信息,这个共享内存的生命周期是随内核的,如果我们主动关闭,共享内存一直存在,除非内核重启。你可以调用shmdt()函数主动关闭,自行验证。
补充:关闭共享内存,我们还可以使用ipcrm -m shmid指令主动关闭,(这也可以体现处key是内核级shmid是用户级)。

接下来我们继续:

在这里插入图片描述
当我们启动processb进程后挂接数变为了2.
在这里插入图片描述
当我们进行通讯时可以发现,读取方并不会在意,输入方是否输入数据,他会一致直执行。从这个现象我们可以知道,共享内存这种通信方式并不存在互斥机制,如果你想让两者实现互斥,可以使用管道通信作为一个简单的锁,这里我就不演示了。

3.4 共享内存特点

  1. 共享内存没有同步互斥之类的保护机制
  2. 共享内存时所有的进程间通信速度最快的(拷贝次数少)
  3. 共享内存内部数据,由用户自己维护

本篇内容就到这里了,上面介绍的好多知识,并非全部给出了演示,大家可以自己尝试。


网站公告

今日签到

点亮在社区的每一天
去签到