共享内存是3个XSI IPC机制中的第二个。如想了解XSI IPC可点击XSI IPC
它允许两个(或更多)不相关的进程访问同一个逻辑内存。共享内存是在两个正在运行的进程之间传递数据的一种非常有效的方式。大多数共享内存的具体实现,都把由不同的进程之间共享的内存安排为同一段物理内存。
由于数据不需要在客户进程和服务器进程之间复制,所以这是最快的一种IPC。
共享内存是由IPC为进程创建的一个特殊的地址范围,它将出现在该进程的地址空间中。其他进程可以将同一段共享内存连接到它们自己的地址空间中。如果某个进程向共享内存写入了数据,所做的的改动将立刻被可以访问同一段共享内存的任何进程看到。
共享内存为在多个进程之间共享和传递数据提供了一种有效的方式。由于它并未提供同步机制,所以我们通常需要用其他的机制来同步对共享内存的访问。通常,信号量被用来实现对共享内存访问的同步。
内核为每个共享内存存储段设置了一个shmid_ds结构:
struct shmid_ds{
struct ipc_perm shm_perm; /* 操作权限*/
size_t shm_segsz; /*段的大小(以字节为单位)*/
time_t shm_atime; /*最后一个进程附加到该段的时间*/
time_t shm_dtime; /*最后一个进程离开该段的时间*/
time_t shm_ctime; /*最后一个进程修改该段的时间*/
unsigned short shm_cpid; /*创建该段进程的pid*/
unsigned short shm_lpid; /*在该段上操作的最后1个进程的pid*/
shmatt_t shm_nattch; /*当前附加到该段的进程的个数*/
/*下面是私有的*/
unsigned short shm_npages; /*段的大小(以页为单位)*/
unsigned long *shm_pages; /*指向frames->SHMMAX的指针数组*/
struct vm_area_struct *attaches; /*对共享段的描述*/
};
按照支持共享内存的需要,每种实现都会在shmid_ds结构中增加其他成员。
shmatt_t 类型定义为不带符号整形,他至少与unsigned short一样大。下表列出了影响共享内存的系统限制:
下图中的箭头显示了每个进程的逻辑地址空间到可用的物理内存的映射关系。实际情况可能要比图中显示的更加复杂,因为可用内存实际上是由物理内存和以交换到磁盘上的内存页面混合组成。
#include
#include
#include
int shmget(key_t key, size_t size, int flag);
/*
key: IPC_PRIVATE 或 ftok的返回值
size: 共享内存区大小
flag: 同open函数的权限位,也可以用8进制表示法
函数返回值
成功: 共享内存段标识符---ID---文件描述符
出错: -1
*/
当新创建一个新共享内存段时,初始化shmid_ds结构的下列成员:
ipc_perm结构按照XSI IPC的IPC结构初始化可以点击XSI IPC查看
shm_lpid、shm_nattach、shm_atime、以及shm_dtiem都设置为0
shm_ctime设置为当前时间
shm_segsz设置为请求的长度
参数size是该共享内存的长度(字节)。实现通常将其向上取为系统页长的整数倍。但是,若应用指定的size值并非系统页长的整数倍,那些最后一页的余下部分是不可使用的。如果正在创建一个新的共享内存段(一般是在服务器进程中),则必须指定其size。如果正在引用一个现存的段(一个客户进程),则将size指定为0。当创建一个新段时,段内的内容初始化为0。
#include
int shmctl(int shmid ,int cmd ,struct shmid ds *buf)
shmid: 要操作的共享内存标识符
cmd : IPC_STAT(获取对象属性) ----实现了命令 ipcs -m
IPC_SET(设置对象属性)
IPC_RMID(删除对象) ---实现了命令 ipcrm -m
buf: 指定IPC_STAT/IPC_SET时用以保存/设置属性
返回值: 成功:0
出错:-1
cmd参数指定下列5种命令的一种,使其在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 从系统中删除该共享内存段。由于每个共享内存段有一个连接计数(shmid_ds结构中的shm_nattch字段),所以除非使用该段的最后一个进程终止或与该段脱节,否则不会实际删除该共享内存段。不管此段是否仍在使用,该段标识符立即被删除,所以不能再shmat与该段连接。此命令只能由下列两种进程执行:
其有效用户ID等于shm_perm.cuid或shm_perm.uid的进程
是具有超级用户的进程
Linux和Solaris提供了下列另外两种命令,但它们并非Single UNIX Specification的组成成分:
SHM_LOCK 将共享内存段锁在内存中。此命令只能由超级用户执行。
SHM_UNLOCK 解锁共享内存段。此命令只能由超级用户执行。
#incldue
void *shmat(int shmid,const void *addr,int flag)
第一个参数:ID号
第二个参数: 映射到的地址,NULL为系统自动完成的映射
第三个参数:flag :SHM_RDONLY共享内存只读;默认是0,表示共享内存可读写
返回值: 成功就是映射后的地址
失败返回NULL
共享内存段连接到调用进程的那个地址空间上与addr参数以及在flag中是否指定SHM_RND位有关。
如果addr为0,则此段连接到由内核选择的第一个可用地址上。这是推荐的使用方式。
如果addr非0,并且没有指定SHM_RND,则此段连接到addr所指定的地址上。
如果addr非0,并且指定了SHM_END,则此段连接到(addr------(addr mod ulus SHMLBA))所表示的地址上。SHM_RND命令的意思是“”取整”。SHMLBA的意思是“低边界地址倍数“,它总是2的乘方。该算式是将地址向下取最近1个SHMLBA的倍数。
除非只计划在一种硬件上运行应用程序,否则不应该指定共享内存段所连接到的地址。所以一般应指定addr为0,以便于由内核选择地址。
如果在flag中指定了SHM_RDONLY位,则以只读方式连接此段。否则以读写方式连接此段。
shmat的返回值是该段所连接的实际地址,如果出错则返回-1.如果shmat成功执行,那么内核将使该共享内存段shmid_ds结构中的shm_nattch计数器值加1。
#include
int shmdt(const void *shmaddr)
参数:shmaddr共享内存映射后的地址
返回值: 成功:0
出错:-1
addr参数是以前调用shmat时的返回值。如果成功,shmdt将使相关shmid_ds结构中的shm_nattch计数器减1。
当对共享内存段的操作已经结束时,则调用shmdt脱离该段。注意,这并不从系统中删除其标识符以及其数据结构。该标识符仍然存在,直至某个进程(一般是服务器进程)调用shmctl(带命令IPC_RMID)特地删除它。