2022-1-19牛客Linux C++ 项目 —— 内存映射

在这里插入图片描述
如何通过修改内存映射来通信?
在这里插入图片描述
进程 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;
}

在这里插入图片描述

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值