如何通过修改内存映射来通信?
进程 1 修改了内存当中的内容,系统会将文件当中的内容同步,进程 2 也将同一个数据文件的内容映射到自己的内存当中,便可以读取到修改后的文件内容。
从而实现了进程间的通信。
实现父子进程之间的内存通信
/*
#include <sys/mman.h>
void *mmap(void *addr, size_t length, int prot, int flags,int fd, off_t offset);
-功能:将一个文件或者一个文件的数据映射到内存当中
-参数:
-void *addr :NULL 由内核指定
-length :要映射的数据的长度,这个值不能为0.建议使用文件的长度
获取文件的长度 stat sleek
最终内存分配的大小是分页的整数倍
-prot :对申请的内存一个操作权限
PROT_EXEC Pages may be executed.
PROT_READ Pages may be read.
PROT_WRITE Pages may be written.
PROT_NONE Pages may not be accessed.
要操作映射区,必须要有读的权限。
PROT_READ、PROT_READ | PROT_WRITE
-flags:
MAP_SHARED :映射区的文件会自动和磁盘文件进行同步,进程间通信必须要设置这个选项
MAP_PRIVATE :不同步,内存区的文件修改了,对原来的文件不会改变,底层会重新创建一个新的文件,(copy on write)
-fd:需要操作的文件的文件描述符,通过 open 得到的
open 打开的是一个磁盘文件,
注意:文件的大小不能为0,open 指定的权限不能和 prot 的大小有冲突
open:O_RDONLY / O_RDWE prot: PROT_READ
open: O_RDWE prot: PROT_READ | PROT_WRITE
-offset :偏移量,一般不用,必须指定的是 4k 的整数倍才能便宜成功,0表示不偏移
返回值:返回创建的内存的首地址
如果失败,返回 MAP_FAILED,其真实值为(void*)-1
int munmap(void *addr, size_t length);
功能 :释放内存映射
参数:
-addr :要释放的内存首地址(和mmap配套着使用)
-length:要释放的内存的大小,要和 mmap 当中的 length 的参数值是一样的
*/
/*
实现使用内存映射来进行进程间的互相通信
1、有关系的进程(父子进程)
-还没有子进程的时候
-通过唯一的父进程,先创建内存映射区
-有了内存映射区后,创建子进程
-父子进程共享创建的内核映射区
2、没有关系的进程间的通信
-准备一个大小不是0的磁盘文件
-进程1 通过磁盘文件创建内存映射区
-得到一个操作这块内存的指针
-进程2 通过磁盘文件创建进程映射区
-得到一个操作这块内存的指针
-使用内存映射区通信
注意:使用内存映射区进行通信,是非阻塞
*/
#include<stdio.h>
#include<sys/mman.h>
#include<sys/stat.h>
#include<sys/types.h>
#include<fcntl.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<sys/wait.h>
int main(void){
//1.打开一个文件
int fd = open("cc.txt",O_RDWR);
if(fd == -1){
perror("open");
exit(0);
}
//获取文件大小
int len = lseek(fd,0,SEEK_END);
if(len == -1){
perror("lseek");
exit(0);
}
//2.创建内存映射区
void*ptr = mmap(NULL,len,PROT_READ | PROT_WRITE,MAP_SHARED,fd,0);
if(ptr == MAP_FAILED){
perror("mmap");
exit(0);
}
//3.创建子进程
pid_t pid = fork();
if(pid > 0){
//父进程
wait(NULL);
//strcpy(ptr,"hello,i am parent.");要注意指针的类型转换
char buf[1024];
strcpy(buf,(char*)ptr);
printf("recv data: %s.\n",buf);
}
else if(pid == 0)
{
strcpy((char*)ptr,"hello,i am son.");
}
else if(pid == -1){
perror("fork");
exit(0);
}
munmap(ptr,len);
return 0;
}
作为中介的文件也被修改了:
思考问题
1、如果对mmap的返回值(ptr)做++操作(ptr++), munmap是否能够成功?
2、如果open时O_RDONLY, mmap时prot参数指 PROT_READ | PROT_WRITE会怎样?
3、如果文件偏移量为1000会怎样?
4、mmap什么情况下会调用失败?
5、可以open的时候O_CREAT一个新文件来创建映射区吗?
6、mmap后关闭文件描述符,对mmap映射有没有影响?
7、对ptr越界操作会怎样?
1、如果对mmap的返回值(ptr)做++操作(ptr++), munmap是否能够成功?
ptr = mmap(...);
ptr++;//可以对其进行 ++ 操作,在此之前需要对首地址进行备份
munmap(ptr,len);//错误,如果传入的不是首地址指针,会导致内存的错误释放
2、如果 open 时 O_RDONLY , mmap 时 prot 参数指 PROT_READ | PROT_WRITE 会怎样?
错误,会返回 MAP_FAILED 也就是 void*(-1)
open ()函数中的权限建议和 mmap 中 prot 的权限一致
否则无法实现内存映射
3、如果文件偏移量为1000会怎样?
偏移量必须是 4k(4096) 的整数倍,如果不是会返回 MAP_FAILED
4、mmap什么情况下会调用失败?
第二个参数:len = 0;
第三个参数:prot
只指定了写的权限
prot PROT_READ | PROT_WRITE
第五个参数 fd 通过 open 函数打开文件的权限指定的是 O_RDONLY 或者是 O_WRONLY
5、可以open的时候O_CREAT一个新文件来创建映射区吗?
可以,但是要向其中写入数据。如果创建文件的大小为 0 的话,是无法成功映射的。
要对新创建的文件进行扩展
lseek()
truncate()
6、mmap后关闭文件描述符,对mmap映射有没有影响?
int fd = open("XXXX");
mmap(,,,,fd,0);
closed(fd);
映射区还存在,创建映射区的 fd 还存在没有任何影响。
7、对ptr越界操作会怎样?
void* ptr = mmap(NULL,100,,,);
越界操作,操作的是非法的内存 -> 会产生段错误
使用内存映射去完成文件复制。
如上图所示,开辟两个内存映射,将两个文件都映射到内存当中,将第一个内存映射区的数据拷贝到第二个内存映射区当中,并且设置磁盘文件和内存映射区的文件同步改动。这样第二个文件也同样的会被修改。
//使用内存映射来进行文件的拷贝
/*
思路:
1、对原始的文件进行内存映射
2、创建一个新文件
3、把新文件的数据映射到内存中
4、将第一个内存映射区的数据拷贝到第二个内存映射区
5、释放资源(确实没有想到)
*/
#include<stdio.h>
#include<sys/mman.h>
#include<sys/stat.h>
#include<sys/types.h>
#include<fcntl.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
int main(void){
//1.打开文件
int fd1 = open("cc.txt",O_RDWR);
if(fd1 == -1){
perror("open");
exit(0);
}
//获取原始文件的大小,长度
int len = lseek(fd1,0,SEEK_END);
if(fd1 == -1){
perror("lseek");
exit(0);
}
//2.新创建一个文件
int fd2 = open("new.txt",O_CREAT | O_RDWR,0664);
if(fd1 == -1){
perror("open");
exit(0);
}
//对新创建的文件进行拓展
truncate("new.txt",len);
//
int len2 = write(fd2," ",1);
if(len2 == -1){
perror("write");
exit(0);
}
//分别对其做内存映射
void* ptr1 = mmap(NULL,len,PROT_READ | PROT_WRITE,MAP_SHARED,fd1,0);
if(ptr1 == MAP_FAILED){
perror("mmap1");
exit(0);
}
void* ptr2 = mmap(NULL,len,PROT_READ | PROT_WRITE,MAP_SHARED,fd2,0);
if(ptr1 == MAP_FAILED){
perror("mmap2");
exit(0);
}
//内存拷贝
memcpy(ptr2,ptr1,len);
//释放资源
munmap(ptr2,len);
munmap(ptr1,len);
close(fd2);
close(fd1);
//释放资源有个小细节,要先释放后申请的资源,防止第二个资源对第一个资源有依赖的关系
return 0;
}
但是不会用来拷贝过大的文件,过大的文件特别消耗内存。
使用匿名内存实现父子之间的进程通信
/*
使用匿名映射
不需要文件实体进行一个内存映射
只能使用父子进程之间进行文件映射
*/
#include<stdio.h>
#include<sys/mman.h>
#include<sys/stat.h>
#include<sys/types.h>
#include<fcntl.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<sys/wait.h>
int main(void){
int lenght = 4096;
void*ptr = mmap(NULL,lenght,PROT_READ |PROT_WRITE,MAP_ANONYMOUS | MAP_SHARED,-1,0);
if(ptr == MAP_FAILED){
perror("mmap");
exit(0);
}
pid_t pid = fork();
if(pid > 0){
// strcpy(ptr,"holle,world");要注意强制类型转换
strcpy((char*)ptr,"holle,world.");
wait(NULL);
}else if(pid == 0){
//子进程
sleep(1);
printf("%s\n",(char*)ptr);
}else if(pid == -1){
perror("fork");
exit(0);
}
//释放内存
int ret = munmap(ptr,lenght);
if(ret == -1){
perror("munmap");
exit(0);
}
return 0;
}