Linux进程通信-内存映射

1. 内存映射

内存映射实际上是把文件映射到一块内存上,在进程中返回映射的地址,以后对该文件的操作就象操作内存一样,加快了文件/设备的访问速度。

与内存映射相关的另外一个概念就是共享内存,A,B进程共享内存的意思是将共享内存映射到A,B各自的进程地址空间中去,以后无论进程A或者是进程B对共享内存的读写,都彼此知道。

从功能上区分,内存映射是为了加快文件/设备的读写速度,而共享内存是加快多个进程间通信。

普通的进程读写文件,需要4次内核copy数据,而内存映射只需要2次,一次是把文件读到内存,另一次是把数据从内存写到文件中去。

 

2. 接口函数


void *mmap(void*start,size_t length,int prot,int flags,int fd,off_t offset);
int munmap(void* start,size_t length);
这两个函数分别是用于内存映射与解除内存映射

第一个参数是用户指定的文件被映射到进程地址空间的虚地址,如果为0,表示让内核自己选地址
第二个参数是映射的长度
第三个参数指定被映射对象的类型 
prot:
PROT_EXEC 表示映射的内存可执行
PROT_WRITE 表示映射的内存可写
PROT_READ 表示映射的内存可读
PROT_NONE 表示映射的页不能被访问
第四个参数是flags:
MAP_FIXED,MAP_SHARED,MAP_PRIVATE,MAP_ANONYMOUS
尽量不用MAP_FIXED。如果参数start指定的地址无法建立映射时就会放弃
MAP_SHARED表示与其它映射该文件/设备的进程共享映射,即对文件的修改在其它进程中也可见
MAP_PRIVATE 表示对创建一个专门的写时复制映射,即对映射的修改不会影响到被映射的文件,用户只能指定
MAP_SHARED与MAP_PRIVATE之一
MAP_ANONYMOUS是一个匿名映射
第五个参数是文件标识符
第六个参数是被映射文件的起始地址

这个函数返回的是被映射到进程地址空间的地址
munmap;
第一个参数被映射到进程地址空间的地址
第二个参数映射的长度
如果映射的长度大于文件的长度,大于的部分页会被清0

 

3. 内存映射例子

实例1: 两个进程通过内存映射进行通信

 

写进程:


#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/mman.h>
#include <sys/types.h>
typedef struct {
char name[4];
int age;

}people;//定义一个people结构体

int main(int argc,char*argv[]){
//映射一个文件到内存
int fd;
int i;
people *p_map;//用于返回值,返回映射的虚地址
fd=open(argv[1],O_CREAT|O_RDWR|O_TRUNC,0777);//将文件长度清0
lseek(fd,sizeof(people)*5-1,SEEK_SET);
write(fd,"",1);
//上面的两条语句设置了文件的长度为sizeof(people)*5
//lseek找到位置,然后write再写内容,这时,文件指针已经到了sizof(poeple)*5-1的位置处,所以文件的长度就会变为sizeof(people)*5

p_map=(people*)mmap(NULL,sizeof(people)*10,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);
//文件映射到内存,start为NULL,让内核去选择映射的物理地址,length,映射的长度,读与写,多个进程共享映射,文件描述符,文件映射的起始位置为0,即文件头开始

close(fd);
char temp='a';
for(i=0;i<10;i++){
temp+=1;
memcpy((*(p_map+i)).name,&temp,2);
(*(p_map+i)).age=20+i;
}

printf("initialize over/n");
sleep(10);
munmap(p_map,sizeof(people)*10);
//解除映射
printf("umap ok/n");

}

 

读进程:

 

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/mman.h>
#include <unistd.h>
typedef struct{
char name[4];
int age;
}people;
int main(int argc,char*argv[]){
int fd,i;
people *p_map;
fd=open(argv[1],O_CREAT|O_RDWR,0777);
p_map=mmap(NULL,sizeof(people)*10,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);
close(fd);
for(i=0;i<10;i++){
printf("name:%s age %d/n",(*(p_map+i)).name,(*(p_map+i)).age);
}

munmap(p_map,sizeof(people)*10);//解除映射

}

 

执行结果:


在map_normalfile1输出initialize over 之后,输出umap ok之前,在另一个终端上运行map_normalfile2 /tmp/test_shm,将会产生如下输出

