淘宝文件系统文件映射原理及实现

1 文件系统映射原理

1.1 文件映射应用场景

主要应用场景如下:

  • 进程间共享信息。
  • 实现文件数据从磁盘到内存的映射,极大的提升应用程序访问文件的速度。

1.2 文件映射相关函数介绍

作用:将一个文件或者其它对象映射进内存。

  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;
}


参考资料:

  1. C/C++从入门到精通-高级程序员之路【奇牛学院】

猜你喜欢

转载自blog.csdn.net/SlowIsFastLemon/article/details/106545225