Win32学习笔记(21)内存映射文件

1.什么是内存映射文件

 

如上图:内存映射文件意思就是吧一个硬盘上的文件映射到物理页上在把物理页映射到虚拟内存上。

这样的好处是不必再打开、关闭文件,直接像访问自己的内存一样想读就读想写就写,想要读写文件时候就直接读写内存。还有一个很大的优点就是当文件很大的时候使用这种方式会有很好的性能。

下面我们尝试用代码实现

介绍下CreateFileMapping()函数,这个可以理解为准备物理内存的。在之前的物理内存中我们也知道,这个函数是来实现多个进程公用物理页的。他还可以把硬盘上的内容映射到物理页上(把文件句柄)

HANDLE CreateFileMappingA(
  [in]           HANDLE                hFile,
  [in, optional] LPSECURITY_ATTRIBUTES lpFileMappingAttributes,
  [in]           DWORD                 flProtect,
  [in]           DWORD                 dwMaximumSizeHigh,
  [in]           DWORD                 dwMaximumSizeLow,
  [in, optional] LPCSTR                lpName
);

MapViewOfFile()函数:把物理内存与虚拟内存关联(就是最上面图中左边的箭头)

当执行完后我们会得到一个地址,我们代码中得到的就是虚拟内存的地址。然后我们就可以使用这个地址了。(下面我们读取地址)

LPVOID MapViewOfFile(
  [in] HANDLE hFileMappingObject,
  [in] DWORD  dwDesiredAccess,
  [in] DWORD  dwFileOffsetHigh,
  [in] DWORD  dwFileOffsetLow,
  [in] SIZE_T dwNumberOfBytesToMap
);

#include"stdafx.h"
#include<stdio.h>
#include<Windows.h>
DWORD MappingFile(LPSTR lpcFile)
{
    HANDLE hFile;
    HANDLE hMapFile;
    DWORD dwFileMapSize;
    LPVOID lpAddr;
​
    //1.得到文件句柄
    hFile = CreateFileA(lpcFile,
        GENERIC_READ | GENERIC_WRITE,
        0,
        NULL,
        OPEN_EXISTING,
        FILE_ATTRIBUTE_NORMAL,
        NULL
        );
    if (hFile == INVALID_HANDLE_VALUE)
    {
        printf("CreateFile 失败:%d \n", GetLastError());
        return 0;
    }
​
    //2.创建FileMapping对象
    hMapFile = CreateFileMapping(hFile,
        NULL,
        PAGE_READWRITE,
        0,
        0,
        NULL
        );
    if (hMapFile == NULL)
    {
        printf("CreateFileMapping 失败:%d \n ", GetLastError());
        CloseHandle(hFile);
        return 0;
    }
    //3.映射到虚拟内存
    lpAddr = MapViewOfFile(hMapFile,
        FILE_MAP_COPY,//FILE_MAP_ALL_ACCESS FILE_MAP_COPY
        0, 0, 0);
​
    //4.读取文件
    DWORD dwTest1 = *(PDWORD)lpAddr;//读文件开始的地方
    DWORD dwTest2 = *((PDWORD)lpAddr + 0x30);//想读到哪(加上偏移)
    printf("%x %x \n", dwTest1, dwTest2);
}
int main(int argc, char* argv[])
​
{
    MappingFile("D:\\DebugView++CHS X32.exe");
    return 0;
}

我们用debugview++来测试看看。

我们先用wxmedit查看debugview++

 

代码中我们加了0x30.因为是DWORD类型,加1相当于加4。所以对应查看的应该是0xC0

看下输出

 

因为是小端。正好对应

下面是写文件:

#include"stdafx.h"
#include<stdio.h>
#include<Windows.h>
DWORD MappingFile(LPSTR lpcFile)
{
    HANDLE hFile;
    HANDLE hMapFile;
    DWORD dwFileMapSize;
    LPVOID lpAddr;
​
    //1.得到文件句柄
    hFile = CreateFileA(lpcFile,
        GENERIC_READ | GENERIC_WRITE,
        0,
        NULL,
        OPEN_EXISTING,//打开现有文件
        FILE_ATTRIBUTE_NORMAL,
        NULL
        );
    if (hFile == INVALID_HANDLE_VALUE)
    {
        printf("CreateFile 失败:%d \n", GetLastError());
        return 0;
    }
​
    //2.创建FileMapping对象
    hMapFile = CreateFileMapping(hFile,
        NULL,
        PAGE_READWRITE,
        0,
        0,
        NULL
        );
    if (hMapFile == NULL)
    {
        printf("CreateFileMapping 失败:%d \n ", GetLastError());
        CloseHandle(hFile);
        return 0;
    }
    //3.映射到虚拟内存
    lpAddr = MapViewOfFile(hMapFile,
        FILE_MAP_COPY,//FILE_MAP_ALL_ACCESS FILE_MAP_COPY
        0, 0, 0);
​
    //4.读取文件
    //DWORD dwTest1 = *(PDWORD)lpAddr;//读文件开始的地方
    //DWORD dwTest2 = *((PDWORD)lpAddr + 0x30);//想读到哪(加上偏移)
    /*printf("%x %x \n", dwTest1, dwTest2);*/
    //5.写文件
    *(PDWORD)lpAddr = 0x41414141;
    printf("A写入: %x\n", *(PDWORD)lpAddr);
    //强制更新缓存
    FlushViewOfFile(((PDWORD)lpAddr), 4);
    //6.关闭资源
    UnmapViewOfFile(lpAddr);
    CloseHandle(hMapFile);
    CloseHandle(hFile);
}
int main(int argc, char* argv[])
​
{
    MappingFile("D:\\DebugView++CHS X32.exe");
    return 0;
}

