进程通信专题

5、文件映射

1、课程目标

  1. 理解文件映射的原理和优势
  2. 掌握CreateFileMapping和MapViewOfFile的使用
  3. 实现基于文件映射的进程间通信
  4. 学会使用命名文件映射对象共享数据

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、课后作业

  1. 实现一个基于文件映射的进程间命令执行器
  2. 编写程序使用文件映射读取PE文件并解析DOS头和NT头
  3. 实现带超时和重试机制的共享内存通信库
  4. 对比文件映射和ReadFile/WriteFile的性能差异

7、扩展阅读

  • MSDN: File Mapping Functions
  • Windows Internals: Memory Management
  • 内存映射文件的安全性考虑