Linux进程通信-共享内存

来源:互联网 发布:重启mysql命令 编辑:程序博客网 时间:2024/06/14 22:27

1.共享内存

 

共享内存是指把共享数据放到共享内存区域,任何需要访问共享内存区域数据的进程都在自己的进程地址空间中开辟一个新的内存区域,用来映射共享内存数据的物理页面,所有需要访问共享内存区域的进程都要把该共享区域映射到本进程的地址空间中去,系统用shmget获得或创建一个IPC的共享内存区域,并返回相应的标识符,通过shmat将共享内存区域映射到进程的地址空间中去,每一个共享内存区域都对应shm文件系统上的一个文件,相当于映射shm文件系统上的同名文件到共享内存区域,shmdt是解除对共享内存区的映射,shmctl是对共享内存区的控制操作
共享内存作用是加快进程间的通信,共享内存的修改对进程是可见的,将共享内存区域映射到进程地址空间中去
而内存映射是加快进程访问文件/设备的速度
(1)系统V共享内存,不把数据写入磁盘,而mmap()映射普通文件可以指定何时把数据写入磁盘
V共享内存是通过特殊的文件系统shm中的文件实现的,文件系统shm的安装点在交换区上,重新引导后,数据会丢失
(2)系统V共享内存是随内核持续的,所有访问共享内存的进程都已经终止,它仍然存在,对内核引导前,对该共享内存区域的任何改写操作一直保留
(3)mmap()映射普通文件是随进程持续的,一定要注意何时终止进程

 

 

2. 接口函数

 

 int shmget(key_t key,size_t size,int shmflg);
//返回共享内存标识符
//如果共享内存区域不存在,创建一个共享内存区域
//第一个参数是IPC对象标识符,第二个参数是共享内存的大小,为页的整数倍 ,第三个参数是创建共享内存时的标志
void *shmat(int shmid,const void* shmaddr,int shmflg);
将共享内存映射到进程的地址空间中,返回的是共享内存的虚拟地址。
第一个参数是共享内存标识符
第二个参数是映射的共享内存的地址,为NULL,让内核自己去选择
第三个参数是本进程对该内存的操作模式如果为0,不设置任何权限限制,即具有读写权限
SHM_RDONLY 只读-将进程连接到只读段中去
SHM_RND 
SHM_REMAP、
参数 shmaddr 是共享内存的附加点,不同的取值有不同的含义:

* 如果为空,则由内核选择一个空闲的内存区;如果非空,返回地址取决于调用者是否给 shmflg 参数指定 SHM_RND 值,如果没有指定,则共享内存区附加到由 shmaddr 指定的地址;否则附加地址为 shmaddr 向下舍入一个共享内存低端边界地址后的地址 (SHMLBA ,一个常址)。
* 通常将参数 shmaddr 设置为 NULL 。

shmat() 调用成功后返回一个指向共享内存区的指针,使用该指针就可以访问共享内存区了,如果失败则返回 -1。
shmdt(const void* shmaddr);
//当进程结束使用共享内存区时,要通过函数shmdt断开与共享内存区的连接
shmctl是对共享内存的控制操作

int shmctl(int shmid,int cmd,struct shmid_ds* buf);
int shmid是共享内存的ID
int cmd是控制命令
IPC_STAT 取得共享内存状态
IPC_SET 改变共享内存状态
IPC_RMID 删除共享内存
struct shmid_ds* buf是一个结构体指针,IPC_STAT,IPC_SET可以取得状态或者是设置状态

 

 

 3. 应用实例

 

实例1: 两个进程共享内存读写

 

写进程-将数据写入到共享内存

 

#include <string.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <stdlib.h>
#include <sys/shm.h>
typedef struct {
char name[4];
int age;
}people;
int main(int argc,char* argv[]){
key_t key;
int i;
key=ftok("/etc/exports",10);//创建一个IPC对象标识符
int shmid;
people *p_map;
//返回共享内存标识符,如果共享内存区域不存在,则创建一个与key相对应的共享内存区域,否则返回与key对应的共享内存标识符
shmid=shmget(key,4096,IPC_CREAT|IPC_EXCL|0666);
//将共享内存区域映射到进程的地址空间中去
p_map=(people*)shmat(shmid,NULL,0);//让内核去选择一个地址,对操作权限没有限制,返回一个共享内存地址
char temp='a';
for(i=0;i<10;i++){
temp+=1;
memcpy((*(p_map+i)).name,&temp,1);
(*(p_map+i)).age=20+i;
}

//断开与共享内存区域的边接
shmdt(p_map);

}

 

 

读进程-从共享内存中读数据

 

#include <string.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/shm.h>
#include <sys/types.h>
#include <fcntl.h>
typedef struct{
char name[4];
int age;
}people;
int main(){
int i;
key_t key;
people *p_map;
char temp;
int shmid;
//获得IPC对象标识符
key=ftok("/etc/exports",10);
//返回共享内存标识符
shmid=shmget(key,4096,IPC_CREAT);

//将共享内存区域映射到进程的地址空间中去,返回共享内存区域的地址,利用这个指针进行操作
p_map=(people*)shmat(shmid,NULL,0);
for(i=0;i<10;i++){
printf("%s/n",(*(p_map+i)).name);
printf("%d/n",(*(p_map+i)).age);
}

//断开与共享内存的连接
shmdt(p_map);


}

 

实例2: 共享内存实现双向通信

 

 

发送方进程:

 