name: b age 20; name: c age 21; name: d age 22; name: e age 23; name: f age 24;
name: g age 25; name: h age 26; name: I age 27; name: j age 28; name: k age 29;


在map_normalfile1 输出umap ok后,运行map_normalfile2则输出如下结果:

name: b age 20; name: c age 21; name: d age 22; name: e age 23; name: f age 24;
name: age 0; name: age 0; name: age 0; name: age 0; name: age 0;

可以得出以下结论:

(1)内存映射的长度不会超过文件的长度大小,即映射不能改变文件本身的大小

(2)可用于进程通信的地址空间大体上受限于被映射的文件大小

(3)对文件的映射,对返回地址的访问是对某一个内存区域的访问,暂时脱离了磁盘的影响,对返回地址的操作在内存中才有意义,只有在munmap或msync才把内存中的内容写回到文件
(4)可用于进程地址通信的空间大小不超过文件大小+一个页面,即使是一个字节,也会分配一个页面来存取

 

实例2: 父子间的匿名内存映射

 


/**
匿名内存映射
匿名内存映射主要是用于具有亲缘关系的进程之间的共享内存
即不给出文件标识符
**/

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/mman.h>
typedef struct {

char name[4];
int age;
}people;
int main(){
int childpid;
int fd=-1;//用于匿名文件映射
int i;
people *p_map;
p_map=(people*)mmap(NULL,sizeof(people)*10,PROT_READ|PROT_WRITE,MAP_SHARED|MAP_ANONYMOUS,-1,0);
//内存映射
char temp;
if(fork()==0){
sleep(2);
for(i=0;i<5;i++){
printf("the age is %d:/n",(*(p_map+i)).age);

}

(*p_map).age=100;
munmap(p_map,sizeof(people)*10);
exit(0);
}

temp='a';
for(i=0;i<5;i++){
temp+=1;
memcpy((*(p_map+i)).name,&temp,2);
(*(p_map+i)).age=20+i;
}
sleep(5);
printf("parent read: the first age is:%d/n",(*p_map).age);
munmap(p_map,sizeof(people)*10);
printf("umap ok/n");
}

 

 

执行结果:


the age is 20:
the age is 21:
the age is 22:
the age is 23:
the age is 24:
parent read: the first age is:100
umap ok

 

实例3:对映射地址空间的访问

 


#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/mman.h>
typedef struct{
char name[4];
int age;
}people;

int main(int argc,char* argv[]){
int fd,i;
int pagesize,offset;
people* p_map;
//得到pagesize的大小
pagesize=sysconf(_SC_PAGESIZE);
printf("pagesize is:%d/n",pagesize);
fd=open(argv[1],O_CREAT|O_RDWR|O_TRUNC,0777);
//文件的大小为pagesize*2-99
lseek(fd,pagesize*2-100,SEEK_SET);
write(fd,"",1);
offset=0;
p_map=(people*)mmap(NULL,pagesize*3,PROT_READ|PROT_WRITE,MAP_SHARED,fd,offset);
close(fd);
for(i=1;i<10;i++){
printf("%d,%d/n",pagesize/sizeof(people)*i,sizeof(people));
(*(p_map+pagesize/sizeof(people)*i-2)).age=100;
printf("access page %d over/n",i);
(*(p_map+pagesize/sizeof(people)*i-1)).age=100;
printf("access page %d edge over,now begin to access page %d/n",i,i+1);
(*(p_map+pagesize/sizeof(people)*i)).age=100;
printf("access page %d over/n",i+1);

}

munmap(p_map,sizeof(people)*10);
}

 

运行结果:

 


pagesize is:4096
512,8
段错误
[root@localhost ~]# ./memb xx
pagesize is:4096
512,8
access page 1 over
access page 1 edge over,now begin to access page 2
access page 2 over
1024,8
access page 2 over
access page 2 edge over,now begin to access page 3
总线错误

 

 

 

关于段错误与总线错误:

段错误:是由于访问非法内存引起的,内核会发送SIGSEGV信号

总线错误:访问的内存并不是非法内存,由于数据没有对齐引起的,会发送SIGBUS信号

 

实例4: 利用内存映射实现进程间的双向通信

 

 

