转载:UNIX系统共享内存的应用编程技术

来源:互联网 发布:世纪互联 云计算 编辑:程序博客网 时间:2024/06/02 00:13

UNIX系统共享内存的应用编程技术

中国工商银行惠安县支行(362100) 庄文祥


 

共享内存(Shared Memory,下简称SHM)是指由一个进程创建并可与其他进程共享的内存
,UNIX系统中利用SHM可以实现进程间通信(IPC)。为了对系统共享资源(包括SHM)进行
访问的互斥控制,就要用到信号量(Semaphore)机制。系统要求进程在存取共享内存之前应
该先获得相应的信号量的控制。在编程中,共享内存常常要与信号量产生相应的关系。
,共享内存的创建与控制
UNIX
系统开发软件包提供了一系列的函数来实现共享内存的创建与控制。比如调用sh
mget
函数用于创建共享内存;调用shmctl函数用于控制共享内存。例程如下
:
#include
#include
#include
#include
#include
#include
#define SHMKEY Ox1688
#define NODENUM 20
int SHM_id;
struct shmid_ds shm_f;
if((SHM_id=shmget((ket_t)SHMKEY,NODENUM*sizeof(struct pidtos),IPC_CREAT
IPC
_EXCL
0660))<0)
prinf("Create Shared Memory Fail!");
shm_f.shm_perm.uid=220;/*
有效用户主标识
*/
shm_f.shm_perm.gid=110;/*
有效用户组标识
*/
shm_f.shm_perm.mode=0660;/*
操作许可
*/
if(shmctl(SHM_id,IPC_SET,&shm_f)<0)
printf("Set Shard Memory Error");
函数shmget的调用格式如下
:
int shmget(key,size,shmflg)
key_t key;/*SHM
关键值
*/
unsigned int size;/*SHM
长度
*/
int shmflg;/*
标志
*/
作用是创建共享内存或获取已创建的共享内存的标识符。实参shmflg的值必须是一个

整数,且规定下列内容:(1)访问权限,(2)执行方式,(3)控制字段。在创建SHM,该标志可设
IPC_CREAT(创建,值是01000)!IPC_EXCL(限制唯一创建,值是02000)!0660(访问权限)。成
功时返回创建的共享内存标识符,失败时返回-1。实参size的值应大于SHMMIN且小于SHMMA
X,
否则该系统调用失败。当shmflg=0,用于获取已存在的SHM的标识符。

函数shmctl的调用格式如下:
int shmctl(shmid,cmd,sbuf)
int shmid;/*
shmget获取的SHM的标识符
*/
int cmd;/*
将对SHM进行的控制命令
*/
struct shmid_ds*sbuf;/*
存放操作数
*/
作用是对共享内存进行由cmd指定的控制操作。cmd的值比较有用的是下面两个
:
IPC_SET
对于规定的shmid,设置有效用户和组标识及操作权限。

IPC_RMID
连同其相关的SHM段数据结构一起,删除规定的shmid。执行IPC_SETIPC_R
MID
的进程必须具有Owner/Creator或超级用户的有效用户标识。

系统创建的SHM仅仅是内存中一块连续的区域,本身并没有结构。用户进程对共享内存
不能直接进行存取,需要将共享内存附接在进程的数据段上,进程才能对其进行存取,实现方
法是:用户进程可以根据需要自行定义一个数据结构(pidtos),然后将共享内存的地址用
函数shmat赋值给指向结构pidtos的指针buf,相当于是给指针变量分配内存,buf指向共享
内存的起始处。然后就可用数组的方法,按数据结构的长度等分共享内存。这个过程可称之
为共享内存的"结构化"。例程如下:
struct pidtos{
char rhostname[10];
long pidsc;
}*buf;
int
if((buf=(struct pidtos*)shmat(SHM_idm,(char*)0,0))<0)
printf("Access SHM Error!");
for(i=0;i<NODENUM;I++)(
Strcpy(buf[i].rhostname,"");
buf[i].pidsc=0;
) /*
如果有必要,就初始化
SHM*/
shmdt((char*)buf);/*
拆接SHM,释放指针
buf*/
函数shmatshmdt的调用格式
:
char*shmat(shmid,shmaddr,shmflg)
int shmid;/*SHM
标识符
*/
char*shmaddr;/*
相当于偏移量
*/
int shmflg;/*
标志
*/
作用是将共享内存附接到进程的数据段上。实际上是将共享内存在主存中的地址
+shm
addr
后赋值给进程中的某一指针。shmaddr相当于偏移量,相对于共享内存在主存中的起始

