深度剖析 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
#include
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
#include
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
#include
void *shmat(int shmid, const void *shmaddr, int shmflg);
功能:
将标识符为shmid
的共享内存,挂接到当前进程的进程地址空间。
参数
shmid:
- 由
shmget
接口,得到的共享内存标识符。
shmaddr:
- 要挂接到进程的内存地址(属于共享内存区域的),如果传递
nullptr
函数就会在共享内存的合适页码处与进程建立映射。
shmflg:
- 进程以什么方式挂接共享内存,这里我们只介绍
0
,以读写方式挂接。
返回值:
执行成功返回挂接的共享内存地址,失败返回(void *) -1
并设置errno
.
2.4 shmdt()函数
#include
#include
int shmdt(const void *shmaddr);
功能: 将共享内存从调用进程的地址空间分离(去挂接)
参数
shmaddr:
- 由
shmat
函数执行得到的地址。
返回值:
执行成功返回0
,失败返回-1
并设置errno
.
2.5 shmctl()函数
#include
#include
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
#include
#include
#include
#include
#include
#include
#include
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 共享内存特点
- 共享内存没有同步互斥之类的保护机制
- 共享内存时所有的进程间通信速度最快的(拷贝次数少)
- 共享内存内部数据,由用户自己维护
本篇内容就到这里了,上面介绍的好多知识,并非全部给出了演示,大家可以自己尝试。