这个是写到了内存中,然后映射到文件中。

需要注意的是FileMapping对象为了保证效率不是实时写入。比如说:我们写文件执行后他不会立马改,他会写到缓存中。如果我们希望他立马改,我们可以强制跟新缓存。他可以立即把缓存写进文件中。

2.内存映射之共享

 

我们之前讲过两个进程共享一份物理内存,现在同时共享一个文件。

我们先创建一个文件

 

 

下面我们尝试在A进程中修改文件,再在B进程中读取。代码如下

A进程

#include"stdafx.h"
#define MAPPINGNAME "共享文件"
DWORD MappingFile(LPSTR lpcFile)
{
    HANDLE hFile;
    HANDLE hMapFile;
    DWORD dwFileMapSize;
    LPVOID lpAddr;
​
    //1.得到文件句柄
    hFile = CreateFileA(lpcFile,
        GENERIC_READ | GENERIC_WRITE,
        0,
        NULL,
        OPEN_EXISTING,//打开现有文件
        FILE_ATTRIBUTE_NORMAL,
        NULL
        );
    if (hFile == INVALID_HANDLE_VALUE)
    {
        printf("CreateFile 失败:%d \n", GetLastError());
        return 0;
    }
​
    //2.创建FileMapping对象
    hMapFile = CreateFileMapping(hFile,
        NULL,
        PAGE_READWRITE,
        0,
        0,
        MAPPINGNAME
        );
    if (hMapFile == NULL)
    {
        printf("CreateFileMapping 失败:%d \n ", GetLastError());
        CloseHandle(hFile);
        return 0;
    }
    //3.映射到虚拟内存
    lpAddr = MapViewOfFile(hMapFile,
        FILE_MAP_ALL_ACCESS,
        0, 0, 0);
​
    //4.读取文件
    //DWORD dwTest1 = *(PDWORD)lpAddr;//读文件开始的地方
    //DWORD dwTest2 = *((PDWORD)lpAddr + 0x30);//想读到哪(加上偏移)
    /*printf("%x %x \n", dwTest1, dwTest2);*/
    //5.写文件
    *(PDWORD)lpAddr = 0x41414141;//修改文件(4个A)
    printf("A写入: %x\n", *(PDWORD)lpAddr);
    getchar();
    //强制更新缓存
    /*FlushViewOfFile(((PDWORD)lpAddr), 4);*/
    //6.关闭资源
    UnmapViewOfFile(lpAddr);
    CloseHandle(hMapFile);
    CloseHandle(hFile);
}
int main(int argc, char* argv[])
​
{
    MappingFile("D:\\Test.txt");
    return 0;
}

B进程:

#include"stdafx.h"
#define MAPPINGNAME "共享文件"
int main()
{
    HANDLE hFile;
    HANDLE hMapFile;
    DWORD dwFileMapSize;
    LPVOID lpAddr;
​
​
    //1.打开FileMapping对象
    hMapFile = OpenFileMapping(FILE_MAP_ALL_ACCESS, FALSE, MAPPINGNAME);
    if (hMapFile == NULL)
    {
        printf("CreateFileMapping 失败:%d \n ", GetLastError());
        
        return 0;
    }
    //2.映射到虚拟内存
    lpAddr = MapViewOfFile(hMapFile,
        FILE_MAP_ALL_ACCESS,
        0, 0, 0);
​
    //3.读取文件
    DWORD dwTest = *(PDWORD)lpAddr;
    printf("%x \n", dwTest);
    //4.释放资源
    UnmapViewOfFile(lpAddr);
    CloseHandle(hMapFile);
    return 0;
}

运行两个代码先A后B

读取到了:

 

 

发现文件被修改了


我们每一个进程启动时都会映射很多dll。这些dll在物理页上只有一份,而且每个进程都会用这些dll,操作系统不会为每个进程都单独分一块内存,这样就太浪费空间了。我们使用的内存有两种,私有和公有。我们使用的大多都是这种映射的公有的。所以无论是ntdll.dll还是user.dll在物理内存上都只有一份。进程使用时会映射到进程中。

我们刚刚讲到了,两个进程共享一个文件,读的时候没有问题。但是我们写的话怎么办呢?我们映射kerner32.dll是不是意味着我们能修改呢?大家用的是一份那么改的话是不是其他进程就受到影响了呢?

这样就会出现问题。但实际上不会这样。

我们看一下MapViewOfFile函数。其中我们映射文件时用到的是

 

这个。这个参数的意思是,当你修改时会再复制一个物理页。原来的物理页不变

 

 

如上图。当你试图改的时候会复制一个物理页,重新让你指向。你修改的就是复制的物理页。所以当你修改dll时对别人没影响。


版权声明:本文为wzprabbit原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。