内存映射
1. 内存映射的基本概念:
-
内存映射(Memory Mapping): 将一个文件的内容映射到进程的地址空间,使得对文件的读写等操作可以通过对内存的直接访问来完成,而无需通过传统的读写系统调用。
-
文件映射到内存的区域: 文件的内容在进程的地址空间中被划分为一些区域,这些区域可以被直接读取或写入。通常包括可读、可写、可执行等不同权限的区域。
2. 内存映射的优势:
-
性能提升: 由于可以直接在内存中访问文件内容,因此避免了传统的读写系统调用的开销,提高了读写的效率。
-
进程间通信: 多个进程可以将同一个文件映射到各自的地址空间,实现共享内存,从而方便进程间通信。
-
文件映射到内存的同步: 对内存的修改可以通过同步到磁盘文件来保持一致,也可以通过共享内存的方式实现多个进程之间的数据共享。
3. 内存映射的实现机制:
-
mmap
系统调用: 在 Unix/Linux 系统中,使用mmap
系统调用进行内存映射。这个调用会创建一个新的映射区域,将一个文件或者一个设备映射到内存中。 -
munmap
系统调用: 用于解除内存映射。 -
msync
系统调用: 用于将内存映射区域的修改同步到文件。
4. 内存映射的应用场景:
-
文件 I/O: 提高对文件的读写性能,尤其对于大文件的访问。
-
共享内存: 进程间通信的一种方式,多个进程可以映射同一个文件,实现数据的共享。
-
实现类似
malloc
和free
的内存管理机制: 通过内存映射技术,可以实现一些自定义的内存分配和释放机制。
5. 注意事项:
-
文件大小: 文件映射的大小应小于等于文件的大小。
-
同步: 对内存的修改需要通过
msync
等方法同步到文件,确保数据的一致性。 -
错误处理: 在使用内存映射时,需要仔细处理错误情况,确保操作的安全性。
文件映射 - mmap实现
common.h
#ifndef _COMMON_H_
#define _COMMON_H_
#include<iostream>
#include<fcntl.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<sys/mman.h>
#include<string>
#include<string.h>
#include<stdint.h>
#include<errno.h>
#include<stdio.h>
#endif //_COMMON_H_
mmap_file.h
#ifndef _MMAP_FILE_H_
#define _MMAP_FILE_H_
#include<unistd.h> //该头文件包含了 POSIX 系统调用的声明
#include"common.h"
/*
把指定文件映射到内存,提供读写接口
把内存的数据同步到磁盘文件中
*/
namespace Airwave
{
namespace largefile
{
//用于存储内存映射选项的相关参数
struct MMapOption
{
int32_t max_mmap_size_; //最大映射大小
int32_t first_mmap_size_; //首次映射大小
int32_t per_mmap_size_; //每次映射增加的大小
};
//用于管理文件的内存映射操作
class MMapFile
{
public:
MMapFile();
explicit MMapFile(const int fd);
MMapFile(const MMapOption& mmap_option, const int fd);
~MMapFile();
bool sync_file(); //执行同步文件
bool map_file(const bool write = false); //将文件映射到内存,同时设置访问权限
void* get_data() const; //获取映射到内存数据的首地址
int32_t get_size() const; //获取大小
bool munmap_file(); //解除映射
bool remap_file(); //重新执行映射,做内存扩容
private:
bool ensure_file_size(const int32_t size); //确保文件大小,如果文件大小不够,就扩容
private:
int32_t size_; //文件大小
int fd_; //文件描述符
void* data_; //映射到内存的数据首地址
struct MMapOption mmap_file_option_; //映射文件的选项
};
}
}
#endif
mmap_file.cpp
#include"mmap_file.h"
#include<stdlib.h>
static int debug = 1;
namespace Airwave
{
namespace largefile
{
MMapFile::MMapFile():
size_(0),fd_(-1),data_(NULL)
{
}
MMapFile::MMapFile(const int fd):
size_(0),fd_(fd),data_(NULL)
{
}
MMapFile::MMapFile(const MMapOption& mmap_option, const int fd) :
size_(0), fd_(fd), data_(NULL)
{
//将传入的结构体mmap_option的相关数值赋值给mmap_file_option_
mmap_file_option_.max_mmap_size_ = mmap_option.max_mmap_size_;
mmap_file_option_.first_mmap_size_ = mmap_option.first_mmap_size_;
mmap_file_option_.per_mmap_size_ = mmap_option.per_mmap_size_;
}
MMapFile::~MMapFile()
{
if (data_ != NULL)
{
if (debug)
{
std::cout<<"mmap file destruct, fd:"<<fd_<<", maped size:"<<size_<<std::endl;
}
msync(data_,size_,MS_SYNC); //将内存中的数据同步到磁盘上
munmap(data_,size_); //释放内存映射区域,解除映射
size_ = 0;
data_ = NULL;
fd_ = -1;
mmap_file_option_.max_mmap_size_ = 0;
mmap_file_option_.first_mmap_size_ = 0;
mmap_file_option_.per_mmap_size_ = 0;
}
}
bool MMapFile::sync_file()
{
if (data_ != NULL && size_ > 0)
{
if (debug)
{
std::cout<<"mmap file sync, fd:"<<fd_<<", maped size:"<<size_<<std::endl;
}
return msync(data_, size_, MS_ASYNC) == 0;
}
return true;
}
bool MMapFile::map_file(const bool write)
{
int flags = PROT_READ;
if (write == true)
{
flags |= PROT_WRITE;
}
if(fd_ < 0)
{
return false;
}
if(0 == mmap_file_option_.max_mmap_size_)
{
return false;
}
if (size_ < mmap_file_option_.max_mmap_size_)
{
size_ = mmap_file_option_.first_mmap_size_;
}
else
{
size_ = mmap_file_option_.max_mmap_size_;
}
if (!ensure_file_size(size_))
{
fprintf(stderr, "ensure file size failed in map_file,size : %d\n",size_);
return false;
}
data_ = mmap(0, size_, flags, MAP_SHARED, fd_, 0);
if (MAP_FAILED == data_)
{
//打印错误信息
fprintf(stderr, "mmap file failed: %s\n",strerror(errno)); //strerror(errno)返回错误信息,errno是全局变量,表示错误码
size_ = 0;
data_ = NULL;
fd_ = -1;
return false;
}
if(debug)
{
std::cout<<"mmap file success, fd:"<<fd_<<", maped size:"<<size_<<" data:"<<data_<<std::endl; //打印信息"
}
return true;
}
void* MMapFile::get_data() const
{
return data_;
}
int32_t MMapFile::get_size() const
{
return size_;
}
bool MMapFile::munmap_file()
{
if(munmap(data_,size_) == 0)
{
return true;
}
else
{
return false;
}
}
bool MMapFile::remap_file()
{
if (fd_ < 0 || data_ == NULL)
{
fprintf(stderr, "remap file failed, fd:%d, data:%p\n", fd_, data_);
return false;
}
if (size_ == mmap_file_option_.max_mmap_size_)
{
fprintf(stderr, "already mapped max size, now size:%d, max_mmap_size:%d\n", size_, mmap_file_option_.max_mmap_size_);
return false;
}
int32_t new_size = size_ + mmap_file_option_.per_mmap_size_;
if (new_size > mmap_file_option_.max_mmap_size_)
{
new_size = mmap_file_option_.max_mmap_size_;
}
if (!ensure_file_size(new_size))
{
fprintf(stderr, "ensure file size failed in remap_file, size:%d\n", new_size);
return false;
}
if(debug)
{
std::cout<<"mremap file start, fd:"<<fd_<<", maped size:"<<size_<<", new size:"<<new_size<<" old data:"<<data_<<std::endl;
}
void* new_map_data = mremap(data_, size_, new_size, MREMAP_MAYMOVE);
if(MAP_FAILED == new_map_data)
{
fprintf(stderr, "mremap file failed,new size:%d, error desc: %s\n", new_size, strerror(errno));
return false;
}
else
{
if(debug)
{
std::cout<<"mremap file success, fd:"<<fd_<<", maped size:"<<new_size<<" new data:"<<new_map_data<<std::endl; //打印信息"
}
}
data_ = new_map_data;
size_ = new_size;
return true;
}
bool MMapFile::ensure_file_size(const int32_t size)
{
struct stat s;
if (fstat(fd_, &s) < 0)
{
fprintf(stderr, "fstat file failed: %s\n", strerror(errno));
return false;
}
if (s.st_size < size)
{
if (ftruncate(fd_, size) < 0)
{
fprintf(stderr, "ftruncate file failed,size: %d, error desc: %s\n", size, strerror(errno));
return false;
}
}
return true;
}
}
}
测试 main.cpp
#include"common.h"
#include"mmap_file.h"
using namespace Airwave;
using namespace std;
static const mode_t OPEN_MODE = 0644; //文件权限,可读可写
const static largefile::MMapOption mmap_option = { 10240000, 4096, 4096 };//内存映射参数:最大映射大小、首次映射大小和每次映射增加的大小
int open_file(string file_name,int open_flags)
{
int fd = open(file_name.c_str(),open_flags,OPEN_MODE);
if(fd < 0)
{
return -errno; //errno strerror(error)
}
return fd;
}
int main(void)
{
const char* filename = "test.txt";
//1.创建/打开一个文件,取得文件的句柄 open函数
int fd = open_file(filename, O_RDWR | O_CREAT | O_LARGEFILE);
if(fd < 0)
{
fprintf(stderr ,"open file %s failed,\nerror:%s\n",filename,strerror(errno));
return -1;
}
largefile::MMapFile* mmap_file = new largefile::MMapFile(mmap_option, fd);
//2.将文件映射到内存中 mmap函数
bool is_mapped = mmap_file->map_file();
if (is_mapped)
{
memset(mmap_file->get_data(), '8', mmap_file->get_size());
mmap_file->sync_file();
//解除映射
mmap_file->munmap_file();
}
else
{
fprintf(stderr, "map file %s failed,\nerror:%s\n", filename, strerror(errno));
}
//关闭文件
close(fd);
return 0;
}
-
结构体
MMapOption
:- 存储了内存映射选项的相关参数,包括最大映射大小
max_mmap_size_
、首次映射大小first_mmap_size
和每次映射增加的大小per_mmap_size
。
- 存储了内存映射选项的相关参数,包括最大映射大小
-
类
MMapFile
:- 构造函数:
- 提供了三个构造函数,包括默认构造函数、接受文件描述符的构造函数和接受
MMapOption
及文件描述符的构造函数。
- 提供了三个构造函数,包括默认构造函数、接受文件描述符的构造函数和接受
- 成员函数:
- 提供了一系列用于执行文件同步、将文件映射到内存、获取映射到内存数据的首地址、获取映射大小、解除映射和重新执行映射的函数。
- 私有成员和函数:
- 包括文件大小、文件描述符、映射的数据指针以及存储映射选项的结构体。
- 私有函数
ensure_file_size
用于确保文件大小满足要求。
- 构造函数:
这段代码是一个封装了文件内存映射操作的类,提供了一些基本的文件操作接口,并包含了一些内存映射的选项。
msync
实现磁盘文件内容于共享内存区中的内容一致,即同步操作。
函数原型 int msync ( void * addr, size_t len, int flags)
addr:文件映射到进程空间的地址;
len:映射空间的大小;
flags:刷新的参数设置,可以取值MS_ASYNC/ MS_SYNC;
取值为MS_ASYNC(异步)时,调用会立即返回,不等到更新的完成;
取值为MS_SYNC(同步)时,调用会等到更新完成之后返回;
返回值 成功则返回0;失败则返回-1;
mremap
扩大(或缩小)现有的内存映射
函数原型 void * mremap(void *old_address, size_t old_size , size_t new_size, int flags);
头文件
#include <unistd.h>
#include <sys/mman.h>
addr: 上一次已映射到进程空间的地址;
old_size: 旧空间的大小;
new_size: 重新映射指定的新空间大小;
flags: 取值可以是0或者MREMAP_MAYMOVE,0代表不允许内核移动映射区域, MREMAP_MAYMOVE则表示内核可以根据实际情况移动映射区域以找到一个符合 new_size大小要求的内存区域
返回值 成功则返回0;失败则返回-1;
测试
进行扩容后再测试
扩容4kb,将所有数据改为9
重新编译运行
扩容成功