前面讲过,建立内存映射并没有实际拷贝数据,这时,MMU在地址映射表中是无法找到与ptr相对应的物理地址的,也就是MMU失败,将产生一个缺页中断,缺页中断的中断响应函数会在swap中寻找相对应的页面,如果找不到(也就是该文件从来没有被读入内存的情况),则会通过mmap()建立的映射关系,从硬盘上将文件读取到物理内存中,这个过程与内存映射无关。
如果在拷贝数据时,发现物理内存不够用,则会通过虚拟内存机制(swap)将暂时不用的物理页面交换到硬盘上,如图1中过程4所示。这个过程也与内存映射无关。
效率:从代码层面上看,从硬盘上将文件读入内存,都要经过文件系统进行数据拷贝,并且数据拷贝操作是由文件系统和硬件驱动实现的,理论上来说,拷贝数据的效率是一样的。但是通过内存映射的方法访问硬盘上的文件,效率要比read和write系统调用高,这是为什么呢?原因是read()是系统调用,其中进行了数据拷贝,它首先将文件内容从硬盘拷贝到内核空间的一个缓冲区,然后再将这些数据拷贝到用户空间,在这个过程中,实际上完成了 两次数据拷贝 ;而mmap()也是系统调用,如前所述,mmap()中没有进行数据拷贝,真正的数据拷贝是在缺页中断处理时进行的,由于mmap()将文件直接映射到用户空间,所以中断处理函数根据这个映射关系,直接将文件从硬盘拷贝到用户空间,只进行了 一次数据拷贝 。因此,内存映射的效率要比read/write效率高。
创建文件映射示例: #include <iostream> #include <fcntl.h> #include <io.h> #include <afxwin.h> using namespace std; int main() { //开始 //1.获得文件句柄 创建一个文件内核对象 HANDLE hFile=CreateFile( //失败返回INVALID_HANDLE_VALUE "c:\\test.dat", //文件名 GENERIC_READ|GENERIC_WRITE, //对文件进行读写操作,文件权限, FILE_SHARE_READ|FILE_SHARE_WRITE, //共享模式 NULL, //安全属性 OPEN_EXISTING, //打开已存在文件 FILE_ATTRIBUTE_NORMAL, //文件或设备属性或标志 NULL); //模板模式 //返回值size_high,size_low分别表示文件大小的高32位/低32位 DWORD size_low,size_high; size_low= GetFileSize(hFile,&size_high); //2.创建文件的内存映射文件, 创建一个文件映射内核对象 HANDLE hMapFile=CreateFileMapping( hFile, //文件名 NULL, //安全属性 PAGE_READWRITE, //对映射文件进行读写 size_high, size_low, //这两个参数共64位,所以支持的最大文件长度为16EB ,一共要映射多少到内存中 L"MyFileMap"); //映射的文件名字,其他进程,可通过此名字打开共享文件 if(hMapFile==NULL) //失败返回NULL { AfxMessageBox("Can't create file mapping.Error%d:\n", GetLastError()); CloseHandle(hFile); return 0; } //3.把文件数据映射到进程的地址空间 void* pvFile=MapViewOfFile( //失败返回NULL hMapFile, //文件映射的句柄 FILE_MAP_READ|FILE_MAP_WRITE, //权限 0, //高位偏移 0, //低位偏移 0); //一次映射多少 64K,如果是0,则映射从偏移量到文件末尾。 unsigned char *p=(unsigned char*)pvFile; //至此,就获得了外部文件test.dat在内存地址空间的映射, //4.用指针p"折磨"这个文件了 CString s; p[size_low-1]='!'; p[size_low-2]='X'; //修改该文件的最后两个字节(文件大小<4GB高32位为0) s.Format("%s",p); //读文件的最后3个字节 AfxMessageBox(s); //5.结束 //UnmapViewOfFile(pvFile); //撤销映射 //CloseHandle(hFile); //关闭文件 return 0; }打开文件映射示例:#include<iostream> #include<Windows.h> using namespace std; int main() { char szBuf[100]; //1.打开文件映射对象 //权限,继承性,名字 HANDLE hFileMap = OpenFileMapping(FILE_MAP_ALL_ACCESS,NULL,L"MyFileMap"); if(NULL == hFileMap) return 0; //2.将文件映射到进程地址空间 char * pStartAddress = (char*)MapViewOfFile(hFileMap,FILE_MAP_ALL_ACCESS,0,0,0); //3.取出文件内容 memcpy(szBuf,pStartAddress,100); //4.取消映射 UnmapViewOfFile(hFileMap); system("pause"); return 0; }