1、课程目标
- 理解文件映射的原理和优势
- 掌握CreateFileMapping和MapViewOfFile的使用
- 实现基于文件映射的进程间通信
- 学会使用命名文件映射对象共享数据
2、名词解释
| 术语 |
解释 |
| 文件映射 |
将文件内容映射到进程虚拟地址空间的技术 |
| 内存映射文件 |
通过内存访问方式读写文件的机制 |
| 页面文件 |
系统虚拟内存交换文件pagefile.sys |
| 命名映射对象 |
可跨进程共享的有名称的文件映射对象 |
| 视图 |
映射到进程地址空间的文件区域 |
3、使用工具
| 工具 |
用途 |
| Visual Studio |
编译调试程序 |
| Process Explorer |
查看进程句柄和映射 |
| VMMap |
分析进程虚拟内存布局 |
4、技术原理
4.1、文件映射机制
文件映射原理:
┌─────────────────────────────────────────────────┐
│ 物理内存 │
│ ┌─────────────────────────────────────────┐ │
│ │ 共享内存页面 │ │
│ └─────────────────────────────────────────┘ │
│ ↑ ↑ │
│ │ │ │
│ ┌─────┴─────┐ ┌────┴─────┐ │
│ │ 进程A视图 │ │ 进程B视图 │ │
│ └───────────┘ └──────────┘ │
│ │
│ CreateFileMapping() → 创建/打开映射对象 │
│ MapViewOfFile() → 映射到进程地址空间 │
│ UnmapViewOfFile() → 解除映射 │
│ CloseHandle() → 关闭映射对象 │
└─────────────────────────────────────────────────┘
4.2、文件映射与普通IPC对比
| 特性 |
文件映射 |
管道 |
邮槽 |
| 速度 |
最快(直接内存访问) |
中等 |
较慢 |
| 数据持久性 |
可选(可基于文件) |
否 |
否 |
| 通信方式 |
双向 |
单向/双向 |
单向 |
| 随机访问 |
支持 |
顺序 |
顺序 |
5、代码实现
5.1、示例1:基础文件映射 - 写入端
// FileMapWriter.c - 文件映射写入端
#include <windows.h>
#include <stdio.h>
#define SHARED_MEM_NAME "Global\\MySharedMemory"
#define SHARED_MEM_SIZE 4096
typedef struct _SHARED_DATA {
DWORD dwMagic; // 魔数验证
DWORD dwDataSize; // 数据大小
DWORD dwSequence; // 序列号
CHAR szData[4000]; // 实际数据
} SHARED_DATA, *PSHARED_DATA;
int main() {
HANDLE hMapFile = NULL;
PSHARED_DATA pSharedData = NULL;
// 创建文件映射对象(基于页面文件)
hMapFile = CreateFileMappingA(
INVALID_HANDLE_VALUE, // 使用页面文件
NULL, // 默认安全属性
PAGE_READWRITE, // 可读写
0, // 高32位大小
SHARED_MEM_SIZE, // 低32位大小
SHARED_MEM_NAME // 映射对象名称
);
if (hMapFile == NULL) {
printf("CreateFileMapping failed: %d\n", GetLastError());
return 1;
}
// 将文件映射到进程地址空间
pSharedData = (PSHARED_DATA)MapViewOfFile(
hMapFile, // 映射对象句柄
FILE_MAP_ALL_ACCESS, // 完全访问权限
0, 0, // 偏移量
SHARED_MEM_SIZE // 映射大小
);
if (pSharedData == NULL) {
printf("MapViewOfFile failed: %d\n", GetLastError());
CloseHandle(hMapFile);
return 1;
}
printf("[Writer] Shared memory created at: 0x%p\n", pSharedData);
printf("[Writer] Press Enter to write data...\n");
getchar();
// 写入共享数据
pSharedData->dwMagic = 0xDEADBEEF;
pSharedData->dwDataSize = strlen("Hello from Writer!") + 1;
pSharedData->dwSequence = 1;
strcpy(pSharedData->szData, "Hello from Writer!");
printf("[Writer] Data written. Sequence: %d\n", pSharedData->dwSequence);
printf("[Writer] Press Enter to update data...\n");
getchar();
// 更新数据
pSharedData->dwSequence = 2;
strcpy(pSharedData->szData, "Updated message from Writer!");
pSharedData->dwDataSize = strlen(pSharedData->szData) + 1;
printf("[Writer] Data updated. Sequence: %d\n", pSharedData->dwSequence);
printf("[Writer] Press Enter to exit...\n");
getchar();
// 清理资源
UnmapViewOfFile(pSharedData);
CloseHandle(hMapFile);
return 0;
}
5.2、示例2:基础文件映射 - 读取端
// FileMapReader.c - 文件映射读取端
#include <windows.h>
#include <stdio.h>
#define SHARED_MEM_NAME "Global\\MySharedMemory"
#define SHARED_MEM_SIZE 4096
typedef struct _SHARED_DATA {
DWORD dwMagic;
DWORD dwDataSize;
DWORD dwSequence;
CHAR szData[4000];
} SHARED_DATA, *PSHARED_DATA;
int main() {
HANDLE hMapFile = NULL;
PSHARED_DATA pSharedData = NULL;
DWORD lastSequence = 0;
// 打开已存在的文件映射对象
hMapFile = OpenFileMappingA(
FILE_MAP_READ, // 只读访问
FALSE, // 不继承句柄
SHARED_MEM_NAME // 映射对象名称
);
if (hMapFile == NULL) {
printf("OpenFileMapping failed: %d\n", GetLastError());
printf("Please start Writer first.\n");
return 1;
}
// 映射视图
pSharedData = (PSHARED_DATA)MapViewOfFile(
hMapFile,
FILE_MAP_READ, // 只读映射
0, 0,
SHARED_MEM_SIZE
);
if (pSharedData == NULL) {
printf("MapViewOfFile failed: %d\n", GetLastError());
CloseHandle(hMapFile);
return 1;
}
printf("[Reader] Mapped to shared memory at: 0x%p\n", pSharedData);
// 持续读取数据
while (1) {
// 检查魔数
if (pSharedData->dwMagic != 0xDEADBEEF) {
printf("[Reader] Waiting for valid data...\n");
Sleep(1000);
continue;
}
// 检查是否有新数据
if (pSharedData->dwSequence != lastSequence) {
lastSequence = pSharedData->dwSequence;
printf("[Reader] New data (Seq=%d): %s\n",
pSharedData->dwSequence,
pSharedData->szData);
}
Sleep(500);
// 按ESC退出
if (GetAsyncKeyState(VK_ESCAPE) & 0x8000)
break;
}
UnmapViewOfFile(pSharedData);
CloseHandle(hMapFile);
return 0;
}
5.3、示例3:带同步的共享内存通信
// SyncSharedMem.h - 共享头文件
#ifndef SYNC_SHARED_MEM_H
#define SYNC_SHARED_MEM_H
#define SHM_NAME "Local\\SyncSharedMem"
#define MUTEX_NAME "Local\\SyncSharedMemMutex"
#define EVENT_WRITE "Local\\SyncSharedMemEventWrite"
#define EVENT_READ "Local\\SyncSharedMemEventRead"
#define SHM_SIZE 8192
typedef struct _COMMAND_PACKET {
DWORD dwCommand; // 命令类型
DWORD dwParam1; // 参数1
DWORD dwParam2; // 参数2
DWORD dwDataLen; // 数据长度
BYTE bData[8000]; // 数据缓冲区
} COMMAND_PACKET, *PCOMMAND_PACKET;
// 命令定义
#define CMD_PING 1
#define CMD_EXECUTE 2
#define CMD_DOWNLOAD 3
#define CMD_UPLOAD 4
#define CMD_EXIT 0xFF
#endif
// SyncServer.c - 同步共享内存服务端
#include <windows.h>
#include <stdio.h>
#include "SyncSharedMem.h"
int main() {
HANDLE hMapFile = NULL;
HANDLE hMutex = NULL;
HANDLE hEventWrite = NULL;
HANDLE hEventRead = NULL;
PCOMMAND_PACKET pPacket = NULL;
BOOL bRunning = TRUE;
// 创建同步对象
hMutex = CreateMutexA(NULL, FALSE, MUTEX_NAME);
hEventWrite = CreateEventA(NULL, FALSE, FALSE, EVENT_WRITE);
hEventRead = CreateEventA(NULL, FALSE, FALSE, EVENT_READ);
// 创建共享内存
hMapFile = CreateFileMappingA(
INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE,
0, SHM_SIZE, SHM_NAME);
if (!hMapFile || !hMutex || !hEventWrite || !hEventRead) {
printf("Failed to create resources: %d\n", GetLastError());
return 1;
}
pPacket = (PCOMMAND_PACKET)MapViewOfFile(
hMapFile, FILE_MAP_ALL_ACCESS, 0, 0, SHM_SIZE);
printf("[Server] Ready and waiting for commands...\n");
while (bRunning) {
// 等待客户端写入命令
DWORD dwWait = WaitForSingleObject(hEventWrite, 5000);
if (dwWait == WAIT_TIMEOUT)
continue;
// 获取互斥锁
WaitForSingleObject(hMutex, INFINITE);
// 处理命令
switch (pPacket->dwCommand) {
case CMD_PING:
printf("[Server] Received PING\n");
strcpy((char*)pPacket->bData, "PONG");
pPacket->dwDataLen = 5;
break;
case CMD_EXECUTE:
printf("[Server] Execute: %s\n", pPacket->bData);
// 模拟执行命令
sprintf((char*)pPacket->bData, "Executed: %d", pPacket->dwParam1);
pPacket->dwDataLen = strlen((char*)pPacket->bData) + 1;
break;
case CMD_EXIT:
printf("[Server] Exit command received\n");
bRunning = FALSE;
strcpy((char*)pPacket->bData, "Goodbye!");
pPacket->dwDataLen = 9;
break;
default:
printf("[Server] Unknown command: %d\n", pPacket->dwCommand);
strcpy((char*)pPacket->bData, "Unknown command");
pPacket->dwDataLen = 16;
}
ReleaseMutex(hMutex);
// 通知客户端可以读取响应
SetEvent(hEventRead);
}
// 清理
UnmapViewOfFile(pPacket);
CloseHandle(hMapFile);
CloseHandle(hMutex);
CloseHandle(hEventWrite);
CloseHandle(hEventRead);
return 0;
}
// SyncClient.c - 同步共享内存客户端
#include <windows.h>
#include <stdio.h>
#include "SyncSharedMem.h"
BOOL SendCommand(PCOMMAND_PACKET pPacket, HANDLE hMutex,
HANDLE hEventWrite, HANDLE hEventRead,
DWORD dwCmd, DWORD dwP1, DWORD dwP2,
const char* data) {
WaitForSingleObject(hMutex, INFINITE);
pPacket->dwCommand = dwCmd;
pPacket->dwParam1 = dwP1;
pPacket->dwParam2 = dwP2;
if (data) {
strcpy((char*)pPacket->bData, data);
pPacket->dwDataLen = strlen(data) + 1;
}
ReleaseMutex(hMutex);
// 通知服务端
SetEvent(hEventWrite);
// 等待响应
if (WaitForSingleObject(hEventRead, 5000) == WAIT_TIMEOUT) {
printf("Timeout waiting for response\n");
return FALSE;
}
return TRUE;
}
int main() {
HANDLE hMapFile = OpenFileMappingA(FILE_MAP_ALL_ACCESS, FALSE, SHM_NAME);
HANDLE hMutex = OpenMutexA(SYNCHRONIZE | MUTEX_MODIFY_STATE, FALSE, MUTEX_NAME);
HANDLE hEventWrite = OpenEventA(EVENT_MODIFY_STATE, FALSE, EVENT_WRITE);
HANDLE hEventRead = OpenEventA(SYNCHRONIZE, FALSE, EVENT_READ);
if (!hMapFile || !hMutex || !hEventWrite || !hEventRead) {
printf("Failed to open resources. Start server first.\n");
return 1;
}
PCOMMAND_PACKET pPacket = (PCOMMAND_PACKET)MapViewOfFile(
hMapFile, FILE_MAP_ALL_ACCESS, 0, 0, SHM_SIZE);
printf("[Client] Connected to server\n");
// 发送PING
if (SendCommand(pPacket, hMutex, hEventWrite, hEventRead,
CMD_PING, 0, 0, NULL)) {
printf("[Client] Response: %s\n", pPacket->bData);
}
// 发送执行命令
if (SendCommand(pPacket, hMutex, hEventWrite, hEventRead,
CMD_EXECUTE, 123, 0, "calc.exe")) {
printf("[Client] Response: %s\n", pPacket->bData);
}
// 发送退出
if (SendCommand(pPacket, hMutex, hEventWrite, hEventRead,
CMD_EXIT, 0, 0, NULL)) {
printf("[Client] Response: %s\n", pPacket->bData);
}
UnmapViewOfFile(pPacket);
CloseHandle(hMapFile);
CloseHandle(hMutex);
CloseHandle(hEventWrite);
CloseHandle(hEventRead);
return 0;
}
5.4、示例4:基于真实文件的内存映射
// FileMapping.c - 映射真实文件
#include <windows.h>
#include <stdio.h>
void ReadFileWithMapping(const char* filePath) {
HANDLE hFile = CreateFileA(
filePath,
GENERIC_READ,
FILE_SHARE_READ,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
NULL);
if (hFile == INVALID_HANDLE_VALUE) {
printf("Cannot open file: %d\n", GetLastError());
return;
}
DWORD dwFileSize = GetFileSize(hFile, NULL);
printf("File size: %d bytes\n", dwFileSize);
HANDLE hMap = CreateFileMappingA(
hFile,
NULL,
PAGE_READONLY,
0, 0, // 映射整个文件
NULL); // 无名称
if (hMap == NULL) {
printf("CreateFileMapping failed: %d\n", GetLastError());
CloseHandle(hFile);
return;
}
// 映射视图
LPVOID pView = MapViewOfFile(hMap, FILE_MAP_READ, 0, 0, 0);
if (pView == NULL) {
printf("MapViewOfFile failed: %d\n", GetLastError());
CloseHandle(hMap);
CloseHandle(hFile);
return;
}
// 直接访问文件内容
printf("First 100 bytes:\n");
for (DWORD i = 0; i < min(100, dwFileSize); i++) {
printf("%02X ", ((BYTE*)pView)[i]);
if ((i + 1) % 16 == 0) printf("\n");
}
printf("\n");
// 搜索特定模式
BYTE pattern[] = { 0x4D, 0x5A }; // MZ
if (dwFileSize >= 2 &&
memcmp(pView, pattern, 2) == 0) {
printf("This is a PE file!\n");
}
UnmapViewOfFile(pView);
CloseHandle(hMap);
CloseHandle(hFile);
}
void ModifyFileWithMapping(const char* filePath) {
HANDLE hFile = CreateFileA(
filePath,
GENERIC_READ | GENERIC_WRITE,
0,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
NULL);
if (hFile == INVALID_HANDLE_VALUE) {
printf("Cannot open file: %d\n", GetLastError());
return;
}
HANDLE hMap = CreateFileMappingA(
hFile, NULL, PAGE_READWRITE,
0, 0, NULL);
LPVOID pView = MapViewOfFile(hMap, FILE_MAP_WRITE, 0, 0, 0);
// 修改文件内容
// 注意:修改会直接反映到文件
((BYTE*)pView)[0] = 0xFF;
// 刷新到磁盘
FlushViewOfFile(pView, 0);
UnmapViewOfFile(pView);
CloseHandle(hMap);
CloseHandle(hFile);
}
int main(int argc, char* argv[]) {
if (argc < 2) {
printf("Usage: %s <file>\n", argv[0]);
return 1;
}
ReadFileWithMapping(argv[1]);
return 0;
}
6、课后作业
- 实现一个基于文件映射的进程间命令执行器
- 编写程序使用文件映射读取PE文件并解析DOS头和NT头
- 实现带超时和重试机制的共享内存通信库
- 对比文件映射和ReadFile/WriteFile的性能差异
7、扩展阅读
- MSDN: File Mapping Functions
- Windows Internals: Memory Management
- 内存映射文件的安全性考虑