本篇文章介绍的是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指针,当我们读取共享库中的内容而其又没有加载的时候**,就可以通过这个文件结构体指针找到文件内容并加载,实现懒加载。同时这个结构体指针也记录着当前文件指针的偏移量。**