分类: LINUX
2016-03-09 09:04:48
原文地址:IPC--共享内存 作者:zhenhuaqin
1.什么是共享内存
共享内存从字面意义解释就是多个进程可以把一段内存映射到自己的进程空间,以此来实现数据的共享以及传输,这也是所有进程间通信方式中最快的一种。共享内存是存在于内核级别的一种资源,在shell中可以使用ipcs命令来查看当前系统IPC中的状态,在文件系统中/proc目录下有对其描述的相应文件。
在系统内核为一个进程分配内存地址时,通过分页机制可以让一个进程的物理地址不连续,同时也可以让一段内存同时分配给不同的进程。共享内存机制就是通过该原理来实现的,共享内存机制只是提供数据的传送,如何控制服务器端和客户端的读写操作互斥,这就需要一些其他的辅助工具,例如,记录锁概念.
2.linux共享内存详解:
共享内存可以被描述成内存一个区域(段)的映射,这个区域可以被更多的进程所共享。这是IPC机制中最快的一种形式,因为它不需要中间环节,而是把信息直接从一个内存段映射到调用进程的地址空间。 一个段可以直接由一个进程创建,随后,可以有任意多的进程对其读和写。但是,一旦内存被共享之后,对共享内存的访问同步需要由其他 IPC 机制,例如信号量来实现。象所有的System V IPC 对象一样,Linux 对共享内存的存取是通过对访问键和访问权限的检查来控制的。
与消息队列和信号量集合类似,内核为每一个共享内存段(存在于它的地址空间)维护着一个特殊的数据结构shmid_ds,这个结构在include/linux/shm.h中定义如下:
/* 在系统中 每一个共享内存段都有一个shmid_ds数据结构. */
struct shmid_ds {
struct ipc_perm shm_perm; /* 操作权限 */
int shm_segsz; /* 段的大小(以字节为单位) */
time_t shm_atime; /* 最后一个进程附加到该段的时间 */
time_t shm_dtime; /* 最后一个进程离开该段的时间 */
time_t shm_ctime; /* 最后一次修改这个结构的时间 */
unsigned short shm_cpid; /*创建该段进程的 pid */
unsigned short shm_lpid; /*在该段上操作的最后一个进程pid */
short shm_nattch; /*当前附加到该段的进程的个数 */
/* 下面是私有的 */
unsigned short shm_npages; /*段的大小(以页为单位) */
unsigned long *shm_pages; /*指向frames -> SHMMAX指针数组 */
struct vm_area_struct *attaches; /* 对共享段的描述 */
};
shmid_ds结构初始化如表14-4所示。
表14-4 shmid_ds的初始化
shmid_ds结构数据 |
初 值 |
shmid_ds结构数据 |
初 值 |
shm_lpid |
0 |
shm_dtime |
0 |
shm_nattach |
0 |
shm_ctime |
系统当前值 |
shm_atime |
0 |
shm_segsz |
参数 size |
共享内存是存在于内核级别的一种资源,在shell中可以使用ipcs命令来查看当前系统IPC中的状态,在文件系统/proc目录下有对其描述的相应文件。
1) 函数shmget:可以创建或打开一块共享内存区。函数原型如下:
#include int shmget( key_t key, size_t size, int flag ); |
参数说明:函数中参数key用来变换成一个标识符,而且每一个IPC对象与一个key相对应。当新建一个共享内存段时,size参数为要请求的内存长度(以字节为单位)。
注意:内核是以页为单位分配内存,当size参数的值不是系统内存页长的整数倍时,系统会分配给进程最小的可以满足size长的页数,但是最后一页的剩余部分内存是不可用的。
当打开一个内存段时,参数size的值为0。参数flag中的相应权限位初始化ipc_perm结构体中的mode域。同时参数flag是函数行为参数,它指定一些当函数遇到阻塞或其他情况时应做出的反应。
2) 函数shmctl:可以对共享内存段进行多种操作,其函数原型如下:
#include int shmctl( int shm_id, int cmd, struct shmid_ds *buf ); |
参数说明:函数中参数sh_mid为所要操作的共享内存段的标识符,struct shmid_ds型指针参数buf的作用与参数cmd的值相关,参数cmd指明了所要进行的操作,其解释如表14-5所示。
表14-5 shmctl函数中参数 cmd详解
cmd的值 |
意 义 |
IPC_STAT |
取shm_id所指向内存共享段的shmid_ds 结构,对参数buf指向的结构赋值 |
IPC_SET |
使用buf指向的结构对sh_mid段的相关结 构赋值,只对以下几个域有作用,shm_perm. uid shm_perm.gid以及shm_perm.mode 注意此命令只有具备以下条件的进程才可以请求: 1.进程的用户ID等于shm_perm.cuid或者 等于shm_perm.uid 2.超级用户特权进程 |
IPC_RMID |
删除shm_id所指向的共享内存段,只有当 shmid_ds结构的shm_nattch域为零时,才 会真正执行删除命令,否则不会删除该段 注意此命令的请求规则与IPC_SET命令相同 |
SHM_LOCK |
锁定共享内存段在内存,此命令只能由超级用户请求 |
SHM_UNLOCK |
对共享内存段解锁,此命令只能由超级用户请求 |
3) 函数shmat:将一个存在的共享内存段连接到本进程空间,其函数原型如下:
#include void *shmat( int shm_id, const void *addr, int flag ); |
参数说明:函数中参数shm_id指定要引入的共享内存,参数addr与flag组合说明要引入的地址值,通常只有2种用法,addr为0,表明让内核来决定第1个可以引入的位置。addr非零,并且flag中指定SHM_RND,则此段引入到addr所指向的位置(此操作不推荐使用,因为不会只对一种硬件上运行应用程序,为了程序的通用性推荐使用第1种方法),在flag参数中可以指定要引入的方式(读写方式指定)。
说明:函数成功执行返回值为实际引入的地址,失败返回-1。shmat函数成功执行会将shm_id段的shmid_ds结构的shm_nattch计数器的值加1。
4) shmdt函数:当对共享内存段操作结束时,调用此函数,作用是将指定的共享内存段从当前进程空间中脱离出去。函数原型如下:
#include int shmdt( void *addr); |
参数说明:参数addr是调用shmat函数的返回值,函数执行成功返回0,并将该共享内存的shmid_ds结构的shm_nattch计数器减1,失败返回-1。
3.共享内存的处理过程
某个进程第一次访问共享虚拟内存时将产生缺页异常。这时,Linux 找出描述该内存的 vm_area_struct 结构,该结构中包含用来处理这种共享虚拟内存段的处理函数地址。共享内存缺页异常处理代码对shmid_ds 的页表项表进行搜索,以便查看是否存在该共享虚拟内存的页表项。如果没有,系统将分配一个物理页并建立页表项,该页表项加入 shmid_ds 结构的同时也添加到进程的页表中。这就意味着当下一个进程试图访问这页内存时出现缺页异常,共享内存的缺页异常处理代码则把新创建的物理页给这个进程。因此说,第一个进程对共享内存的存取引起创建新的物理页面,而其它进程对共享内存的存取引起把那个页加添加到它们的地址空间。
当某个进程不再共享其虚拟内存时,利用系统调用将共享段从自己的虚拟地址区域中移去,并更新进程页表。当最后一个进程释放了共享段之后,系统将释放给共享段所分配的物理页。
当共享的虚拟内存没有被锁定到物理内存时,共享内存也可能会被交换到交换区中。
4.共享内存使用注意事项
共享内存相比其他几种方式有着更方便的数据控制能力,数据在读写过程中会更透明。当成功导入一块共享内存后,它只是相当于一个字符串指针来指向一块内存,在当前进程下用户可以随意的访问。缺点是,数据写入进程或数据读出进程中,需要附加的数据结构控制,