/**
多信进程访问共享内存,通过信号量来达到同步

**/

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/shm.h>
#include <sys/types.h>
#include <sys/sem.h>
#include <stdlib.h>
#include <signal.h>
int semid;
int shmid;
//定义用户空间内保存信号量集合信息的联合体
union semun{
int val;//初始化信号量集合中的信号量
struct semid_ds *buf;
unsigned short *array;
struct seminfo*_buf;
};

void myhandler(int signum,siginfo_t *si,void* vcontext);
int main(){
int ret;
struct sembuf getsem,setsem;//定义信号量操作的结构体
key_t semkey;//信号量的IPC对象标识符
key_t shmkey;//共享内存的IPC对象标识符
char* p_map;//返回共享内存的地址
//semget来创建一个信号量集合
semkey=ftok("/etc/fb.modes",0x0031);
if(semkey==-1){
perror("sem ftok error");
exit(1);
}

semid=semget(semkey,2,IPC_CREAT|IPC_EXCL|0666);//创建一个信号量集合中有两个信号量
if(semid==-1){
perror("semget error");
exit(1);
}
printf("semid=%d/n",semid);
struct sigaction act,oldact;
act.sa_sigaction=myhandler;
act.sa_flags=SA_SIGINFO|SA_RESETHAND;
sigaction(SIGINT,&act,&oldact);//注册信号处理函数
//初始化信号量集合中的这两个信号量
union semun seminit;
seminit.val=0;//将信号量集合中的信号量初始值设置为0
semctl(semid,0,SETVAL,seminit);
semctl(semid,1,SETVAL,seminit);

//设置信号量的操作
getsem.sem_num=1;
getsem.sem_op=-1;
getsem.sem_flg=SEM_UNDO;

setsem.sem_num=0;
setsem.sem_op=1;
setsem.sem_flg=SEM_UNDO;

//返回共享内存的IPC对象标识符
shmkey=ftok("/root/shm3.c",0x0029);
if(shmkey==-1){
perror("ftok error");
exit(1);
}
printf("shmkey=%d/n",shmkey);
shmid=shmget(shmkey,4096,IPC_CREAT|IPC_EXCL|0666);
printf("shmid%d/n",shmid);
if(shmid==-1){
perror("shm error");
exit(1);
}


p_map=(char*)shmat(shmid,NULL,0);//返回映射文件系统shm中文件到内存的对应的地址

while(1){

printf("Lucy:");
fgets(p_map,256,stdin);
semop(semid,&setsem,1);//释放资源
semop(semid,&getsem,1);
printf("Peter:%s",p_map);

}




}

void myhandler(int signo,siginfo_t *si,void* vcontext){//当我们按下ctrl+c时,应该删除信号量集合与共享内存
puts("delete operation");
semctl(semid,0,IPC_RMID);//这个函数可以有三个参数或者四个参数,当有四个参数时,第四个参数必须是semun联合体,初始化操作,这里使用的是3个参数
//第二个参数是信号量的索引,当为IPC_RMID时,第2个参数被忽略,在进程退出时,删除信号量集合
shmctl(shmid,IPC_RMID,NULL);
exit(1);

}

接收方进程:

 

 

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/shm.h>
#include <sys/sem.h>

int main(){
int semid;
int shmid;
int ret;
char *p_map;
key_t semkey,shmkey;
struct sembuf getsem,setsem;
semkey=ftok("/etc/fb.modes",0x0031);
if(semkey==-1){


perror("ftok error");
exit(1);
}
semid=semget(semkey,0,IPC_CREAT);//当第二个参数为0时,表示不关心信号量的数目
if(semid==-1){
perror("semid error");
exit(1);
}
printf("semid=%d/n",semid);

getsem.sem_num=0;
getsem.sem_op=-1;
getsem.sem_flg=SEM_UNDO;
setsem.sem_num=1;
setsem.sem_op=1;
setsem.sem_flg=SEM_UNDO;

 

shmkey=ftok("/root/shm3.c",0x0029);
if(shmkey==-1){
perror("ftok error");
exit(1);
}
printf("shmkey=%d/n",shmkey);
shmid=shmget(shmkey,4096,IPC_CREAT);//发送方已经创建了共享内存,所以接收方不需要创建,当为IPC_CREA时,如果已经创建与key关联的共享内存时,此时返回的是共享内存标识符
printf("shmid=%d/n",shmid);
if(shmid==-1){
perror("shm error");
exit(1);
}

p_map=(char*)shmat(shmid,NULL,0);//返回映射文件系统shm中文件到内存的对应的地址
//第3个参数shmflg,如果shmflg为SHM_RND,且shmaddr!=0,则共享内存会连接到shmaddr-(shmaddr%SHMLBA)的地址上,即连接到距离shmaddr最近的且是SHMLBA倍数的内存上
//如果不为SHM_RND,则连接到shmaddr指定的内存上
//如果SHM_RDONLY则会连接到只读段,否则连接的段是可被读写的

while(1){
semop(semid,&getsem,1);
printf("Lucy:%s",p_map);
printf("Peter:");
fgets(p_map,256,stdin);
semop(semid,&setsem,1);
}

return 1;
}


 执行结果:

 

[root@localhost ~]# gcc -o shm1 shm1.c
[root@localhost ~]# ./shm1
semid=1441803
shmkey=688616414
shmid6094878
Lucy:hello
Peter:how are you
Lucy:fine
Peter:thank you
Lucy:

 

[root@localhost ~]# ./shm3
semid=1441803
shmkey=688616414
shmid=6094878
Lucy:hello
Peter:how are you
Lucy:fine 
Peter:thank you

由于共享内存中可能存在着多个进程同时对共享内存的读写操作,因此,必须共享内存区域的互斥与同步操作,而通常将共享内存与信号量一起使用。来实现多进程对共享内存区域操作的同步与互斥。