关于mmap的理解和使用

本篇文章介绍的是mmap的经典用法------文件映射,mmap允许用户将文件或设备的内容(文件内核缓冲区)直接映射到用户的虚拟地址空间中,用户通过虚拟地址直接访问文件缓冲区,避免了read/write等系统调用带来的拷贝和性能开销

mmap接口介绍

cpp 复制代码
#include <sys/mman.h>

void *mmap(void *addr, size_t length, int prot, int flags,int fd, off_t offset);
  • addr:我们可以指定一个地址去映射设备或文件内容,但是这个参数可能会被OS忽略(如果不合理的话)。当它为NULL的时候表示由OS自己决定映射到什么地方
  • length:表示要映射到进程地址空间的字节数,这个长度必须是页的整数倍(内存以页为单位管理,一般为4KB),如果不足OS自动向上对齐(多分配一点内存)
  • prot:用于设置这个内存块的访问权限(PROT_READ,PROT_WRITE,PROT_EXEC),学习了虚拟地址空间我们知道,权限是页表上的概念。
  • flags:指定了映射的类型。MAP_PRIVATE表示创建的是一个私有映射,对映射区域的修改不会反映到磁盘上,而且会有写时拷贝机制,即进程间不能共享;MAP_SHARED表示创建的是一个共享映射,对映射区域的修改会反映到磁盘上,进程间共享。MAP_ANONYMOUS表示创建的是匿名映射,不会与磁盘上的实际文件相关联。
  • fd:表示要把那个文件映射到地址空间中。如果是匿名映射则为-1。
  • offset:表示要从文件的什么位置开始映射。
  • 返回值:成功返回映射到的虚拟地址,失败返回-1。
cpp 复制代码
#include <sys/mman.h>

int munmap(void *addr, size_t length);
  • 这个函数用于解除虚拟地址addr与其指向的length字节的空间的映射关系。

使用mmap

共享映射一个文件并向文件写入内容

cpp 复制代码
#include<sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main()
{
    //首先创建并打开一个文件
    int fd = open("text.txt",O_RDWR | O_CREAT,0666);

    //调整这个文件的大小,以便填充mmap的length参数,从而正确映射(不能说文件大小只有1KB,你映射了10KB)
    truncate("text.txt",4096);

    //调用mmap把文件映射到虚拟地址空间
    char* ptr = (char*)mmap(NULL,4096,PROT_READ | PROT_WRITE,MAP_SHARED,fd,0);

    //直接使用地址对文件进行写入
    for(char i = 'a';i<='z';i++)
    {
        ptr[i-'a'] = i;
    }

    //取消映射
    munmap((void*)ptr,4096);

    //关闭文件
    close(fd);
}

私有映射一个文件并从文件读取内容

cpp 复制代码
#include<sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include<iostream>
using namespace std;
int main()
{
    //首先创建并打开一个文件
    int fd = open("text.txt",O_RDWR);

    //获取这个文件的大小,以便填充mmap的length参数,从而正确映射(不能说文件大小只有1KB,你映射了10KB)
    struct stat x;
    fstat(fd,&x);
    int size = x.st_size;

    //调用mmap把文件映射到虚拟地址空间
    char* ptr = (char*)mmap(NULL,size,PROT_READ | PROT_WRITE,MAP_PRIVATE,fd,0);

    //直接使用地址对文件进行读取
    cout<<ptr<<endl;

    //取消映射
    munmap((void*)ptr,size);

    //关闭文件
    close(fd);
}

关于flags取值易混淆的点

虽然说设置了PROT_PRIVATE就不与磁盘交互,并且进程私有这个控件,但并不代表他就是MAP_ANONYMOUS(匿名映射),它还是与一个实际文件相关联的,只不过不会修改这个文件,因此flags为PROT_PRIVATE不能把fd设置为-1,会报错。

私有映射一个文件并写入内容

cpp 复制代码
#include<sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include<iostream>
using namespace std;
int main()
{
    //首先创建并打开一个文件
    int fd = open("text.txt",O_RDWR);

    //获取这个文件的大小,以便填充mmap的length参数,从而正确映射(不能说文件大小只有1KB,你映射了10KB)
    struct stat x;
    fstat(fd,&x);
    int size = x.st_size;

    //调用mmap把文件映射到虚拟地址空间
    char* ptr = (char*)mmap(NULL,size,PROT_READ | PROT_WRITE,MAP_PRIVATE,fd,0);

    //直接使用地址对文件进行修改
    *ptr = 'M';

    //取消映射
    munmap((void*)ptr,size);

    //关闭文件
    close(fd);
}

匿名映射

cpp 复制代码
#include<sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include<iostream>
using namespace std;
int main()
{

    //调用mmap把文件映射到虚拟地址空间
    char* ptr = (char*)mmap(NULL,4096,PROT_READ | PROT_WRITE,MAP_PRIVATE | MAP_ANONYMOUS,-1,0);

    //在这就可以把这个空间当作malloc出来的动态空间使用啦

    //取消映射
    munmap((void*)ptr,4096);
}

匿名映射不需要关联文件,因此比较简单,你可以像使用malloc出来的动态空间一样使用它


共享内存和动态库的加载

我们使用的共享内存其实就是调用mmap以MAP_SHARED方式匿名映射出了一块空间,这样两个进程可以看到同一份空间。

而动态库的加载则是通过mmap把动态库文件的内容以MAP_SHARED方式映射到虚拟地址空间,这样我们访问虚拟地址就相当于访问动态库。
实际上,vm_area_struct中包含一个struct_file指针,当我们读取共享库中的内容而其又没有加载的时候
**,就可以通过这个文件结构体指针找到文件内容并加载,实现懒加载。同时这个结构体指针也记录着当前文件指针的偏移量。**

相关推荐
froginwe111 小时前
jQuery 隐藏/显示详解
开发语言
码云数智-大飞1 小时前
分布式数据库:2026年数据架构的基石与挑战
开发语言
查古穆2 小时前
python进阶-推导式
开发语言·python
njidf2 小时前
C++中的访问者模式
开发语言·c++·算法
英俊潇洒美少年2 小时前
js 同步异步,宏任务微任务的关系
开发语言·javascript·ecmascript
C_Si沉思2 小时前
C++中的工厂模式变体
开发语言·c++·算法
不会聊天真君6472 小时前
基础语法·中(golang笔记第二期)
开发语言·笔记·golang
m0_569881472 小时前
C++中的适配器模式变体
开发语言·c++·算法
第二层皮-合肥2 小时前
基于C#的工业测试控制软件-总体框架
开发语言·c#