写在前面:
在进程间通信中,有一种方式内存映射。内存映射也是进程间通信的方式之一,其效率高,可以直接对内存进行操作。本节我们对内存映射进行学习,并结合案例进行实践。
1、基本理论
内存映射:是将磁盘文件中的数据映射到内存,用户通过修改内存就能修改磁盘文件。
那通过内存映射如何实现进程之间的通信呢?简单来说就是将同一个文件存储映射部分分别映射到不同的进程中,两个进程通过改变文件的内容(读写内存)来实现通信,不必再使用read和write函数等系统调用,加快文件的读取和写入。
内存映射相关函数
#include <sys/mman.h>
void *mmap(void *addr, size_t length, int prot, int flags,int fd, off_t offset);//用于文件或者设备映射到内存中去
int munmap(void *addr, size_t length);//释放内存映射
1、mmap函数
涉及头文件:#include <sys/mman.h>
函数原型:void *mmap(void *addr, size_t length, int prot, int flags,int fd, off_t offset);
功能:将一个文件或者设备的数据映射到内存中去;
参数: - void *adder:NULL;由内核指定。
-lenth:要映射的内存的长度,这个值不能为0;建议使用文件的长度;
获取文件的长度:stat lseek;
没有达到分页的大小,按照分页大小进行,所以是分页的整数倍。
所以一般情况下,申请的内存区域大于等于文件的大小。
-prot:对申请的内存区的操作权限;
PROT_EXEC 可执行的权限
PROT_READ 读的权限
PROT_WRITE 写的权限
PROT_NONE 没有权限
要操作映射内存,必须要读的权限-PROT_READ、PROT_READ|PROT_WRITE
-flags:
MAP_SHARED:映射区的数据会自动和磁盘文件进行同步,如果要完成进程间通信,必须设置这个选项;
MAP_PRIVATE:不同步,内存映射区的数据改变了,对原来的文件不会修改,会重新创建一个新的文件。copy on write.
-fd:
需要操作的文件描述符,通过open得到,打开的是一个磁盘文件。
注意:文件的大小不能为0;
open指定的权限,不能和prot冲突;
port:PROT_READ; open:只读/读写;
port:PROT_READ|PROT_WRITE; open:读写;
总的来说:port的权限要小于open的权限,必须要有读的权限。
-offset:偏移量,一般不用,必须要指定的是4k的整数倍。0表示不偏移。
返回值:
-成功:返回创建内存的首地址;
-失败:返回MAP_FAILED (void *)-1;
2、munmap函数
涉及头文件:#include<sys/mman.h>
函数原型:int munmap(void *addr, size_t length);
功能:释放内存映射;
参数:
void *addr:释放的内存的首地址;
length:要释放的内存的大小,要和mmap函数中的length的值一样。
2、案例一:通过内存映射实现父子进程间的通信
实现方案:
在没有子进程的时候,通过唯一的父进程创建内存映射区;
有了内存映射区,再创建子进程;
父子进程共享内存映射区;
实现流程:
1、打开一个文件;
2、获取文件大小(用于mmap函数的参数);
3、创建内存映射区;fork()之后父子进程共享内存映射区。
4、创建子进程;
父进程读取数据,子进程发送数据;(因为子进程发送完成后,可以被父进程进行回收,避免僵尸进程的产生)。
#include <stdio.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <sys/mman.h>
#include <string.h>
#include <stdlib.h>
int main()
{
//1、打开一个文件
int fd = open("text.txt",O_RDWR);
//2、获取大小
int size=lseek(fd,0,SEEK_END);
//3、创建内存映射区
void *ptr = mmap(NULL,size,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);
if(ptr==MAP_FAILED)
{
perror("mmap");
exit(0);
}
//4、创建子进程
pid_t pid = fork();
if(pid>0)
{
wait(NULL);
//父进程:
char buf[64];
strcpy(buf,(char *)ptr );
printf("read data:%s\n",buf);
}
else if(pid==0)
{
//子进程:
strcpy((char *)ptr,"nihao,sun!!");
}
//关闭内存映射区:
munmap(ptr,size);
return 0;
}
运行结果:
3、案例二:通过内存映射实现没有关系进程之间的通信
实现方案:
准备一个大小不为0的磁盘文件;
进程1 通过磁盘文件(与进程2的文件相同)创建内存的映射区;
得到一个操作这块内存的指针。
进程2 通过磁盘文件(与进程1的文件相同)创建内存映射区;
得到一个操作这块内存的指针。
使用内存映射区进行通信。
注意:内存映射区通信,没有阻塞;
进程1:写内容
#include <stdio.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <sys/mman.h>
#include <string.h>
#include <stdlib.h>
int main()
{
// 1、准备一个磁盘文件。
// 2、通过磁盘文件创建内存的映射区;
int fd = open("test.txt",O_RDWR);
int size= lseek(fd,0,SEEK_END);
void * prt=mmap(NULL,size,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);
if( prt == MAP_FAILED)
{
perror("mmap");
exit(0);
}
strcpy((char *)prt,"sixsixsix");
munmap(prt,size);
return 0;
}
进程2:读文件
#include <stdio.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <sys/mman.h>
#include <string.h>
#include <stdlib.h>
int main()
{
// 1、准备一个磁盘文件。
// 2、通过磁盘文件创建内存的映射区;
int fd = open("test.txt",O_RDWR);
int size= lseek(fd,0,SEEK_END);
void * prt=mmap(NULL,size,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);
if( prt == MAP_FAILED)
{
perror("mmap");
exit(0);
}
char buf[64];
strcpy(buf,(char *)prt);
printf("read data:%s\n",buf);
munmap(prt,size);
return 0;
}
运行结果:
4、内存映射的注意事项
1、如果对mmap的返回值(ptr)做++操作,munmap能够成功吗?
void * ptr =mmap(...)
可以对其进行++操作,但是不建议因为释放的时候,需要把最开始的地址记录下来。
2、如果open时,O_RDONLY,mmap时prot参数指定 PROT_READ|PROT_WRITE 会怎么样?
错误,会返回宏MAP_FAILED
open()权限建议和prot参数保持一致,更准确的说open()的权限要大于prot参数的权限;
3、如果文件偏移量为1000会怎么样?
偏移量必须是4k的整数倍,否则 错误,会返回宏MAP_FAILED
4、mmap什么情况下会调用失败?
-第二个参数:length=0;
-第三个参数:prot权限
-只是指定了写权限;
-prot参数权限为:PROT_READ|PROT_WRITE,第5个参数文件描述符fd(通过open函数打开时,O_RDONLY、O_WRONLY)
5、可以open的时候,O_CREAT一个新文件来创建映射区。
可以的,但是创建的文件的大小如果为0,肯定不行;
-lseek()
-truncate()
进行扩展;
6、mmap后关闭文件描述符,对mmap映射有没有影响?
int fd=open("xxx");
mmap(,,,,fd,0);
close(fd);
映射区还存在,创建映射区的fd被关闭,没有任何影响;
7、对ptr进行越界操作会怎么?
void *ptr=mmap(NULL,100..);
4k
越界操作,操作的是非法内存,-段错误。
5、使用内存映射实现文件的拷贝
使用内存映射实现文件拷贝的功能
/*
1、对原始的文件进行内存映射;
2、创建一个新的文件,新文件进行扩展;
3、把新文件的数据映射到内存中;
4、通过内存拷贝,将第一个文件的内存数据拷贝到新的文件内存中;
5、释放资源;
*/
#include <stdio.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
int main()
{
// 1、对原始的文件进行内存映射;
int fd =open("english.txt",O_RDWR);
if(fd==-1)
{
perror("open");
exit(0);
}
//获取原始文件的大小
int len=lseek(fd,0,SEEK_END);
// 2、创建一个新的文件,新文件进行扩展;
int fd1 =open("cpy.txt",O_RDWR|O_CREAT,0664);
if(fd1==-1)
{
perror("open");
exit(0);
}
//对新创建的文件进行拓展
truncate("cpy.txt",len);
write(fd," ",1);
//3、分别做内存映射
void * ptr= mmap(NULL,len,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);
void * ptr1= mmap(NULL,len,PROT_READ|PROT_WRITE,MAP_SHARED,fd1,0);
if(ptr == MAP_FAILED)
{
perror("mmap");
exit(0);
}
if(ptr1 == MAP_FAILED)
{
perror("mmap");
exit(0);
}
//内存拷贝
memcmp(ptr1,ptr,len);
//释放资源
munmap(ptr1,len);
munmap(ptr,len);
close(fd1);
close(fd);
return 0;
}
运行结果:
6、父子进程间匿名内存映射
匿名映射不需要文件实体,直接进行内存映射。在父子进程中可以使用匿名映射。没有关系的进程不能进行,没有关联了。
void *mmap(void *addr, size_t length, int prot, int flags,int fd, off_t offset);
关键在于:port需要用到MAP_ANONYMOUS,此参数是匿名映射所需要的。
#define _DEFAULT_SOURCE
#include <stdio.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <sys/mman.h>
#include <string.h>
#include <stdlib.h>
int main()
{
//1、创建匿名内存映射区
int len =4096;
void *ptr = mmap(NULL,len,PROT_READ|PROT_WRITE ,MAP_SHARED | MAP_ANONYMOUS,-1,0);//
if(ptr==MAP_FAILED)
{
perror("mmap");
exit(0);
}
//2、父子进程通信
pid_t pid=fork();
if(pid>0)
{
//父进程
strcmp((char*)ptr,"hello,world");
wait(NULL);
}
else if(pid==0)
{
//子进程
sleep(1);
printf("%s\n",(char*)ptr);
}
//释放内存映射区
int ret=munmap(ptr,len);
if(ret==-1)
{
perror("munmap");
exit(0);
}
int ret1=munmap(ptr,len);
if(ret1==-1)
{
perror("munmap");
exit(0);
}
}
运行结果:
以上便是进程间内存映射的相关知识,结合案例进行了分析,大家学习后一定要多多练习!!
创作不易,还请多多点赞支持!!!