进程的共享主存通信
【预备知识】
共享存储区为进程提供了直接通过主存进行通信的有效手段,不像消息缓冲机制那样需要系统提供缓冲,也不像pipe机制那样需要事先建立一个特殊文件,而是由通信双方直接访问某些共享虚拟储存空间。在Linux中,系统管理一组共享主存段控制块。通信进程在使用共享主存段以前,首先提出申请,系统为之分配存储空间并返回共享主存段标识号。一个共享段建立后,进程把它被附加到自己虚拟空间中。一个进程可以附加多个共享主存段。一个共享主存段一旦被附加到进程的虚拟空间后,对它的访问与其他虚拟地址的访问完全相同。但为了保证共享主存段数据的完整性,通信的进程之间要互斥地进行访问。当通信进程不再需要该共享主存段时,可使用命令将其与进程分离,从而使其从进程的虚空间删除。
- 共享主存段使用的数据结构
(1)共享主存段控制块(或共享主存段头)
每个共享主存段都有一个控制块,用来描述共享主存段的一些属性,共享主存段控制块定义在sys/shm.h中,其结构如下:
struct shmid_ds
{
struct ipc_perm shmperm; / * 共享主存段访问控制结构 * /
int shmsegsz; / * 共享段以字节为单位的长度 * /
struct ptentry * shmptbl; / * 共享页表始址 * /
ushort shmlpid; / * 最近执行共享段操作的进程标识 * /
ushort shmcpid; / * 创建共享段的进程标识 * /
ushort shmnattch; / * 当前附件段号 * /
ushort shmcnattch; / * 主存中的附加段号 * /
time_t shmatime; / * 最近一次附件操作的时间 * /
time_t shmdtime; / * 最近一次与进程分离操作的时间 * /
time_t shmctime; / * 最近一次修改时间 * /
}
为了便于管理,系统将维持的共享主存段组成一个表,共有SHMMNI=100个元素,其结构如下:
struct shmid_ds shmen[SHMMNI]; / * 共享段表 * /
其访问控制结构定义如下:
struct ipc_perm
{
key_t key;
ushort uid; /* owner euid and egid * /
ushort gid;
ushort cuid;
ushort cgid;
ushort mode; /*lower 9 bits of shmflg*/
ushort seq /*sequence number*/
};
(2)共享主存段的数据结构
每个共享主存段都对应一个页表和允许的存取权限,结构如下;
struct shmptds
{
int shmspte; /*开始也表项*/
int shmsflg; /*对共享段的读/写权限*/
}
每个进程最多允许6个共享主存段(SHMSEG=6).
- 申请一个共享主存段
参与通信的进程,通信前要先申请一个共享主存段,若是第一次申请,则要为其分配一个主存空间及页表,并对共享主存区控制块进行初始化,申请共享主存段调用语法如下:
# include<sys/ipc.h>
# include<sys/shm.h>
int shmget(key_t key,size_t size,int shmflg)
其中,key为共享主存段的关键字,size是共享主存段字节长度,shmflg为创建和访问标志。
返回值:成功时,为与key值相关的共享主存段的标识号,且大小是页对齐;失败时,为-1。
如果key的值为IPC_PRIVATE,或不为IPC_PRIVATE,创建的共享段与key无关,关键字由系统分配。
Shmflg由如下成分组成:
.IPC_CREAT,创建一个新段。如果该标志没有设置,将查找与key相关的段,且该段允许用户访问。
.IPC_EXCL,与IPC_CREAT一起使用,确保创建一个新的共享段。若该段已经存在,出错。
低9位为三类用户的访问方式的定义。
- 将共享段附加到申请通信的进程空间
对于已申请通信所需的共享段,进程需把它附加到自己的虚拟空间后才能对其进行读写,将共享段附加到申请通信的进程空间的函数调用语法:
#include < sys/sem.h >
void shmat ( int shmid,coid * shmadd,nt shmflg);
这里shmid是进程调用shmget后返回的共享段标识号;shmadd是给出的应附加到进程虚空间的地址;shmflg为允许对共享段的访问方式。
返回值:成功时为附加到进程地址空间的虚地址,失败时为-1。
连接到进程虚空间的地址规则为:
.shmadd为0,则将该共享段附加到系统选择的进程的第一个可用地址之后。
.shmadd为非0,如果shmflg指定了SHM_RND标志,则将该共享段附加shmadd取整后指定的地址上。
.shmadd为非0,如果shmflg未指定SHM_RND标志,则将该共享段附加shmadd指定的地址上。通常将shmadd指定为0,由系统安排连接地址。
4.将共享段与进程之间解除链接
当进程不再需要共享段时,将其从它的地址空间拆下,调用语法为:
#include < sys/sem.h >
int shmdt( void * shmaddr );
其中,shmaddr是共享段在进程地址空间的虚地址。返回值为0。
5.对共享段进行控制
#include < sys/sem.h >
int shmctl(int shmid,int cmd,struct_ds * buf);
返回值:成功时为0,失败时为-1。
系统调用shmctl根据控制命令cmd对共享段进行控制。cmd可能的取值有:
. IPC_STAT,将有关共享段的信息复制到具有shmid_ds结构的buf中;
. IPC_SET,用于用户和用户组,或对其存取权限进行修改,要求用户必须为超级用户或拥有者;
. IPC_RMID,用于标志删除共享段。在共享段与所有进行分离时,实际进行删除;
. IPC_LOCK,在Linux环境下,将共享段锁在主存中;
. IPC_UNLOCK,在Linux环境下,允许将共享段锁换出主存。
编程实现例<一>
【任务】
编程实现一个是进程向共享段写信息的例子。
【程序】
#include <sys/types.h>#include <sys/ipc.h>#include <sys/shm.h>#define SHMKEY 75#define K 1024int shmid;main(){int i,*pint;char *addr;extern char *shmat();shmid=shmget(SHMKEY,16*K,0777|IPC_CREAT);/*申请创建一个16K大小的共享存储段,其标识号为SHMKEY*/addr=shmat(shmid,0,0); /*缺省地址将共享段附加到系统指定位置*/printf(“addr ox%x\n”,addr);/*打印附加到进程地址空间的地址*/pint=(int *) addr;for (i=0;i<256;i++)*pint++=i; /*向共享主存段写入0~255个数*/pint=(int *)addr;*pint=256; /*共享段第一个字中写入信息长度256,以便接收进程读*/Pause(); /*暂停等待接收进程读*/}
修正后的代码:
【运行结果】
【分析】
第一个例子是一个进程向共享段写信息的例子,它使用了共享内存来实现进程间的通信。程序首先申请创建一个16K大小的共享存储段,标识号为SHMKEY,然后将共享段附加到系统指定位置,并打印附加到进程地址空间的地址。接着向共享主存段写入0~255个数,并在共享段第一个字中写入信息长度256,以便接收进程读取。最后暂停等待接收进程读
编程实现例<二>
【任务】
编程实现从共享段读信息的例子
【程序】
#include <sys/types.h>#include <sys/ipc.h>#include <sys/shm.h>#define SHMKEY 75#define K 1024int shmid;main(){int i,*pint;char *addr;extern char *shmat();shmid=shmget(SHMKEY,8*K,0777); /*由SHMKEY得到共享段标识号*/addr=shmat(shmid,0,0); /*将共享段与本进程相连*/pint=(int *) addr;while (*pint==0); /*若共享区无信息时,在此等待*/for (i=0;i<256;i++)printf(“%d\n”,*pint++);}
修正后的代码:
【运行结果】
【分析】
第二个例子是一个从共享段读信息的例子,同样使用了共享内存来实现进程间的通信。程序首先通过SHMKEY得到共享段标识号,然后将共享段与本进程相连。接着程序进入一个while循环,在共享区无信息时,在此等待。当共享区有信息时,将信息读取出来并打印出来。