发送方:

 

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/mman.h>
#include <sys/sem.h>
#include <sys/stat.h>
#include <stdlib.h>
union semun{
int val;
struct semid_ds *buf;
unsigned short *array;
struct seminfo *_buf;
};
int main(){
char* p_map;//用于返回内存映射的地址
key_t semkey;
struct sembuf getsem,setsem;//定义两个sembuf结构,有关信号量的操作
int ret;
int semid;//返回信号量标识符
union semun seminit;//进行信号量的初始化
semkey=ftok("/etc/issue",0x000018);
if(semkey==-1){
perror("ftok error");
exit(1);
}

semid=semget(semkey,2,IPC_CREAT|IPC_EXCL|0666); //如果与key相关联的信号量集合不存在,则返回一个信号量集合的标识符,里面有两个信号量
if(semid==-1){
perror("semget error");
exit(1);
}

//进行信号量集合的初始化
seminit.val=0;//即最开始信号量集合中的信号量个数为0
semctl(semid,0,SETVAL,seminit);//对索引为0的信号量初始化,即第1个信号量初始化
semctl(semid,1,SETVAL,seminit);//对索引为1的信号量初始化,即第2个信号量初始化

//下面设置一个信号量操作
getsem.sem_num=1;//信号量序号,表示信号量集合中的第2个信号量
getsem.sem_op=-1;//sem_op<0,表示申请信号量
getsem.sem_flg=SEM_UNDO;//表示如果进程终止时,信号量值还没变,那么由内核去收回资源

setsem.sem_num=0;//表示信号量集合中的第1个信号量
setsem.sem_op=1;//释放资源
setsem.sem_flg=SEM_UNDO;


//利用内存映射提高读写文件速度
char zero[4096];
const char* filename="mmap";
memset(zero,0,4096);
int fd=open(filename,O_RDWR|O_CREAT);
if(fd==-1){
semctl(semid,0,IPC_RMID);
exit(1);
}
write(fd,zero,4096);//初始化文件大小
p_map=(char*)mmap(NULL,4096,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);
while(1){
printf("Lucy:");
fgets(p_map,256,stdin);
p_map[strlen(p_map)-1]='/0';
ret=semop(semid,&setsem,1);//对第1个信号量开始操作,表示释放资源
if(ret==-1){
perror("semop error:");
}

ret=semop(semid,&getsem,1);//表示对第0个信号量进行操作,申请资源
if(ret==-1){
perror("semop error");
}

printf("Peter:%s/n",p_map);//打印出peter说的话


}

 

 接收方:

 

#include <stdio.h>
#include <sys/mman.h>
#include <sys/wait.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/sem.h>
#include <fcntl.h>
#include <string.h>
int main(){
char *p_map;//返回内存映射的空间地址
int fd;
int semid;
key_t semkey;
const char* filename="mmap";
struct sembuf getsem,setsem;
semkey=ftok("/etc/issue",0x000018);
if(semkey==-1){
perror("ftok error/n");
}
semid=semget(semkey,0,0);
if(semid==-1){
perror("semget error");

}

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;

fd=open(filename,O_RDWR|O_CREAT);


p_map=(char*)mmap(0,4096,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);

while(1){
semop(semid,&getsem,1);
printf("Lucy:%s/n",p_map);
printf("Peter:");
fgets(p_map,256,stdin);
p_map[strlen(p_map)-1]='/0';
semop(semid,&setsem,1);//释放占有的资源

}

}

 

内存映射与共享内存的区别:

 

(1)内存映射是将普通的文件映射到内存中去,而共享内存实际上是映射特殊的文件系统shm中的文件到内存中去,这个文件系统的安装点在交换区上,重新引导后,内容会丢失,内存映射需要将内存的内容写回到文件,而共享内存绝不将内存内容写回到文件

(2)内存映射主要是为了加快文件/设备的读写速度,而共享内存是加快多个进程间的通信,它是多进程间通信的最快方式

 

 

关于内存映射就总结到这了。

 

 

 

 

 

 

 

阅读更多
个人分类: LINUX
想对作者说点什么? 我来说一句

没有更多推荐了,返回首页

加入CSDN,享受更精准的内容推荐,与500万程序员共同成长!
关闭
关闭