云计算百科
云计算领域专业知识百科平台

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

Linux系列文章


文章目录

  • Linux系列文章
  • 前言
  • 一、共享内存的基本概念
  • 二、共享内存的创建
    • 2.1 shmget()函数
    • 2.2 ftok()函数
    • 2.3 shmat()函数
    • 2.4 shmdt()函数
    • 2.5 shmctl()函数
  • 三、代码实现
    • 3.1 进程通信头文件代码
    • 3.2 写入共享内存进程代码
    • 3.3 读取共享内存进程代码
    • 3.4 共享内存特点

前言

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);

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

参数

pathname:

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

proj_id:

  • 项目id,int型整数

返回值: 执行成功返回一个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 共享内存特点

  • 共享内存没有同步互斥之类的保护机制
  • 共享内存时所有的进程间通信速度最快的(拷贝次数少)
  • 共享内存内部数据,由用户自己维护
  • 本篇内容就到这里了,上面介绍的好多知识,并非全部给出了演示,大家可以自己尝试。

    赞(0)
    未经允许不得转载:网硕互联帮助中心 » 深度剖析 Linux 共享内存:基本概念、系统调用及代码实现
    分享到: 更多 (0)

    评论 抢沙发

    评论前必须登录!