最近在学习 Win32编程,所以顺便将每日所学记录下来,一方面为了巩固学习的知识,另一方面也为同样在学习Win32开发的童鞋们提供一份参考。
本系列博文均根据学习《Windows核心编程》一书总结而来;
运行环境:
- 操作系统: Windows 10家庭版
- 编译器:Visual Studio 2019
内存映射文件
与虚拟内存一样,内存映射文件用来保留一个地址空间的区域,并将物理内存提交给它,它们之间的差别是,物理内存来自于一个位于磁盘上的文件,而不是系统文件。一旦该文件被映射,就像整个文件加载内存一样;
内存映射文件可以用于三个不同的目的:
- 系统使用内存映射文件,以便于加载和执行exe或dll文件。这可以节约页文件空间和系统执行时间;
- 可以使用内存映射文件来访问磁盘上的文件数据。
- 可以使用内存映射文件,使同一台计算上运行的多个线程可以共享数据。
内存映射的可执行文件和DLL
当系统调用CreateProcess函数时,系统将执行以下步骤:
- 系统找到调用CreateProcess时设定的.exe文件;
- 系统创建一个进程的内核对象
- 系统为这个新进程创建一个私有地址空间
- 系统保留一个足够大的地址空间区域,用于存放.exe文件。
- 系统注意到支持已保留区域的物理内存是在磁盘上的.exe文件中,而不是在系统的页文件中。
当exe文件被映射到进程的地址空间中以后,系统将访问.exe文件的每一个部分,该文件列举了包含.exe文件中的代码要调用的函数的DLL文件。然后系统为每个DLL文件调用LoadLibrary函数。
当所有的exe和dll都被映射到进程的地址空间以后,系统就可以开始执行exe文件的启动代码。当exe被映射以后,系统将负责所有分页、缓冲和高速缓存的处理。
可执行文件或DLL多个实例不能共享数据
当为正在运行的应用程序创建新实例的时候,系统将打开用于标识可执行文件映像的文件影响对象和另一个内存映像试图,并创建一个新进程对象和一个新线程对象。
系统应用写拷贝这样的特性来防止这种行为,如果我们要尝试修改它的内存映射文件时,系统就会为我们建立一个页文件;
可执行文件或DLL中共享静态数据
每个exe或dll的映像都由许多个节组成。
每个节都拥有与其相关的一组属性。
我们可以借助工具来查看我们的一个EXE的节:
使用内存映射文件
若要使用内存应文件,必须执行以下步骤:
- 创建或打开一个文件内核对象,该对象用于识别磁盘上你想用作内存映射的文件
- 创建一个文件映射内核对象,告诉系统该文件大小和你想如何访问文件
- 让系统将文件映射对象全部或一部分映射到你的进程地址空间中
创建或打开文件内核对象
若要创建或打开一个文件内核对象,总是要调用CreateFile函数:
1 2 3 4 5 6 7
| HANDLE CreateFile(LPCTSTR lpFileName, DWORD dwDesiredAccess, DWORD dwShareMode, LPSECURITY_ATTRIBUTES lpSecurityAttributes, DWORD dwCreationDispostion , DWORD dwFlagsAndAttributes, HANDLE hTemplateFile);
|
创建文件映射内核对象
调用CreateFileMapping函数,就可以将文件映像的物理内存高速系统;
1 2 3 4 5 6
| HANDLE CreateFileMapping(HANDLE hFile, LPSECURITY_ATTRIBUTES lpFileMappingAttributes, DWORD flProtect, DWORD dwMaximumSizeHigh, DWORD dwMaximumSizeLow, LPCTSTR lpName );
|
将文件数据映射到进程地址空间
当创建了一个文件映射对象后,仍然必须让系统为文件保留一个地址空间区域,并将文件的数据作为映射到该区域的物理内存进行提交;
1 2 3 4 5
| LPVOID MapViewOfFile( HANDLE hFileMappingObject, DWORD dwDesiredAccess, DWORD dwFileOffsetHigh, DWORD dwFileOffsetLow, DWORD dwNumberOfBytesToMap );
|
从进程地址空间撤销映射
当不再需要保留映射到进程地址空间中的文件数据时,可以调用以下函数释放:
1
| BOOL UnmapViewOfFile( LPCVOID lpBaseAddress );
|
为了提高效率,系统将文件的数据页面缓存,并在文件的映像操作的时候不立即更新;我们可以利用FlushViewOfFile
1 2
| BOOL FlushViewOfFile( LPCVOID lpBaseAddress, DWORD dwNumberOfBytesToFlush );
|
设定内存映射文件的基地址
正如可以使用VirtualAlloc函数来确定对地址空间进行倒叙所用的起始地址一样;
1 2 3 4 5 6 7
| LPVOID MapViewOfFileEx( HANDLE hFileMappingObject, DWORD dwDesiredAccess, DWORD dwFileOffsetHigh, DWORD dwFileOffsetLow, SIZE_T dwNumberOfBytesToMap, LPVOID lpBaseAddress
|
demo01:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73
| #include <Windows.h> #include <iostream> #include <stdio.h> using namespace std;
int main() { HANDLE hFile = CreateFile ( TEXT("gushi.txt"), GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL );
if (hFile == INVALID_HANDLE_VALUE) { cout << "打开文件失败:" << GetLastError() << endl; return -1; }
HANDLE hMapFile = CreateFileMapping ( hFile, NULL, PAGE_READONLY, 0, 0, TEXT("文件映射")
);
if (hMapFile == NULL) { cout << "创建文件映射失败:" << GetLastError() << endl; return -2; }
LPVOID lpDw = MapViewOfFile ( hMapFile, FILE_MAP_READ, 0, 0, 0 );
if (lpDw == NULL) { cout << "链接物理页失败:" << GetLastError() << endl; return -3; }
printf("%ls", lpDw); getchar(); UnmapViewOfFile(lpDw); CloseHandle(hFile); CloseHandle(hMapFile); return 0; }
|
demo02:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| #include <Windows.h> #include <iostream> #include <stdio.h> using namespace std;
int main() { HANDLE hFile = OpenFileMapping ( FILE_MAP_READ, FALSE, TEXT("文件映射") );
if (hFile == NULL) { cout << "打开文件映射对象失败:" << GetLastError() << endl; return -1; }
LPVOID lpDw = MapViewOfFile(hFile, FILE_MAP_READ, 0, 0, 0);
printf("%ls\n",(LPCSTR)lpDw);
UnmapViewOfFile(lpDw); CloseHandle(hFile);
return 0; }
|
共享内存
共享内存的申请与释放
共享内存
通过VirtualAlloc或VirtualAllocEx申请的内存我们称为私有内存,它的物理页是独占的,只有在进程内部可以访问,但是我们可以通过Mapping的方式来映射内存,达到两个进程共享一个物理页;
Mapping
1 2 3 4 5 6 7 8 9
| CreateFileMapping( INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, 3...=---------------""""""""???????????????????????????????????? BUFSIZ, MapFileName );
|
1 2 3 4 5 6 7
| g_lpBuff = (LPTSTR)MapViewOfFile( g_hMapFile, FILE_MAP_ALL_ACCESS, 0, 0, BUFSIZ );
|
demo01:·
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| #include <Windows.h> #include <stdio.h> #include <tchar.h>
#define MapFileName "共享内存" #define BUF_SIZE 0x1000 HANDLE g_hMapFile; LPTSTR g_lpBuff;
int main() { getchar(); HANDLE g_hMapFile = CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, BUFSIZ, L"MapFileName");
LPTSTR g_lpBuff = (LPTSTR)MapViewOfFile(g_hMapFile, FILE_MAP_ALL_ACCESS, 0, 0, BUFSIZ); DWORD dwError = GetLastError(); *(PDWORD)g_lpBuff = 0x12345678; printf("%p", g_lpBuff); getchar(); UnmapViewOfFile(g_lpBuff); CloseHandle(g_hMapFile); getchar(); return 0; }
|
demo02:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| #include "windows.h" #include <tchar.h> #include <stdio.h>
#define MapFileName "共享内存" #define BUF_SIZE 0x1000 HANDLE g_hMapFile; LPTSTR g_lpBuff;
int main(int argc, char* argv[]) {
g_hMapFile = CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, BUF_SIZE, L"MapFileName"); g_lpBuff = (LPTSTR)MapViewOfFile(g_hMapFile, FILE_MAP_ALL_ACCESS, 0, 0, BUFSIZ);
printf("进程B:%x", *(PDWORD)g_lpBuff); UnmapViewOfFile(g_lpBuff); CloseHandle(g_hMapFile); getchar(); return 0; }
|