共享内存(Shared Memory,下简称SHM)是指由一个进程创建并可与其他进程共享的内存块,在UNIX系统中利用SHM可以实现进程间通信(IPC)。为了对系统共享资源(包括SHM)进行访问的互斥控制,就要用到信号量(Semaphore)机制。系统要求进程在存取共享内存之前应该先获得相应的信号量的控制。在编程中,共享内存常常要与信号量产生相应的关系。
一. 共享内存的创建与控制
UNIX系统开发软件包提供了一系列的函数来实现共享内存的创建与控制。比如调用shmget函数用于创建共享内存;调用shmctl函数用于控制共享内存。例程如下:
#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且小于SHMMAX,否则该系统调用失败。当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_SET或IPC_RMID的进程必须具有Owner/Creator或超级用户的有效用户标识。
系统创建的SHM仅仅是内存中一块连续的区域,本身并没有结构。用户进程对共享内存不能直接进行存取,需要将共享内存附接在进程的数据段上,进程才能对其进行存取,实现方法是:用户进程可以根据需要自行定义一个数据结构(如pidtos),然后将共享内存的地址用函数shmat赋值给指向结构pidtos的指针buf,相当于是给指针变量分配内存,让buf指向共享内存的起始处。然后就可用数组的方法,按数据结构的长度等分共享内存。这个过程可称之为共享内存的"结构化"。例程如下:
struct pidtos{
char rhostname[10];
long pidsc;
}*buf;
int i;
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*/
函数shmat及shmdt的调用格式:
char *shmat(shmid,shmaddr,shmflg)
int shmid; /*SHM标识符*/
char *shmaddr; /*相当于偏移量*/
int shmflg; /*标志*/
作用是将共享内存附接到进程的数据段上。实际上是将共享内存在主存中的地址+shmaddr后赋值给进程中的某一指针。shmaddr相当于偏移量,相对于共享内存在主存中的起始地址。调用失败时返回(char*)-1。shmflg可取值为0,或者是SHM_RND和SHM_EDONLY中的一个或两者的或。
int shmdt(shmaddr)
char *shmaddr; /*采用shmat函数的返回值*/
作用是拆接共享内存段,成功时返回0,失败时返回-1。
#define SEMKEY 0x1680
int SEM_id;
struct semid_ds sem_f;
union semun{
int val;
struct semid_ds *buf;
unsigned short 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(semctl(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个信号量,编号从0至nsems-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;
unsigned short array[nsems]; /*nsems具体根据信号量的数目定义*/
}arg; /*操作数*/
作用是对指定的信号量组或组中编号为semnum的信号量进行由cmd指定的控制操作。比较有用的cmd命令如下:
SETVAL 将信号量(semid,semnm)的当前值置为arg.val的值,常用于初始化某个信号量。
IPC_SET 将信号量组的状态信息设置成arg.buf中的内容。
IPC_RMID 删除信号量组标识符semid 。
struct sembuf{
int sem_num; /*信号量编号*/
int sem_op; /*信号量操作数*/
int sem_flg; /*操作标志*/
};
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由系统定义*/
数组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,这时信号量的值为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次这样的操作,这个过程可称之为信号量的"块操作",类似批处理方法。
利用上面的三个函数,可以实现对共享资源的互斥访问,也可设计出具有复杂同步操作要求的并发程序。
三,共享内存的实际应用
我们假设一个初始化程序(程序init)已经完成了SHM及信号量组的创建过程。这时不的进程可用相同的关键值(SHMKEY)去存取共享内存,互斥访问也用相同的信号量去控制,关键值是SEMKEY。我们假设进程(程序progl)是一个与远程主机连接的通信进程,其所要连接的远程主机名由命令行参数argv[1]指定。进程将远程主机名和本身的进程号在HM中登记或更新。如:
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<NODENUM;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;
}
总之,不同进程通过相同的关键值SHMKEY及SEMKEY,来获取共享内存及信号量的标识符,然后使用标识符分别对它们进行操作。相同的关键值是实现不同进程共享资源的基础与前提。
四,有关安全性的问题
所谓安全性问题指的是在一个进程将共享内存加锁以后,由于某种原因该进程停止运行,没能执行解锁操作,从而使SHM一直处于被锁状态,致使其他进程无法使用SHM。为了尽量避免出现这种情况,我们可以将一些系统信号忽略掉。具体做法是先定义一些函数指针,将原来系统对这些信号的处理功能暂放函数指针中,然后设置对这些信号的处理方式为SIG_IGN(忽)。如:
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操作后,无论成功与否都要将对这些系统信号原来的处理功能恢复过来。如: 在对SHM操作后,无论成功与否都要将对这些系统信号原来的处理功能恢复过来。如:
在对SHM操作后,无论成功与否都要将对这些系统信号原来的处理功能恢复过来。如:
在对SHM操作后,无论成功与否都要将对这些系统信号原来的处理功能恢复过来。如:
在对SHM操作后,无论成功与否都要将对这些系统信号原来的处理功能恢复过来。如:
signal(SIGINT,f1);
signal(SIGTERM,f2);
signal(SIGQUIT,f3);
signal(SIGHUP,f4);
分享到:
相关推荐
IPC是一个C 库,它使用Windows上的共享内存提供进程间通信
进程间通信 IPC :共享内存机制
IPC库:在Linux/Windows上使用共享内存的高性能进程间通信。- mutouyun / cpp-ipc-源码
PHP与C++进行IPC命名管道共享内存通信,实现PHP网页与C++程序之间高效率跨进程通信。
该程序是我写的博客“一起talk C栗子吧(第九十六回:C语言实例--使用共享内存进行进程间通信二)”的配套程序,共享给大家使用
利用共享内存实现进程间的通信,可用于操作系统的教学。(原创)
基于共享内存的IPC同步算法的研究,廖昕,余波,介绍了基于共享内存的进程间通信的三种同步算法的具体实现流程和优缺点:单buffer同步算法需要资源少,算法简单,但只能满足小数据
IPC_STAT 得到共享内存的状态 IPC_SET 改变共享内存的状态 IPC_RMID 删除共享内存 struct shmid_ds *buf是一个结构体指针。IPC_STAT的时候,取得的状态放在这个结构体中。如果要改变共享内存的状态,用这个结构体...
之前用过Prosix版本的共享内存和信号量,一直没有实践System V版本的,主要是因为其信号量集的概念操作有些复杂,今天试着写一个SV版本的共享内存进程间通信,使用信号量同步。程序提供了几个简单的用于操作SV版本...
ipc-quartztime是一个基于共享内存,互斥锁和信号的进程间通信库。 ipc-quartztime用于进程之间的高速和高吞吐量通信。
管道,有名管道,信号,消息队列,信号量,共享内存。讲解及代码实例
host端FFT的IPC通信,开辟CMEM192M空间的2M空间为共享内存,2M空间分成四个512K分区。 1、App.c和App.h 2、AppCommon.h 3、cmem.c和cmem.h
两个控制台,一个动态库,加载后AB共享内存,可在IO线程中收发消息。这只是一个简单的demo,使用IPC中data_seg。
Linux下6种进程间通信的服务端和客户端实例。
本书从对Posix IPC和System V IPC的内部结构开始讨论,全面深入地介绍了4种IPC形式:消息传递(管道、FIFO、消息队列)、同步(互斥锁、条件变量、读写锁、文件与记录锁、信号量)、共享内存(匿名共享内存、具名...
qt不同进程间的共享内存通信实例,利用共享内存,完成了相对server和client之间的通信,包含两端的完整工程,有需要的可以参考
- IPC对象(消息队列、共享内存和信号量)存在于内核而不是文件系统中,由用户控制释放(用户管理IPC对象的生命周期),不像管道的释放由内核控制。 - IPC对象通过其标识来引用和访问,所有的IPC对象在内核空间中唯一性...
3.共享内存(Shared Memory) 4.信号量(Semaphore) 5.消息队列(Message Queues) 6.远程过程调用(Remote Procedure Calls, RPC) 7.Mailslot 8.报文传输协议(Message Transfer Protocol, MTP) 9.串行端口...
使用共享内存的实时应用程序的Pub-Sub IPC(进程间通信)库。 项目状态 状态:Alpha,编译并运行 目标听众 建筑学 项目结构 代码生成器 齿轮-d -e -o data_dict.hpp data_dict_hpp_gen.txt 齿轮-d -e -o data_dict....
获取已创建的共享内存时,shmflg不要用IPC_CREAT(只能用创建共享内存时的权限标识,如0640),否则在某些情况下,比如用ipcrm删除共享内存后,用该函数并用IPC_CREAT参数获取一次共享内存(当然,获取失败),则...