地址。调用失败时返回(char*)-1.shmflg可取值为0,或者是SHM_RNDSHM_EDONLY中的一个
或两者的或。
int shmdt(shmaddr)
char *shmaddr;/*
采用shmat函数的返回值*/
作用是拆接共享内存段,成功时返回0,失败时返回-1

,信号量的创建与控制
为了支持并发进程对共享内存的互斥访问,保证共享内存中数据的一致性以及对共享内
存操作的完整性(原子性)。一个进程在SHM操作之前先要将SHM加锁,在操作完成后再将其解
,为此引入了信号量机制。信号量可用来控制一个共享资源,当然也包括共享内存。由于
许多应用需要使用一个以上的信号量,因此UNIX系统中具备有创建信号量组的能力。信号量
组可以含有一个或多个信号量。比如我们可以用一个信号量组中编号为0的信号量来控制一
个资源,再用编号为1的信号量来控制另一资源。当我们在编制程序时应该知道它们之间的
对应关系。信号量的创建与控制和共享内存的创建与控制很相似。如下面的例程:
#define SEMKEY 0x1680
int SEM_id;
struct semid_ds sem_f;
union semun{
int val;
struct semid_ds *buf;
ushort array[1];/*[]
中的值应根据信号量数目具体定义
*/
}arg;
if((SEM_id=semget((key_t)SEMKEY,1,IPC_CREAT
IPC_EXCL;0660)<0)
printf("Creat Semaphore Fail!");
arg.val=0;
if(semct(SEM_id,0,SETVAL,arg.val)<0)
printf("Access Semaphore Error!");
/*
信号量的值必须初始化。将编号为0的信号量的值初始化为
0*/
arg.buf=&sem_f;
sem_f.sem_perm.uid=220;/*
有效用户主标识
*/
shm_f.shm_perm.gid=110;/*
有效用户组标识
*/
shm_f.shm_perm.mode=0660;/*
操作许可
*/
if(semctl(SEM_id,IPC_SET,arg)<0)
printf("Set Semaphore Error!");
上述例程首先设置一个信号量组的关键值(SEMKEY),然后调用semgetr利用该关键值创

建只有一个信号量的信号量组。其中第三个参数的含义与函数shmget第三个参数的含义一
样。
函数semget的调用格式:
int semget(key,nsems,semflg)
key_t key;/*
信号量组的关键值
*/
int nsems;/*
信号量个数
*/
int semflg;/*
信号量组的创建标志
*/
用来创建一个信号量组,其中包含nsems个信号量,编号从0nsems-1;创建方式及访问

权限由semflg指定。成功时初始化相应的控制块信息,并返回创建的信号量组的标识符,
错时返回-1。当semflg=0时用于获取已存在的信号量的标识符。
函数semctl的调用格式:
int semctl(semid,semnum,cmd,arg)
int semid;/*
信号量组的标识符
*/
int semnum;/*
信号量的编号
*/
int cmd;/*
控制命令
*/
union semun{
int val;
struct semid_ds *buf;
ushort array[nsems];/*nsems
具体根据信号量的数目定义
*/
}arg;/*
操作数
*/
作用是对指定的信号量组或组中编号为semnum的信号量进行由cmd指定的控制操作。比

较有用的cmd命令如下:
cmd
作用

SETVAL
将信号量(semid,semnm)的当前值置为arg.val的值,常用于初始化某个信号量
IPC_SET
将信号量组的状态信息设置成arg.buf中的内容
IPC_RMID
删除信号量组标识符semid
对信号量的操作由semop函数来实现。当一个进程存取某一共享内存时,先查看相应信

号量的值,若为0,则表示SHM目前空闲,未被其他进程占用,那么就将信号量置1,表示SHM已被
占用,然后进程就可存取SHM;若信号量不为0,表明此时SHM已被其他进程占用了,于是这个进
程开始睡眠,等待其他进程释放SHM,即信号量为0,才被系统唤醒。如下面的例程:
static struct sembuf sem_lock[2]=(0,0,0,0,1,0);
if(semop(SEM_id,&sem_lock[0],2)<0)
printf("Access Semaphore Error!");/*
加锁*/其中结构sembuf由系统定义
:
struct sembuf{
int sem_num;/*
信号量编号
*/
int sem_op;/*
信号量操作数
*/
int sem_flg;}/*
操作标志
*/
数组sem_lock[2]可以看作是sem_lock[0]=(0,0,0)sem_lock[1]=(0,1,0)的组合。

上面的例程实际上是两步合为一步来做,是对同一个信号量(编号为0)做两次不同的操作。
经过这一步后,实际上可以看作是已经将SHM加锁,进程接下来就可以创建或存取SHM了。如
果其他进程此时想占用SHM,就必须等待。当操作完成后,为了其他进程可以存取SHM,就要释
放资源,将信号量的值重新清零,即一个解锁的过程。
static struct sembuf sem_unlock[1]=(0,-1,IPC_NOWAIT);
if(semop(SEM_id,&sem_unlock[0],1)<0)
printf("Access Semaphore Error!");/*
解锁*/
在此之前,信号量的值为1,小于等于-1的绝对值,于是就将信号量减1,重置为0,表示资

源已经释放。如果信号量的当前值为0,小于-1的绝对值,因为标志设为IPC_NOWAIT,就会立
即返回,此时因为信号量的值为0,表示资源空闲,也就无所谓解锁了。
显然对于共享内存的互斥控制采用的是VP算法。该算法还有另一种实现方式:用信号量
1表示资源空闲,信号量为0表示资源占用,正好与上面的做法相反。在这种情况下,应将信
号量的值初始化为1。例程如下:
static struct sembuf sem_lock[1]=(0,-1,0),
sem_unlock[1]=(0,1,0);
semop(SEM_id,&sem_lock[0],1);/*
解锁
*/
加锁时,如果信号量当前值为1(资源空闲),那么就减1,这时信号量的值变为0,表示资源

已经被占用。如果信号量当前值为0(资源占用),因为0小于-1的绝对值,于是进程开始睡眠
,
直到该信号量变为1,才被系统唤醒。解锁时,如果信号量当前值为0,那么就加一,这时信
号量的值为1,表示资源空闲。这种方式更加简单一些。
函数semop的调用格式:
int semop(semid,sops,nsops)
int semid;/*
信号量组标识符
*/
struct sembuf**sops;/*
指向信号操作量数缓冲区
*/
unsigned nsops;/*
操作的信号量信数
*/
功能是完成对标识符为semid的信号量组中若干信号量的操作,根据sops[i]sem_flg

操作数sops[i].sem_op,对编号为sops[i]·sem_num的信号量进行操作。一共要做nsops次这
样的操作,这个过程可称之为信号量的"块操作",类似批处理方法。
利用上面的三个函数,可以实现对共享资源的互斥访问,也可设计出具有复杂同步操作
要求的并发程序。不同进程对SHM的互斥访问可用图1表示:
@@46P16100.GIF;
1@@
,共享内存的实际应用

我们假设一个初始化程序(程序init)已经完成了SHM及信号量组的创建过程。这时不同
的进程可用相同的关键值(SHMKEY)去存取共享内存,互斥访问也用相同的信号量去控制,
键值是SEMKEY。我们假设进程(程序progl)是一个与远程主机连接的通信进程,其所要连接
的远程主机名由命令行参数argv[1]指定。进程将远程主机名和本身的进程号在SHM中登记
或更新。如:
SEM_id=semget((key_t)SEMKEY,1,0);
semop(SEM_id,&sem_lock[0],2);/*
加锁
*/
SHM_id=shmget((key_t)SHMKEY,NODENUM*sizeof(struct pidtos),0);
buf=(struct pidtos*)shmat(SHM_id,(char*)0,0);
for(i=0;i
if(srcmp(buf[i].rhostname,argv[1]==0) break;
if(buf[i].pidsc==0) break;
if(i==NODENUM) return(-1);
strcpy(buf[i].rhostname,argv[1]);
buf[i].pidsc=getpid();/*
操作部分结束
*/
shmdt((char *)buf);/*
拆接
SHM*/
semop(SEM_id,&sem_unlock[0],1);/*
解锁
*/
这样相同的一批进程带不同的命令行参数运行后,就在SHM中登记了一批远程主机名和

与之相连的通信进程的进程号。下面我们就可让进程(程序prog2)依据远程主机名(Remote
Host)
SHM中查出与该主机相连的通信进程的进程号,以实现对这些通信进程的管理。只要

将上面例程中的操作部分换成下面的程序段即可。
for(i=0;i<NODENUM;I++)
if(strcmp(buf[i].rhostname,RemoteHost)==0)
break;
if(i==NODENUM) pid=-1;else
pid=buf[i].pidsc;
总之,不同进程通过相同的关键值SHMKEYSEMKEY,来获取共享内存及信号量的标识符
,
然后使用标识符分别对它们进行操作。相同的关键值是实现不同进程共享资源的基础与前
提。
,有关安全性的问题
所谓安全性问题指的是在一个进程将共享内存加锁以后,由于某种原因该进程停止了运
,没能执行解锁操作,从而使SHM一直处于被锁状态,致使其他进程无法使用SHM。为了尽量
避免出现这种情况,我们可以将一些系统信号忽略掉。具体做法是先定义一些函数指针,
原来系统对这些信号的处理功能暂放函数指针中,然后设置对这些信号的处理方式为SIG_I
GN(
忽略)。如
:
int (*f1)();
int (*f2)();
int (*f3)();
int (*f4)();
f1=signal(SIGINT,SIG_IGN);
f2=signal(SIGTERM,SIG_IGN);
f3=signal(SIGQUIT,SIG_IGN);
f4=signal(SIGHUP,SIG_IGN);
在对SHM操作后,无论成功与否都要将对这些系统信号原来的处理功能恢复过来。如
:
signal(SIGINT,f1);
signal(SIGTERM,f2);
signal(SIGQUIT,f3);
signal(SIGHUP,f4);

 
原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 怀孕30周羊水多怎么办 孕30周羊水少怎么办 拔完智齿嗓子疼怎么办 神经功能性引起的胸闷怎么办 中风后说话不清怎么办 老人吃不进去饭怎么办 老人吃什么就吐怎么办 老人吃了就吐怎么办 老人吃饭噎着了怎么办 胃胀气嗝不出来怎么办 嗓子咽口水都疼怎么办 产后盆底肌肉松弛怎么办 2个月宝宝鼻塞怎么办 人淹死捞不上来怎么办 胶囊卡在胃里怎么办 药卡在气管里怎么办 胶囊药卡在气管怎么办 被胶囊卡在喉咙怎么办 药卡在食道里怎么办 胶囊黏在喉咙里怎么办 要一直卡在喉咙怎么办 胃老是往上反气怎么办 有口气憋在喉咙怎么办 肛裂伤口不愈合怎么办 肛裂口子不愈合怎么办 宝宝胃食道反流怎么办 去角质后脸发红怎么办 红烧肉做的太甜怎么办 红烧排骨太甜了怎么办 唱歌时嗓子有痰怎么办 一唱歌喉咙有痰怎么办 鼻子老是打喷嚏还流鼻涕怎么办 鼻涕流到喉咙里怎么办 鼻塞怎么办怎样让鼻通气 流清鼻涕嗓子疼怎么办 喉咙疼咳嗽有痰怎么办 扁桃体发炎痛得厉害怎么办 腭垂掉下来了怎么办 喉咙干有异物感怎么办 嗓子干有异物感怎么办 输液的时候手疼怎么办