上接进程间通信(二)
四、共享内存
共享内存也称共享存储,共享内存就是指允许两个或多个进程共享一定的存储区。因为数据不需要客户进程和服务器进程之间复制,所以说共享内存时最快的一种IPC。使用共享内存时要掌握的唯一要点,就是多个进程之间对一定存储区的同步访问。若服务器进程正在将数据放入共享存储区,则在它做完这一操作之前,客户进程不应该去取这些数据。通常,信号量被用来实现对共享内存的同步访问。(当然记录锁也可用于这种场合)
内核为每个共享存储段设置了一个shmid_ds结构;
struct shmid_ds {
struct ipc_perm shm_perm; //权限控制
size_t shm_segsz; //共享存储段大小(字节为单位)
pid_t shm_lpid; //最后一个调用shmop()函数的进程
pid_t shm_cpid; //创建该段的进程的进程ID
shmatt_t shm_nattch; //当前附加到该段的进程个数
time_t shm_atime; //最后一个进程附加到段的时间
time_t shm_dtime; //最后一个进程离开该段的时间
time_t shm_ctime; //最后一个进程修改该段的时间
...
};
(按照支持共享存储段的需要,每种实现会在shmid_ds结构中增加其他成员。)
shmatt_t 类型定义为不带符号整型,它至少与unsigned short一样大。
为了获取一个共享存储标识,调用的第一个函数通常是shmget()。
#include<sys/shm.h>
int shmget(key_t key,size_t size,int flag); //成功返回共享存储的ID,出错返回-1
当创建一个新的共享存储段时,初始化shmid_ds结构的下列成员:
·ipc_perm结构中的mode成员按flag中的相应权限位设置。
·shm_lpid、shm_nattach、shm_stime、以及shm_dtime都设置为0.
`shm_ctime设置为当前时间。
·shm_segsz设置为请求的长度(size)。
参数size是该共享存储段的长度(单位:字节)。实现通常将其向上取为系统页长的整数倍。但是,若应用指定的size值并非系统页长的正数倍,那么最后一页的余下部分是不可能使用的。如果正在创建一个新段(一般在服务器进程中),则必须指定size。如果正在引入一个现存的段(一个客户进程),则将size指定为0。当创建一新段时,段内的内容初始化为0。
shmctl()函数对共享内存段执行多种操作。
#include<sys/shm.h>
int shmctl(int shmid,int cmd,struct shmid_ds *buf);//成功返回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提供了下列两种命令,UNIX中并没有。
SHM_LOCK 将共享存储段锁定在内存中。此命令只能超级用户执行。
SHM_UNLOCK 解锁共享存储段。此命令只能由超级用户执行。
一旦创建了一个共享存储空段,进程就可调用shmat将其连接到它的地址空间中。
#include<sys/shm.h>
void *shmat(int shmid,const void *addr,int flag);//若成功返回该共享存储段的指针,出错返回-1
共享存储段连接到调用进程的哪个地址与addr参数以及在flag中是否制定SHM_RND位有关。
·如果addr为0,则指定连接到由内核选择的第一个可用地址上。这是推荐的使用方式。
·如果addr非0,并且没有指定SHM_RND,则次段连接到addr指定的地址上。
·如果addr非0,并且指定了SHM_RND,则此段连接到(addr-(addr mod ulus SHMLBA))表示的地址上。SHN_RND命令的意思是“取整”。SHMLBA的意思是“低边界地址倍数”,它总是2的乘方。该算式是将地址向下取最近一个SHMLBA的倍数。
除非只计划在一种硬件上运行应用程序,否则不应该指定共享段所连接到的地址。所以一般应指定addr为0,以便由内核选择地址。
如果在flag中指定了SHM_RDONLY位,则以只读方式连接此段。否则以读写方式连接此段。
shmat()的返回值是该段所连接的实际地址,如果出错返回-1。如果shmat执行成功,那么内核将使该共享存储段shmid_ds结构中的shm_nattch计数器值加1。
当共享存储段的操作已经结束时,则调用shmdt()脱接该段。注意,这并不是从系统中删除其标识符以及其数据结构,该标识符仍然存在,直至某个进程(一般是服务器进程)调用shmctl()(带命令IPC_RMID)特地删除它。
#include<sys/shm.h>
int shmdt(void *addr); //成功返回0,出错返回-1
addr参数是以前调用shmat时的返回值。如果成功,shmdt将使其相关shmid_ds结构中的shm_nattch计数器值减1。