1 文件系统映射原理
1.1 文件映射应用场景
主要应用场景如下:
- 进程间共享信息。
- 实现文件数据从磁盘到内存的映射,极大的提升应用程序访问文件的速度。
1.2 文件映射相关函数介绍
作用:将一个文件或者其它对象映射进内存。
- 使用普通文件提供的内存映射。
- 使用特殊文件提供匿名内存映射。
mmap函数:
#include <sys/mman.h>
void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
int munmap(void *addr, size_t length);
/*
参数start:指向欲映射的内存起始地址,通常设为 NULL,代表让系统自动选定地址,映射成功后返回该地址。
参数length:代表将文件中多大的部分映射到内存。
参数prot:映射区域的保护方式。可以为以下几种方式的组合:
PROT_EXEC 执行
PROT_READ 读取
PROT_WRITE 写入
PROT_NONE 不能存取
参数flags:影响映射区域的各种特性。必须要指定MAP_SHARED 或MAP_PRIVATE。
MAP_SHARED - 映射区域数据与文件对应,允许其他进程共享
MAP_PRIVATE - 映射区域生成文件的copy,修改不同步文件
MAP_ANONYMOUS - 建立匿名映射。此时会忽略参数fd,不涉及文件,而且映射区域无法和其他进程共享。
MAP_DENYWRITE - 允许对映射区域的写入操作,其他对文件直接写入的操作将会被拒绝。
MAP_LOCKED - 将映射区域锁定住,这表示该区域不会被置swap
参数fd:要映射到内存中的文件描述符。如果使用匿名内存映射时,即flags中设置了MAP_ANONYMOUS,fd设为-1。有些系统不支持匿名内存映射,则可以使用fopen打开/dev/zero文件,然后对该文件进行映射,可以同样达到匿名内存映射的效果。
参数offset:文件映射的偏移量,通常设置为0,代表从文件最前方开始对应,offset必须是分页大小的整数倍(分页大小一般是4KB)。
*/
msync函数介绍:
实现磁盘文件内容于共享内存区中的内容一致,即同步操作。
//函数原型
int msync ( void * addr, size_t len, int flags)
//头文件
#include<sys/mman.h>
/*
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大小要求的内存区域
返回值:成功则返回映射后内存区域的地址,失败则返回(void*)-1;
*/
2 文件映射实战
主要由map_file.cpp、map_file.h、common.h、main.cpp几个文件。
common.h:
#ifndef _COMMON_H_INCLUDED_
#define _COMMON_H_INCLUDED_
#include <iostream>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string>
#include <string.h>
#include <stdint.h>
#include <inttypes.h>
#include <errno.h>
#include <stdio.h>
#include <sys/mman.h>
#include <unistd.h>
#include <stdlib.h>
#include <assert.h>
#endif /*_COMMON_H_INCLUDED_*/
map_file.h:
#ifndef QINIU_LARGEFILE_MMAPFILE_H_
#define QINIU_LARGEFILE_MMAPFILE_H_
#include <unistd.h>
#include "common.h"
namespace qiniu
{
namespace largefile
{
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(); //重新执行映射 mremap
private:
bool ensure_file_size(const int32_t size);
private:
int32_t size_;
int fd_;
void *data_;
struct MMapOption mmap_file_option_;
};
}
}
#endif //
map_file.cpp:
#include "mmap_file.h"
#include <stdio.h>
static int debug = 1;
namespace qiniu
{
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_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_){
if(debug) printf("mmap file destruct, fd: %d, maped size: %d, data: %p\n", fd_, size_, data_);
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(NULL !=data_ && size_ > 0){
return msync(data_, size_, MS_ASYNC)==0;
}
return true;
}
bool MMapFile::map_file(const bool write )
{
int flags = PROT_READ;
if(write){
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, "map file failed: %s\n", strerror(errno));
size_ = 0;
fd_ = -1;
data_ = NULL;
return false;
}
if(debug) printf("mmap file successed, fd: %d maped size: %d, data: %p\n", fd_, size_, data_);
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::ensure_file_size(const int32_t size)
{
struct stat s;
if(fstat(fd_, &s) < 0){
fprintf(stderr, "fstat error, error desc: %s\n", strerror(errno));
return false;
}
if(s.st_size < size){
if(ftruncate(fd_, size) < 0){
fprintf(stderr, "ftruncate error, size: %d, error desc: %s\n", size, strerror(errno));
return false;
}
}
return true;
}
bool MMapFile::remap_file()
{
//1. 防御性编程
if(fd_ < 0 || data_ == NULL){
fprintf(stderr, "mremap not mapped yet\n");
return false;
}
if(size_ == mmap_file_option_.max_mmap_size_){
fprintf(stderr, "already mapped max size, now size: %d, max 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) printf("mremap start. fd: %d, now size: %d, new size: %d, old data: %p\n", fd_, size_, new_size, data_);
void *new_map_data = mremap(data_, size_, new_size, MREMAP_MAYMOVE);
if(MAP_FAILED == new_map_data){
fprintf(stderr, "mremap failed , fd: %d, new size: %d, error desc: %s\n", fd_, new_size, strerror(errno));
return false;
}else {
if(debug) printf("mremap success. fd: %d, now size: %d, new size: %d, old data: %p, new data: %p\n", fd_, size_, new_size,
data_, new_map_data);
}
data_ = new_map_data;
size_ = new_size;
return true;
}
}
}
main.cpp:
#include "mmap_file.h"
#include "common.h"
using namespace std;
using namespace qiniu;
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); //open 成功返回的一定是>0
if(fd < 0){
return -errno; //errno strerror(errno); //read errno
}
return fd;
}
int main(void ){
const char *filename = "./mapfile_test.txt";
//1. 打开/创建一个文件,取得文件的句柄 open函数
int fd = open_file(filename, O_RDWR | O_LARGEFILE);
if(fd<0){
//调用read ,出错重置 errno
fprintf(stderr, "open file failed. filename:%s, error desc: %s\n", filename, strerror(-fd));
return -1;
}
largefile::MMapFile *map_file = new largefile::MMapFile(mmap_option, fd);
bool is_mapped = map_file->map_file(true);
if(is_mapped){
map_file->remap_file();
memset(map_file->get_data(), '9', map_file->get_size());
map_file->sync_file();
map_file->munmap_file();
}else {
fprintf(stderr, "map file failed\n");
}
delete map_file;
close(fd);
return 0;
}
参考资料: