一、共享内存概述
共享内存区是最快的可用IPC形式。它允许多个不相关的进程去访问同一部分逻辑内存。如果需要在两个运行中的进程之间传输数据,共享内存将是一种效率极高的解决方案。一旦这样的内存区映射到共享它的进程的地址空间,这些进程间数据的传输就不再涉及内核。这样就可以减少系统调用时间,提高程序效率。
共享内存是由IPC为一个进程创建的一个特殊的地址范围,它将出现在进程的地址空间中。其他进程可以把同一段共享内存段“连接到”它们自己的地址空间里去。所有进程都可以访问共享内存中的地址。如果一个进程向这段共享内存写了数据,所做的改动会立刻被有访问同一段共享内存的其他进程看到。
要注意的是共享内存本身没有提供任何同步功能。也就是说,在第一个进程结束对共享内存的写操作之前,并没有什么自动功能能够预防第二个进程开始对它进行读操作。共享内存的访问同步问题必须由程序员负责。可选的同步方式有互斥锁、条件变量、读写锁、纪录锁、信号量。
共享内存区分为System V共享内存区和Posix共享内存区。本节介绍System V共享内存区。
二、共享内存区结构
和其他XSI IPC一样,内核为每个共享存储段设置一个shmid_ds结构。
struct shmid_ds
{
struct ipc_perm shm_perm; /*operation perms操作权限*/
int shm_segez; /*size of segment段长度大小*/
time_t shm_atime; /*last attach time最近attach时间*/
time_t shm_dtime; /*last detach time最近detach时间*/
time_t shm_ctime; /*last change time最近change时间*/
unsigned short shm_lpid; /*pid of creator*/
unsigned short shm_cpid; /*pid of last operator*/
short shm_nattch; /*no.of current attaches*/
…….
};
其中ipc_perm是我们在XSI IPC里介绍的权限结构。
struct ipc_perm
{
key_t key;
ushort uid; /*owner euid and egid*/
ushort gid;
ushort cuid; /*creator euid and egid*/
ushort cgid;
ushort mode; /*lower 9 bits of shmflg*/
ushort seq; /*sequence number*/
};
三、共享内存区函数
shmget函数创建一个尚未存在的共享内存区,或者访问一个已存在的共享内存区。
1.
名称:: | shmget |
功能: | 获得一个共享存储标识符 |
头文件: | #inlcude <sys/shm.h> #include <sys/ipc.h> |
函数原形: | int shmget(key_t key,int size,int shmflg); |
参数: | ket键 size内存区大小 shmflg权限值 |
返回值: | 若成功则返回共享内存id,若出错则为-1 |
key为共享存储的外部键,通过ftok获得。
size是该共享存储段的长度。如果正在创建一个新段,则必须指定其size。如果正在引用一个现存的段,则将size指定为0。当创建一新段时,段内的内容初始化为0。
shmflg由九个权限标志构成,它们的用法和创建文件时使用的mode模式标志是一样的。当需要创建新的共享内存段时需要与IPC_CREAT标志按位或。设置IPC_CREAT标志并传递已存在的共享内存段不会产生错误。如果想创建一个读一无二的共享内存区可以与IPC_CREAT|IPC_EXCL按位或,这样如果系统以存在这个共享内存区,shmget函数就会报错。
还有两个选项:
SHM_R:可读
SHM_W:可写
System V共享内存区至少具有随内核持续性,因此程序结束该共享内存区还存在。
在共享内存段刚被创建的时候,任何进程还都不能访问它。为了建立这个共享内存段的访问渠道,必须由我们来把它连接到某个进程的地址空间。这项工作是由shmat函数完成的。
2、
名称:: | shmat |
功能: | 将共享内存段连接到他的地址空间 |
头文件: | #include <sys/ipc.h> #inlcude <sys/shm.h> |
函数原形: | void *shmat(int shm_id,void *shm_addr,int shmflg); |
参数: | shm_id标识码 shm_addr连接到的地址 shmflg 标志位 |
返回值: | 若成功则为指向共享存储的指针,若出错则为-1 |
shm_id是shmget返回的共享内存标识码。
shm_addr是把共享内存连接到当前进程去的时候准备放置它的那个地址。这通常是一个空指针,表示把选择共享内存出现处的地址这项工作交给系统去完成。
shmflg是一组按位或的标志。它的两个可能值是SHM_RND(这个标志与shm_addr一起控制着共享连接的地址)和SHM_RDONLY(它使连接的共享内存成为一个只读区间)。很少有需要控制共享内存连接的地址的情况,一般都是由系统替你挑选一个地址,否则就会使你的软件对硬件的依赖性过高。
shmat的返回值是该段所连接的实际地址,如果出错则返回-1。如果shmat成功执行,那么内核将该共享存储段shmid_ds结构的shm_nattch计算器加1。
缺省情况下,只要调用进程具有某个共享内存区的读写权限,它附接该内存区后就能够同时读写该内存区。只有flag参数指定SHM_RDONLY值时,它以只读方式访问。
当一个进程完成某个共享内存区的使用时,它可调用shmdt函数脱离与这个共享内存区的联系。
3、
名称:: | shmdt |
功能: | 脱接共享存储段 |
头文件: | #include <sys/ipc.h> #inlcude <sys/shm.h> |
函数原形: | int shmdt(void *shmaddr); |
参数: | shmaddr |
返回值: | 若成功则为0,若出错则为-1 |
当一个进程终止时,它的所有当前附接着的共享内存区都自动断接掉。注意本函数调用并不是从系统中删除其标识符以及其数据结构。该标识符仍然存在,直至某个进程调用shmctl特地删除它。
addr参数是以前调用shmat时的返回值。如果成功,shmdt将使相关shmid_ds结构中的shm_nattch计数其减1。
在创建子进程的时候注意以下几点:
(1)、子进程继承父进程挂载的共享内存
(2)、如果调用exec执行一个新的程序,则所有挂载的共享内存将被卸载
(3)、如果在某个进程中调用了exit()函数,所有挂载的共享内存将与当前进程脱离关系
4、
名称:: | shmctl |
功能: | 对共享存储段执行多种操作 |
头文件: | #include <sys/ipc.h> #inlcude <sys/shm.h> |
函数原形: | int shmctl(int shm_id,int command,struct shmid_ds *buf); |
参数: | shm_id共享内存标识码 command功能选择 buf指向shmid_ds结构的指针 |
返回值: | 若成功则为0,若出错则为-1 |
shmctl提供了对一个共享内存区的多种操作。
shmid为共享存储的ID,用于内部标识共享存储。
cmd参数指定下列命令中的一种,使其在shmid指定的段上执行。
IPC_STAT: 取此段的shmid_ds结构,并将它存放在由buf指向的结果中。
IPC_SET: 按buf指向结构中的值设置与此段相关结构中的下列三个字段:shm_perm.uid , shm_perm.gid和shm_perm.mode.此命令只对有效用户ID等于shm_perm.cuid或shm_perm.uid的进程和具有超级用户特权的用户有效。
IPC_RMID: 从系统中删除该共享存储段。因为每个共享存储段有一个连接计数,所以除非使用该段的最后一个进程终止或与该段脱节,否则不会世界上删除该段。
IPC_INFO: 查看共享内存的基本信息
SHM_LOCK :将共享存储段锁定在内存中。此命令只能用超级用户执行。
SGM_UNLOCK:解锁共享存储段。此命令只能用超级用户执行。
buf是一个指针,它指向一个保存着共享内存的模式状态和访问权限的数据结构。