共享内存
共享内存:将内存进行共享,它允许多个不相关的进程访问同一个逻辑内存,直接将一块裸露的内存放在需要数据传输的进程前,供进程使用。
因此,共享内存是效率最高的一种IPC通信机制,可以在多个进程间共享和传递数据,进程间需要共享的数据被放在共享内存区域 ,所有需要访问该共享内存的进程都要把该共享区域映射到本进程的地址空间中,因此所有进程都可访问共享内存的地址。但是,共享内存需要进程自己去维护,如同步、互斥等 。如进程1在读取共享内存的数据时,进程2却修改了共享内存的数据,这会导致数据混乱。因此共享内存属于临界资源,在某一时刻只能有一个进程对其操作(读写)。共享内存一般不能单独使用,而是配合信号量、互斥锁等协调机制,让各个进程在高效交换数据时,不会发生数据践踏、破坏等行为。
共享内存思想:进程间虚拟内存空间本来相互独立,不能相互访问,但是可以通过某种方式使得相同的一块物理内存多次映射到不同的进程虚拟空间中,相当于多个进程的虚拟内存空间部分重叠在一起。共享内存少了拷贝的操作,减少了系统开销,因此效率极高。
shmget():创建或获取共享内存
shmget()函数会创建或获取一个共享内存对象,并返回共享内存标识符。
cpp
#include <sys/ipc.h>
#include <sys/shm.h>
int shmget(key_t key, size_t size, int shmflg);
/*
key:共享内存的标识符
IPC_PRIVATE:创建一块新的共享内存
0:当shmflg参数设置了IPC_PRIVATE标志,则将创建一块新的共享内存
大于0的32位整数:根据shmflg参数来确定操作
size:要创建共享内存的大小,所有的内存分配操作都是以页为单位的,所有即使只申请一个字节的内存,内存也会分配一页。
shmflg:表示创建的共享内存的模式标志参数。标志 | mode
IPC_CREAT:如果内核中不存在关键字与key相等的共享内存,则新建一个共享内存;如果存在则返回此共享内存的标识符。
IPC_EXCL:如果内核中不存在关键字与key相等的共享内存,则新建一个共享内存;如果存在则报错
SHM_HUGETLB:使用"大页面"来分配共享内存,所谓的"大页面"指的是内核为了提高程序性能,对内存实行分页管理时,采用比默认尺寸(4KB)更大的分页,以减少缺页中断。 Linux 内核支持以 2MB 作为物理页面分页的基本单位。
SHM_NORESERVE:不在交换分区中为这块共享内存保留空间。
返回值:共享内存的ID。
*/
当调用shmget()函数失败时将产生错误代码:
EACCES:指定的消息队列已存在,但调用进程没有权限访问它
EEXIST:key指定的消息队列已存在,而msgflg中同时指定IPC_CREAT和IPC_EXCL标志
EINVAL:创建共享内存是参数size小于SHMMIN或大于SHMMAX
ENFILE:已达到系统范围内打开文件总数的限制
ENOENT:给定的key不存在任何共享内存,并且未指定IPC_CREAT
ENOMEM:内存不足,无法为共享内存分配内存
shmat():映射
shmat()函数是把共享内存区域对象映射到调用进程的地址空间。
cpp
#include <sys/types.h>
#include <sys/shm.h>
void *shmat(int shmid, const void *shmaddr, int shmflg);
/*
shmid:共享内存ID
shmaddr:如果不为NULL,则系统会根据shmaddr来选择一个合适的内存区域;如果为NULL,则系统会自动选择一个合适的虚拟内存空间地址去映射共享内存
shmflg:
SHM_RDONLY:以只读方式映射共享内存
SHM_REMAP:重新映射,此时shmaddr不能为NULL
NULLSHM:自动选择比shmaddr小的最大页对齐地址
返回值:共享内存的起始地址。
*/
共享内存的映射需注意:
共享内存只能以只读或可读写方式映射,无法以只写方式映射。
shmaddr参数一般设置为NULL,让系统自动地寻找合适的地址。当不为NULL时,SHMFLG必须设置为SHM_RND,系统将会选择比shmaddr小而又最大的页对齐地址(即为SHMLBA的整数倍)作为共享内存区域的起始地址。如果没有设置SHM_RND,那么shmaddr必须是严格的页对齐地址。
shmdt():解除映射
shmdt()函数是解除进程和共享内存间的映射。
cpp
#include <sys/types.h>
#include <sys/shm.h>
void shmdt(const void *shmaddr);
/*
shmaddr:映射的共享内存的起始地址
返回值:
执行成功:0
执行失败:-1,并将错误原因存于errno
*/
注意:该函数并不删除所指定的共享内存区,而只是将先前用shmat()函数映射好的共享内存脱离当前进程,共享内存还是存在于物理内存中。
shmctl():获取或设置属性
shmctl()函数用于获取或设置共享内存的相关属性。
cpp
#include <sys/ipc.h>
#include <sys/shm.h>
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
/*
shmid:共享内存标识符
cmd:
IPC_STAT:获取属性信息,放置到 buf 中。
IPC_SET:设置属性信息为 buf 指向的内容。
IPC_RMID:删除该共享内存。
IPC_INFO:获得关于共享内存的系统限制值信息。
SHM_INFO:获得系统为共享内存消耗的资源信息。
SHM_STAT:与 IPC_STAT 具有相同的功能,但 shmid 为该 SHM 在内核中记录所有 SHM 信息的数组的下标,因此通过迭代所有的下标可以获得系统中所有 SHM 的相关信息。
SHM_LOCK:禁止系统将该 SHM 交换至 swap 分区。
SHM_UNLOCK:允许系统将该 SHM 交换至 swap 分区。
buf:共享内存属性信息结构体指针,设置或者获取信息都通过该结构体
*/
一个SHM被交换至swap分区后如果被设置了SHM_LOCK,那么任何访问这个SHM的进程都将会遇到页错误。进程可以通过IPC_STAT后得到的mode来检测SHM_LOCKED信息。
cpp
struct shmid_ds {
struct ipc_perm shm_perm; /* 所有权和权限 */
size_t shm_segsz; /* 共享内存尺寸(字节) */
time_t shm_atime; /* 最后一次映射时间 */
time_t shm_dtime; /* 最后一个解除映射时间 */
time_t shm_ctime; /* 最后一次状态修改时间 */
pid_t shm_cpid; /* 创建者 PID */
pid_t shm_lpid; /* 后一次映射或解除映射者 PID */
shmatt_t shm_nattch; /* 映射该 SHM 的进程个数 */
...
};
struct ipc_perm {
key_t __key; /* 该共享内存的键值 key */
uid_t uid; /* 所有者的有效 UID */
gid_t gid; /* 所有者的有效 GID */
uid_t cuid; /* 创建者的有效 UID */
gid_t cgid; /* 创建者的有效 GID */
unsigned short mode; /* 读写权限 + SHM_DEST + SHM_LOCKED 标记 */
unsigned short __seq; /* 序列号 */
};