概念
将一个文件直接映射到进程的进程空间中(“映射”就是建立一种对应关系,这里指硬盘上文件的位置与进程逻辑地址空间中一块相同区域之间一 一对应,这种关系纯属是逻辑上的概念,物理上是不存在的),这样可以通过内存指针用读写内存的办法直接存取文件内容。
特点
文件数据可以用内存读/写指令来访问,而不是用Read和Write这样的I/O系统函数,从而提高了文件存取速度。
流程
- 打开文件,创建文件句柄;
- 为文件创建内存映射内核对象,返回内存映射文件句柄;
- 映射整个文件或一部分到进程的虚拟地址空间,返回文件映射到内存后的起始地址;
- 解除文件映射;
- 关闭内存映射文件句柄;
- 关闭文件句柄;
函数
1)创建文件句柄。
- windows
# 函数
HANDLE CreateFile(LPCTSTR lpFileName, //普通文件名或者设备文件名
DWORD dwDesiredAccess, //访问模式(写/读)
DWORD dwShareMode, //共享模式
LPSECURITY_ATTRIBUTES lpSecurityAttributes, //指向安全属性的指针
DWORD dwCreationDisposition, //如何创建
DWORD dwFlagsAndAttributes, //文件属性
HANDLE hTemplateFile //用于复制文件句柄
);
- linux
# 函数
int open(const char *pathname, int flags, mode_t mode);
2)创建内存映射内核对象。
- windows
# 函数
HANDLE CreateFileMapping(
HANDLE hFile, // 文件句柄,填写 INVALID_HANDLE_VALUE
LPSECURITY_ATTRIBUTES lpFileMappingAttributes, // 安全描述符,填写 NULL
DWORD flProtect, // 映射对象保护属性
DWORD dwMaximumSizeHigh, // 文件映射的最大长度的高32位
DWORD dwMaximumSizeLow, // 文件映射的最大长度的低32位
LPCTSTR lpName // 文件映射对象名称
);
- linux
# 函数
void *mmap(void *start, size_t length, int prot, int flags, int fd, off_t offset);
# 参数说明
## start:映射区的开始地址
## length:映射区的长度
## prot:期望的内存保护标志,不能与文件的打开模式冲突。是以下的某个值,可以通过or运算合理地组合在一起
## flags:指定映射对象的类型,映射选项和映射页是否可以共享。
## fd:有效的文件描述词。如果MAP_ANONYMOUS被设定,为了兼容问题,其值应为-1
## offset:被映射对象内容的起点。
3)映射文件到进程的虚拟地址空间。
- windows
# 函数
LPVOID MapViewOfFile(
HANDLE hFileMappingObject, // CreateFileMapping()返回的文件映像对象句柄
DWORD dwDesiredAccess, // 数据的访问方式
DWORD dwFileOffsetHigh, // 文件映射起始偏移的高32位
DWORD dwFileOffsetLow, // 文件映射起始偏移的低32位
DWORD dwNumberOfBytesToMap // 文件中要映射的字节数,为0表示映射整个文件映射对象
);
4)在接收进程中打开对应的内存映射对象。
# 函数
HANDLE OpenFileMapping(
DWORD dwDesiredAccess, // 数据的访问方式
BOOL bInheritHandle, // 是否继承句柄
LPCTSTR lpName // 要打开的文件映射对象名称
);
5)回写内存映射文件。
- windows
# 函数
BOOL FlushViewOfFile(
LPCVOID lpBaseAddress, // 开始的地址
SIZE_T dwNumberOfBytesToFlush // 数据块的大小
);
- linux
#include <sys/mman.h>
# 函数
int msync(void *addr, size_t length, int flags);
# 参数说明
## 内存段需要修改的部分作为参数传递过来的起始地址addr和长度len确定。
## flags 参数控制着执行修改的具体方式:
#### MS_ASYNC 采用异步写方式
#### MS_SYNC 采用同步写方式
#### MS_INVALIDATE 从文件中读回数据
6)解除文件映射。
- windows
# 函数
BOOL UnmapViewOfFile(LPCVOID lpBaseAddress);
# 参数说明
## lpBaseAddress: 指向要取消映射的文件的映射视图基址的指针。此值必须与上一次调用 MapViewOfFile或 MapViewOfFileEx 函数返回的值相同。
- linux
# 函数
int munmap(void * addr, size_t len);
# 参数说明
## addr:映射内存起始地址
## len: 欲取消的内存大小
# 返回值
执行成功时,munmap()返回0。失败时,munmap返回-1。
示例
- windows实例
#include <iostream>
#include <stdio.h>
#include <windows.h>
#include <chrono>
#include <ctime>
using namespace std;
int main()
{
// MyData为测试文件,大小为1G
// linux可执行"truncate -s 1G MyData"命令生成大文件
uint64_t start = std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::system_clock::now().time_since_epoch()).count();
// 创建文件
HANDLE hFile = CreateFile(L"D://MyData",
GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
NULL);
if (INVALID_HANDLE_VALUE == hFile)
{
cout << " CreateFile fail" << endl;
return -1;
}
// 创建一个文件映射内核对象
HANDLE hFileMapping = CreateFileMapping(hFile,
nullptr,
PAGE_READWRITE,
0,
0,
nullptr);
if (nullptr == hFileMapping)
{
cout << "CreateFileMapping fail" << endl;
CloseHandle(hFile);
return -1;
}
// 将文件数据映射到进程的地址空间
char* mapData = (char*)MapViewOfFile(hFileMapping,
FILE_MAP_ALL_ACCESS,
0,
0,
0);
if (nullptr == mapData)
{
cout << "MapViewOfFile fail" << endl;
CloseHandle(hFileMapping);
CloseHandle(hFile);
return -1;
}
// 数据拷贝
char* myBuf = mapData;
// 计算时间
uint64_t end = std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::system_clock::now().time_since_epoch()).count();
uint64_t duration = end - start; // 微妙差
cout << "duration:" << duration << endl;
UnmapViewOfFile(myBuf);
CloseHandle(hFileMapping);
CloseHandle(hFile);
system("pause");
return 0;
}
执行结果:
1G的文件完成文件映射需要143微妙。
- linux实例
#include <iostream>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/fcntl.h>
#include <errno.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <chrono>
#include <ctime>
using namespace std;
#define MY_FILE_PATH "./MyData"
size_t get_file_size(const char *file_path)
{
struct stat buf;
if (stat(file_path, &buf) < 0)
{
printf("%s[%d]:%s", __FUNCTION__, __LINE__, strerror(errno));
return -1;
}
return buf.st_size;
}
int main()
{
// 执行"truncate -s 1G MyData"命令生成大文件
uint64_t start = std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::system_clock::now().time_since_epoch()).count();
// 打开文件
int fd = open(MY_FILE_PATH, O_RDWR|O_CREAT, S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH);
if (-1 == fd)
{
cout << "open failed,error:" << strerror(errno) << endl;;
return -1;
}
// 获取文件大小
size_t filelen = get_file_size(MY_FILE_PATH);
if (-1 == (int)filelen)
{
cout << "get file size failed" << endl;
return -1;
}
cout << "filesize = " << filelen << endl;
// 开始映射
void* result = mmap(0, filelen,
PROT_READ|PROT_WRITE,
MAP_SHARED,
fd, 0);
if (result == (void *)-1)
{
cout << "mmap failed,error:" << strerror(errno) << endl;
return -1;
}
// 计算时间
uint64_t end = std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::system_clock::now().time_since_epoch()).count();
uint64_t duration = end - start; // 微妙差
cout << "duration:" << duration << endl;
// 取消文件映射
munmap(result, filelen);
// 关闭文件句柄
close(fd);
return 0;
}
执行结果:
# 1G文件完成映射耗时270微妙
[root@localhost debug.x64-linux]# ./testFileMapping
filesize = 1073741824
duration:270
[root@localhost debug.x64-linux]#