System V共享内存
1.概念
-
共享内存允许多个进程之间共享物理内存的同一块区域,这块区域通常被称为共享内存段
-
使用该共享内存段需要将该共享内存段附加到进程的虚拟地址空间中,该共享内存段会成为进程用户空间内存段的一部分,内核并不参与,与管道和消息队列需要将数据送入内核内存相比,共享内存的速度是所有IPC对象中最快的
2.创建或打开一个共享内存段
shmget()创建一个共享内存段或获取一个已有段的标识符,新创建的段中的内容会被置为0
#include<sys/types.h>
#include<sys/shm.h>
int shmget(key_t key, size_t size, int shmglg);
//成功:返回共享内存段标识符 失败:返回-1
参数解释
-
key
用于生成共享内存段的键
-
size
如果是获取一个已有共享内存段,那么该参数不会对已有段产生任何效果,不过该参数仍然要求小于或等于段的大小
如果是创建一个新的段,那么size表示需要分配的字节数,该参数实际上会被提升至系统分页大小的整数倍
-
shmflg
用于指定新创建的内存段的权限,或者用来检查已有内存段的权限,还可以OR上下面的掩码
IPC_CREATE 如果没有已有段,创建一个新段 IPC_EXCL 与IPC_CREATE搭配使用,只用于创建新段,不会打开已有段 SHM_HUGETLB 创建一个使用hugepage的共享内存段,可以用来减少TLB表的条目数目 SHM_NORESERVE 待续
3.使用共享内存
shmat()系统调用将shmid标识的共享内存段附加到调用进程的虚拟地址空间
#include<sys/types.h>
#include<sys/shm.h>
void *shmat(int shmid, const void *shmaddr, int shmflg);
//成功:返回共享内存段的地址 失败:返回 (void *) -1
参数解释
-
shmid
共享内存段标识符
-
shmaddr
用于指定附加的位置,与shmflg的SHM_RND选项有关
-
如果shamddr为NULL, 那么段会被附加到一个由内核选择的合适的地址(推荐使用)
-
如果shamaddr不为NULL并且没有设置SHM_RND标志,那么shamaddr会被附加到指定的地址
如果该地址不是系统分页大小的整数倍,那么会发生EINVAL错误
-
如果shmaddr不为空并且设置了SHM_RND标志,那么段地址会被映射到
SHMLBA
(shared memory low boundary address指定的地址),该常量为系统分页大小的某个倍数,有助于提高系统性能
-
-
shmflg
值 描述 SHM_RDONLY 附加只读段 SHM_REMAP 替换shmddr所在的当前共享内存段,此时shmaddr不能为NULL SHM_RND 将shmaddr四舍五入为SHMLBA字节的倍数 shmdt()系统调用将共享内存段分离出其虚拟地址空间
#include<sys/type.h> #include<sys/shm.h> int shmdt(const void *shmaddr); //成功:返回0 失败:返回-1
-
通过fork()创建的子进程会继承其父进程附加的共享内存段
-
在进程使用exec()后,所有附加的共享内存段都会被分离,在进程终止之后共享内存段也会自动被分离
5.共享内存在虚拟内存中的位置
如果采用内核推荐的方式,那么一个典型的进程地址空间布局就会如图所示
附加共享内存段的虚拟地址从0x40000000开始,位于堆和栈之间未使用的区域,内存映射和共享库也被放置在这个区域
通过修改内核常量TASK_UNMAPPED_BASE就可以修改这个起始地址
如果采用了非内核推荐的方法附加内存段,那么该起始地址可以被放在小于TASK_UNMAPPED_BASE所在的地方
6.在共享内存中使用指针
由于共享内存很有可能被附加到多个进程的虚拟地址空间,所以其地址在不同虚拟地址中可能是不同的,所以不能够在共享内存中使用指向绝对地址的指针,应该使用相对偏移量
*p = target; //!wrong
*p = (target - baseaddr); target = baseaddr + *p; //right
7.共享内存控制操作
shmctl()系统调用在shmid标识的共享内存段上执行一组控制操作
#include<sys/types.h>
#include<sys/shm.h>
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
//成功:返回 0 失败: 返回-1
参数解释
-
shmid
共享内存标识符
-
cmd
cmd可以取如下值
-
IPC_RMID
标识这个共享内存段以便删除,如果当前没有进程附加该段,那么就会立即执行删除操作,否则在所有进程都与该段进行分离后删除
-
IPC_STAT
将与这个共享内存段关联的shmid_ds数据结构的一个副本复制到buf指向的缓冲区
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; /* number of current attaches */ ... };
ipc_perm结构如下
struct ipc_perm{ key_t __key; //get()调用提供 uid_t uid; gid_t gid; //IPC对象的拥有者的uid和gid,这两个字段可以使用ctl()调用进行更改 uid_t cuid; gid_t cgid; //IPC对象创建者的uid和gid,这两个字段不会改变 unsigned short mode; //IPC对象权限掩码 unsigned short __seq; //序列号 }
-
IPC_SET
使用buf指向的缓冲区来更新这个域共享内存相关的shmid_ds数据结构
加锁和解锁
-
SHM_LOCK
该标志可以将一个共享内存锁进RAM(内存
-
SHM_UNLCOK
该标志可以将一个共享内存解锁以允许共享内存分页被交换出内存
将共享内存锁进内存提高速度,避免因为页错误而延迟,当指定SHM_LOCK标志时不会立即将所有的分页锁进内存中,当共享内存分页第一次被进程附加到进程虚拟地址中(此时进入内存),他就会被所在内存中
-
SHM_LOCK
该标志可以将一个共享内存锁进RAM(内存
-
SHM_UNLCOK
该标志可以将一个共享内存解锁以允许共享内存分页被交换出内存
将共享内存锁进内存提高速度,避免因为页错误而延迟,当指定SHM_LOCK标志时不会立即将所有的分页锁进内存中,当共享内存分页第一次被进程附加到进程虚拟地址中(此时进入内存),他就会被所在内存中
-