Reactos 第 5 章 进程与线程 — 5.13 Windows的跨进程操作

第 5 章 进程与线程 --- 5.13 Windows的跨进程操作

本节深入剖析 Windows/ReactOS 中跨进程操作(Inter-Process Communication, IPC)机制的完整实现。

概述

跨进程操作是现代操作系统中实现进程间协作的核心机制。由于 Windows 采用严格的进程隔离模型,每个进程拥有独立的地址空间,进程间无法直接访问彼此的内存。为了实现进程间的数据交换和协作,Windows 提供了多种 IPC 机制。

跨进程操作的本质是什么?

跨进程操作是一种打破进程隔离的受控通道,在保证安全性的前提下,允许进程间进行数据交换、资源共享和同步协调。IPC 机制的设计需要在安全性、性能和易用性之间取得平衡。

想象一个工厂场景:

  • 跨进程内存操作:工人(进程)需要查看其他车间(进程)的库存清单,需要特殊权限才能进入;
  • 共享内存:多个车间共用一个仓库,可以同时存取货物;
  • 管道通信:车间之间通过管道传输原材料,单向或双向流动;
  • 邮槽:广播通知系统,向所有车间发送消息;
  • LPC:车间之间的电话系统,可以进行复杂的对话和请求-响应交互;
  • 句柄复制:将一个车间的工具(句柄)借给另一个车间使用。

本节内容概览

  1. 5.13.0 框架图:Windows跨进程操作完整架构图;
  2. 5.13.1 概述:跨进程操作的定义、IPC机制分类;
  3. 5.13.2 跨进程内存操作:VirtualAllocEx、ReadProcessMemory/WriteProcessMemory;
  4. 5.13.3 共享内存机制:CreateFileMapping、MapViewOfFile;
  5. 5.13.4 管道通信:匿名管道和命名管道;
  6. 5.13.5 邮槽机制:单向广播通信;
  7. 5.13.6 LPC机制:本地过程调用;
  8. 5.13.7 句柄复制:DuplicateHandle;
  9. 5.13.8 IPC机制对比与选择:性能和适用场景分析;
  10. 5.13.9 设计哲学问答:10个关键设计问题的深入解答。

学习目标

读完本节后,读者应当能够:

  • 理解进程隔离与IPC机制的矛盾与统一;
  • 掌握跨进程内存操作API的实现原理;
  • 分析共享内存机制的内核实现;
  • 理解管道和邮槽通信的工作原理;
  • 掌握LPC机制的消息传递模型;
  • 解释句柄复制的权限要求;
  • 根据实际需求选择合适的IPC机制。

涉及的内核子系统

子系统 职责
kernel32 用户态IPC API(ReadProcessMemory、CreateFileMapping、CreatePipe等)
ntoskrnl/mm 内存管理(MmCopyVirtualMemory、Section对象)
ntoskrnl/ob 对象管理(NtDuplicateObject、句柄表)
ntoskrnl/lpc LPC机制(NtCreatePort、NtConnectPort等)
npfs 命名管道文件系统驱动
msfs 邮槽文件系统驱动

5.13.0 框架图

复制代码
┌──────────────────────────────────────────────────────────────────────────────────────┐
│                      Windows 跨进程操作完整架构                                        │
├──────────────────────────────────────────────────────────────────────────────────────┤
│                                                                                      │
│   用户态 API 层                                                                       │
│   ┌────────────────────────────────────────────────────────────────────────────┐     │
│   │  kernel32.dll                                                              │     │
│   │  ├─► 跨进程内存: VirtualAllocEx, ReadProcessMemory, WriteProcessMemory    │     │
│   │  ├─► 共享内存: CreateFileMapping, MapViewOfFile, OpenFileMapping          │     │
│   │  ├─► 管道: CreatePipe, CreateNamedPipe, ConnectNamedPipe                  │     │
│   │  ├─► 邮槽: CreateMailslot, GetMailslotInfo                                │     │
│   │  └─► 句柄复制: DuplicateHandle                                            │     │
│   └────────────────────────────────────────────────────────────────────────────┘     │
│                              │                                                       │
│                              ▼                                                       │
│   NT Native API 层                                                                   │
│   ┌────────────────────────────────────────────────────────────────────────────┐     │
│   │  ntdll.dll                                                                 │     │
│   │  ├─► 内存: NtAllocateVirtualMemory, NtReadVirtualMemory, NtWriteVirtualMemory │ │
│   │  ├─► Section: NtCreateSection, NtMapViewOfSection, NtOpenSection           │     │
│   │  ├─► 管道: NtCreateNamedPipeFile, NtFsControlFile                          │     │
│   │  ├─► 邮槽: NtCreateMailslotFile                                            │     │
│   │  ├─► LPC: NtCreatePort, NtConnectPort, NtRequestPort, NtReplyPort          │     │
│   │  └─► 句柄: NtDuplicateObject                                               │     │
│   └────────────────────────────────────────────────────────────────────────────┘     │
│                              │                                                       │
│                              ▼                                                       │
│   内核态实现层                                                                       │
│   ┌────────────────────────────────────────────────────────────────────────────┐     │
│   │  ntoskrnl                                                                 │     │
│   │  ├─► mm/ARM3/virtual.c                                                    │     │
│   │  │    ├─► NtReadVirtualMemory (2781行)                                    │     │
│   │  │    ├─► NtWriteVirtualMemory (2895行)                                   │     │
│   │  │    └─► MmCopyVirtualMemory (1271行)                                    │     │
│   │  ├─► mm/section.c                                                         │     │
│   │  │    ├─► NtCreateSection                                                 │     │
│   │  │    └─► NtMapViewOfSection                                              │     │
│   │  ├─► ob/obhandle.c                                                        │     │
│   │  │    └─► NtDuplicateObject (3410行)                                      │     │
│   │  └─► lpc/                                                                 │     │
│   │       ├─► create.c: NtCreatePort                                          │     │
│   │       ├─► connect.c: NtConnectPort                                        │     │
│   │       ├─► send.c: NtRequestPort                                           │     │
│   │       └─► reply.c: NtReplyPort                                            │     │
│   └────────────────────────────────────────────────────────────────────────────┘     │
│                              │                                                       │
│                              ▼                                                       │
│   文件系统驱动层                                                                     │
│   ┌────────────────────────────────────────────────────────────────────────────┐     │
│   │  npfs.sys (命名管道文件系统)                                               │     │
│   │  ├─► IRP_MJ_CREATE: 创建管道实例                                          │     │
│   │  ├─► IRP_MJ_READ/WRITE: 管道数据传输                                      │     │
│   │  └─► FSCTL_PIPE_LISTEN/TRANSCEIVE: 连接和事务                             │     │
│   │                                                                            │     │
│   │  msfs.sys (邮槽文件系统)                                                   │     │
│   │  ├─► IRP_MJ_CREATE: 创建邮槽                                              │     │
│   │  └─► IRP_MJ_READ: 读取邮槽消息                                            │     │
│   └────────────────────────────────────────────────────────────────────────────┘     │
│                                                                                      │
│   IPC 机制分类                                                                       │
│   ┌────────────────────────────────────────────────────────────────────────────┐     │
│   │  ┌─────────────────┐  ┌─────────────────┐  ┌─────────────────┐            │     │
│   │  │   直接内存访问   │  │   共享内存机制   │  │   消息传递机制   │            │     │
│   │  │ ReadProcessMemory│  │ Section对象     │  │ 管道/邮槽/LPC  │            │     │
│   │  │ WriteProcessMemory│ │ MapViewOfFile   │  │ PORT_MESSAGE   │            │     │
│   │  │ 需要特殊权限     │  │ 高性能共享     │  │ 结构化通信     │            │     │
│   │  └─────────────────┘  └─────────────────┘  └─────────────────┘            │     │
│   │                                                                            │     │
│   │  ┌─────────────────┐  ┌─────────────────┐                                │     │
│   │  │   句柄传递机制   │  │   同步机制      │                                │     │
│   │  │ DuplicateHandle │  │ 事件/互斥体/信号量│                                │     │
│   │  │ 跨进程资源共享   │  │ 跨进程同步     │                                │     │
│   │  └─────────────────┘  └─────────────────┘                                │     │
│   └────────────────────────────────────────────────────────────────────────────┘     │
│                                                                                      │
└──────────────────────────────────────────────────────────────────────────────────────┘

5.13.1 概述

5.13.1.1 进程隔离与IPC的矛盾

Windows 操作系统采用严格的进程隔离模型:

  1. 地址空间隔离:每个进程拥有独立的 4GB(32位)虚拟地址空间;
  2. 资源隔离:进程的句柄、线程、内存等资源默认不共享;
  3. 权限隔离:进程只能访问自己拥有的资源。

这种隔离带来了安全性,但也产生了协作需求:

  • 数据交换:进程间需要传递数据;
  • 资源共享:多个进程需要访问同一资源;
  • 同步协调:进程间需要协调执行顺序;
  • 服务请求:客户端进程需要请求服务端进程的服务。

5.13.1.2 IPC机制分类

Windows 提供的 IPC 机制可以按以下维度分类:

按通信方式分类:

类型 机制 特点
直接内存访问 ReadProcessMemory/WriteProcessMemory 直接读写目标进程内存,需要特殊权限
共享内存 Section对象、MapViewOfFile 多进程映射同一内存区域,高性能
消息传递 管道、邮槽、LPC 通过消息队列传递数据,结构化
句柄传递 DuplicateHandle 跨进程共享句柄

按通信方向分类:

类型 机制 特点
双向通信 命名管道、LPC、共享内存 双方都可以发送和接收
单向通信 匿名管道、邮槽 只能单向传输数据

按通信范围分类:

类型 机制 特点
本地通信 LPC、匿名管道 仅限同一机器
网络通信 命名管道(远程)、邮槽 可跨机器通信

5.13.1.3 IPC机制选择指南

复制代码
IPC 选择决策树
┌─────────────────────────────────────────────────────────────────┐
│ 问题:需要什么样的IPC?                                         │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│ 1. 是否需要高性能大量数据传输?                                 │
│    ├─► 是 → 使用共享内存(Section对象)                        │
│    │    └─► 需要同步?→ 配合互斥体/事件                        │
│    └─► 否 → 继续判断                                           │
│                                                                 │
│ 2. 是否需要请求-响应模式?                                      │
│    ├─► 是 → 使用LPC或命名管道                                  │
│    │    ├─► 本地高性能?→ LPC                                  │
│    │    └─► 需要网络支持?→ 命名管道                           │
│    └─► 否 → 继续判断                                           │
│                                                                 │
│ 3. 是否需要广播消息?                                           │
│    ├─► 是 → 使用邮槽                                           │
│    └─► 否 → 继续判断                                           │
│                                                                 │
│ 4. 是否需要简单父子进程通信?                                   │
│    ├─► 是 → 使用匿名管道                                       │
│    └─► 否 → 继续判断                                           │
│                                                                 │
│ 5. 是否需要直接访问目标进程内存?                               │
│    ├─► 是 → 使用ReadProcessMemory/WriteProcessMemory           │
│    │    └─► 需要调试权限                                       │
│    └─► 否 → 继续判断                                           │
│                                                                 │
│ 6. 是否需要共享句柄资源?                                       │
│    ├─► 是 → 使用DuplicateHandle                                │
│    └─► 否 → 重新评估需求                                       │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

5.13.2 跨进程内存操作

5.13.2.1 VirtualAllocEx/VirtualFreeEx

VirtualAllocEx 允许在目标进程中分配内存:

c 复制代码
LPVOID
NTAPI
VirtualAllocEx(IN HANDLE hProcess,
               IN LPVOID lpAddress,
               IN SIZE_T dwSize,
               IN DWORD flAllocationType,
               IN DWORD flProtect)
{
    NTSTATUS Status;

    /* 确保地址在系统分配粒度范围内(64KB) */
    if ((lpAddress != NULL) &&
        (lpAddress < UlongToPtr(BaseStaticServerData->SysInfo.AllocationGranularity)))
    {
        SetLastError(ERROR_INVALID_PARAMETER);
        return NULL;
    }

    /* 调用内核API分配内存 */
    Status = NtAllocateVirtualMemory(hProcess,
                                     &lpAddress,
                                     0,
                                     &dwSize,
                                     flAllocationType,
                                     flProtect);

    if (!NT_SUCCESS(Status))
    {
        BaseSetLastNTError(Status);
        return NULL;
    }

    return lpAddress;
}

源码位置dll/win32/kernel32/client/virtmem.c#L23(file:///d:/reactos/dll/win32/kernel32/client/virtmem.c#L23)

参数说明:

参数 说明
hProcess 目标进程句柄,需要 PROCESS_VM_OPERATION 权限
lpAddress 期望的分配地址,NULL 表示系统自动选择
dwSize 分配大小
flAllocationType 分配类型(MEM_COMMIT、MEM_RESERVE)
flProtect 内存保护属性(PAGE_READWRITE等)

5.13.2.2 ReadProcessMemory/WriteProcessMemory

ReadProcessMemory 从目标进程读取数据:

c 复制代码
NTSTATUS
NTAPI
NtReadVirtualMemory(IN HANDLE ProcessHandle,
                    IN PVOID BaseAddress,
                    OUT PVOID Buffer,
                    IN SIZE_T NumberOfBytesToRead,
                    OUT PSIZE_T NumberOfBytesRead OPTIONAL)
{
    KPROCESSOR_MODE PreviousMode = ExGetPreviousMode();
    PEPROCESS Process;
    NTSTATUS Status = STATUS_SUCCESS;
    SIZE_T BytesRead = 0;

    /* 用户态参数验证 */
    if (PreviousMode != KernelMode)
    {
        /* 检查地址范围,防止写入内核空间 */
        if ((((ULONG_PTR)BaseAddress + NumberOfBytesToRead) < (ULONG_PTR)BaseAddress) ||
            (((ULONG_PTR)Buffer + NumberOfBytesToRead) < (ULONG_PTR)Buffer) ||
            (((ULONG_PTR)BaseAddress + NumberOfBytesToRead) > MmUserProbeAddress) ||
            (((ULONG_PTR)Buffer + NumberOfBytesToRead) > MmUserProbeAddress))
        {
            return STATUS_ACCESS_VIOLATION;
        }

        /* 探测输出参数 */
        if (NumberOfBytesRead) ProbeForWriteSize_t(NumberOfBytesRead);
    }

    /* 引用目标进程 */
    if (NumberOfBytesToRead)
    {
        Status = ObReferenceObjectByHandle(ProcessHandle,
                                           PROCESS_VM_READ,
                                           PsProcessType,
                                           PreviousMode,
                                           (PVOID*)(&Process),
                                           NULL);
        if (NT_SUCCESS(Status))
        {
            /* 执行内存复制 */
            Status = MmCopyVirtualMemory(Process,
                                         BaseAddress,
                                         PsGetCurrentProcess(),
                                         Buffer,
                                         NumberOfBytesToRead,
                                         PreviousMode,
                                         &BytesRead);

            ObDereferenceObject(Process);
        }
    }

    /* 返回实际读取的字节数 */
    if (NumberOfBytesRead)
    {
        *NumberOfBytesRead = BytesRead;
    }

    return Status;
}

源码位置ntoskrnl/mm/ARM3/virtual.c#L2781(file:///d:/reactos/ntoskrnl/mm/ARM3/virtual.c#L2781)

5.13.2.3 MmCopyVirtualMemory内核实现

MmCopyVirtualMemory 是跨进程内存复制的核心函数:

c 复制代码
NTSTATUS
NTAPI
MmCopyVirtualMemory(IN PEPROCESS SourceProcess,
                    IN PVOID SourceAddress,
                    IN PEPROCESS TargetProcess,
                    OUT PVOID TargetAddress,
                    IN SIZE_T BufferSize,
                    IN KPROCESSOR_MODE PreviousMode,
                    OUT PSIZE_T ReturnSize)
{
    NTSTATUS Status;
    PEPROCESS Process = SourceProcess;

    /* 拒绝零大小传输 */
    if (!BufferSize) return STATUS_SUCCESS;

    /* 如果源进程是当前进程,锁定目标进程 */
    if (SourceProcess == PsGetCurrentProcess()) Process = TargetProcess;

    /* 获取进程停止保护 */
    if (!ExAcquireRundownProtection(&Process->RundownProtect))
    {
        return STATUS_PROCESS_IS_TERMINATING;
    }

    /* 根据数据大小选择复制方式 */
    if (BufferSize > MI_POOL_COPY_BYTES)
    {
        /* 大数据:使用MDL映射方式 */
        Status = MiDoMappedCopy(SourceProcess,
                                SourceAddress,
                                TargetProcess,
                                TargetAddress,
                                BufferSize,
                                PreviousMode,
                                ReturnSize);
    }
    else
    {
        /* 小数据:使用池缓冲区复制 */
        Status = MiDoPoolCopy(SourceProcess,
                              SourceAddress,
                              TargetProcess,
                              TargetAddress,
                              BufferSize,
                              PreviousMode,
                              ReturnSize);
    }

    /* 释放停止保护 */
    ExReleaseRundownProtection(&Process->RundownProtect);
    return Status;
}

源码位置ntoskrnl/mm/ARM3/virtual.c#L1271(file:///d:/reactos/ntoskrnl/mm/ARM3/virtual.c#L1271)

5.13.2.4 权限检查

跨进程内存操作需要特定权限:

操作 所需权限 说明
VirtualAllocEx PROCESS_VM_OPERATION 在目标进程分配内存
ReadProcessMemory PROCESS_VM_READ 读取目标进程内存
WriteProcessMemory PROCESS_VM_WRITE 写入目标进程内存
VirtualProtectEx PROCESS_VM_OPERATION 修改目标进程内存保护

权限获取方式:

c 复制代码
// 获取目标进程句柄(需要相应权限)
HANDLE hProcess = OpenProcess(PROCESS_VM_READ | PROCESS_VM_WRITE, 
                              FALSE, 
                              targetProcessId);
if (hProcess == NULL)
{
    // 权限不足或进程不存在
    return GetLastError();
}

// 执行跨进程内存操作
SIZE_T bytesRead;
BOOL result = ReadProcessMemory(hProcess, 
                                targetAddress, 
                                buffer, 
                                bufferSize, 
                                &bytesRead);

CloseHandle(hProcess);

5.13.2.5 完整示例:远程进程内存注入

c 复制代码
#include <windows.h>
#include <stdio.h>

// 要注入的代码(简单示例)
const char shellcode[] = {
    0x6A, 0x00,             // push 0
    0x68, 0x00, 0x00, 0x00, 0x00, // push "Hello" 地址(需要动态填充)
    0x68, 0x00, 0x00, 0x00, 0x00, // push "Title" 地址
    0x6A, 0x00,             // push MB_OK
    0xB8, 0x00, 0x00, 0x00, 0x00, // mov eax, MessageBoxA 地址
    0xFF, 0xD0,             // call eax
    0xC3                    // ret
};

BOOL InjectShellcode(DWORD targetProcessId)
{
    HANDLE hProcess;
    LPVOID remoteMemory;
    SIZE_T bytesWritten;
    
    // 打开目标进程
    hProcess = OpenProcess(PROCESS_VM_OPERATION | 
                           PROCESS_VM_WRITE | 
                           PROCESS_VM_READ |
                           PROCESS_CREATE_THREAD,
                           FALSE, 
                           targetProcessId);
    if (!hProcess) return FALSE;
    
    // 在目标进程分配内存
    remoteMemory = VirtualAllocEx(hProcess, 
                                  NULL, 
                                  sizeof(shellcode) + 256,
                                  MEM_COMMIT | MEM_RESERVE,
                                  PAGE_EXECUTE_READWRITE);
    if (!remoteMemory)
    {
        CloseHandle(hProcess);
        return FALSE;
    }
    
    // 写入代码
    WriteProcessMemory(hProcess, 
                       remoteMemory, 
                       shellcode, 
                       sizeof(shellcode), 
                       &bytesWritten);
    
    // 创建远程线程执行代码
    HANDLE hThread = CreateRemoteThread(hProcess, 
                                         NULL, 
                                         0, 
                                         (LPTHREAD_START_ROUTINE)remoteMemory, 
                                         NULL, 
                                         0, 
                                         NULL);
    
    CloseHandle(hThread);
    CloseHandle(hProcess);
    return TRUE;
}

5.13.3 共享内存机制

5.13.3.1 CreateFileMapping/OpenFileMapping

共享内存通过 Section 对象实现:

c 复制代码
HANDLE
NTAPI
CreateFileMappingW(HANDLE hFile,
                   LPSECURITY_ATTRIBUTES lpFileMappingAttributes,
                   DWORD flProtect,
                   DWORD dwMaximumSizeHigh,
                   DWORD dwMaximumSizeLow,
                   LPCWSTR lpName)
{
    NTSTATUS Status;
    HANDLE SectionHandle;
    OBJECT_ATTRIBUTES LocalAttributes;
    POBJECT_ATTRIBUTES ObjectAttributes;
    UNICODE_STRING SectionName;
    ACCESS_MASK DesiredAccess;
    LARGE_INTEGER LocalSize;
    PLARGE_INTEGER SectionSize = NULL;
    ULONG Attributes;

    /* 设置默认访问权限 */
    DesiredAccess = STANDARD_RIGHTS_REQUIRED | SECTION_QUERY | SECTION_MAP_READ;

    /* 获取SEC属性并清理flProtect */
    Attributes = flProtect & (SEC_FILE | SEC_IMAGE | SEC_RESERVE | SEC_NOCACHE | 
                              SEC_COMMIT | SEC_LARGE_PAGES);
    flProtect ^= Attributes;

    /* 默认使用SEC_COMMIT */
    if (!Attributes) Attributes = SEC_COMMIT;

    /* 根据保护属性设置写入权限 */
    if (flProtect == PAGE_READWRITE)
    {
        DesiredAccess |= SECTION_MAP_WRITE;
    }
    else if (flProtect == PAGE_EXECUTE_READWRITE)
    {
        DesiredAccess |= (SECTION_MAP_WRITE | SECTION_MAP_EXECUTE);
    }

    /* 初始化对象名称 */
    if (lpName) RtlInitUnicodeString(&SectionName, lpName);

    /* 转换对象属性 */
    ObjectAttributes = BaseFormatObjectAttributes(&LocalAttributes,
                                                   lpFileMappingAttributes,
                                                   lpName ? &SectionName : NULL);

    /* 设置大小 */
    if (dwMaximumSizeLow || dwMaximumSizeHigh)
    {
        SectionSize = &LocalSize;
        SectionSize->LowPart = dwMaximumSizeLow;
        SectionSize->HighPart = dwMaximumSizeHigh;
    }

    /* 处理文件句柄 */
    if (hFile == INVALID_HANDLE_VALUE)
    {
        hFile = NULL;
        if (!SectionSize)
        {
            SetLastError(ERROR_INVALID_PARAMETER);
            return NULL;
        }
    }

    /* 创建Section对象 */
    Status = NtCreateSection(&SectionHandle,
                             DesiredAccess,
                             ObjectAttributes,
                             SectionSize,
                             flProtect,
                             Attributes,
                             hFile);
    
    if (!NT_SUCCESS(Status))
    {
        BaseSetLastNTError(Status);
        return NULL;
    }

    return SectionHandle;
}

源码位置dll/win32/kernel32/client/file/filemap.c#L45(file:///d:/reactos/dll/win32/kernel32/client/file/filemap.c#L45)

5.13.3.2 MapViewOfFile/UnmapViewOfFile

将 Section 映射到进程地址空间:

c 复制代码
LPVOID
NTAPI
MapViewOfFileEx(HANDLE hFileMappingObject,
                DWORD dwDesiredAccess,
                DWORD dwFileOffsetHigh,
                DWORD dwFileOffsetLow,
                SIZE_T dwNumberOfBytesToMap,
                LPVOID lpBaseAddress)
{
    NTSTATUS Status;
    LARGE_INTEGER SectionOffset;
    SIZE_T ViewSize;
    ULONG Protect;
    LPVOID ViewBase;

    /* 转换偏移量 */
    SectionOffset.LowPart = dwFileOffsetLow;
    SectionOffset.HighPart = dwFileOffsetHigh;

    ViewBase = lpBaseAddress;
    ViewSize = dwNumberOfBytesToMap;

    /* 将访问标志转换为保护属性 */
    if (dwDesiredAccess == FILE_MAP_COPY)
    {
        Protect = PAGE_WRITECOPY;
    }
    else if (dwDesiredAccess & FILE_MAP_WRITE)
    {
        Protect = (dwDesiredAccess & FILE_MAP_EXECUTE) ?
                   PAGE_EXECUTE_READWRITE : PAGE_READWRITE;
    }
    else if (dwDesiredAccess & FILE_MAP_READ)
    {
        Protect = (dwDesiredAccess & FILE_MAP_EXECUTE) ?
                   PAGE_EXECUTE_READ : PAGE_READONLY;
    }
    else
    {
        Protect = PAGE_NOACCESS;
    }

    /* 映射Section */
    Status = NtMapViewOfSection(hFileMappingObject,
                                NtCurrentProcess(),
                                &ViewBase,
                                0,
                                0,
                                &SectionOffset,
                                &ViewSize,
                                ViewShare,
                                0,
                                Protect);
    
    if (!NT_SUCCESS(Status))
    {
        BaseSetLastNTError(Status);
        return NULL;
    }

    return ViewBase;
}

源码位置dll/win32/kernel32/client/file/filemap.c#L162(file:///d:/reactos/dll/win32/kernel32/client/file/filemap.c#L162)

5.13.3.3 Section对象类型

Section 对象是内核中的核心对象类型:

c 复制代码
// Section 对象结构(简化)
typedef struct _SECTION {
    PVOID Segment;              // 段对象
    LARGE_INTEGER SizeOfSection; // Section大小
    ULONG Protection;           // 保护属性
    PVOID FileObject;           // 关联的文件对象(如果有)
    ULONG Flags;                // 标志位
} SECTION, *PSECTION;

Section 类型:

类型 说明
文件映射 基于文件的Section,映射文件内容
页面文件映射 基于页面文件的Section,纯内存共享
图像映射 映射PE可执行文件,用于加载DLL/EXE

5.13.3.4 共享内存同步问题

共享内存本身不提供同步机制,需要配合其他同步原语:

c 复制代码
#include <windows.h>

// 共享内存数据结构
struct SharedData {
    CRITICAL_SECTION cs;    // 临界区(需要跨进程初始化)
    LONG counter;           // 共享计数器
    HANDLE hEvent;          // 通知事件
};

// 创建共享内存
HANDLE CreateMapAndInit()
{
    HANDLE hMap = CreateFileMapping(INVALID_HANDLE_VALUE,
                                    NULL,
                                    PAGE_READWRITE,
                                    0,
                                    sizeof(SharedData),
                                    L"MySharedMemory");
    
    if (hMap == NULL) return NULL;
    
    BOOL isFirst = (GetLastError() != ERROR_ALREADY_EXISTS);
    
    SharedData* pData = (SharedData*)MapViewOfFile(hMap,
                                                    FILE_MAP_WRITE,
                                                    0, 0, 0);
    
    if (isFirst)
    {
        // 第一个进程初始化同步对象
        InitializeCriticalSection(&pData->cs);
        pData->counter = 0;
        pData->hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
    }
    
    return hMap;
}

// 使用共享内存
void IncrementCounter(SharedData* pData)
{
    EnterCriticalSection(&pData->cs);
    pData->counter++;
    LeaveCriticalSection(&pData->cs);
    
    // 通知其他进程
    SetEvent(pData->hEvent);
}

5.13.4 管道通信

5.13.4.1 CreatePipe匿名管道

匿名管道用于父子进程通信:

c 复制代码
BOOL
WINAPI
CreatePipe(PHANDLE hReadPipe,
           PHANDLE hWritePipe,
           LPSECURITY_ATTRIBUTES lpPipeAttributes,
           DWORD nSize)
{
    WCHAR Buffer[64];
    UNICODE_STRING PipeName;
    OBJECT_ATTRIBUTES ObjectAttributes;
    IO_STATUS_BLOCK StatusBlock;
    LARGE_INTEGER DefaultTimeout;
    HANDLE ReadPipeHandle, WritePipeHandle;
    LONG PipeId;

    /* 设置默认超时120秒 */
    DefaultTimeout.QuadPart = -1200000000;

    /* 使用默认缓冲区大小 */
    if (!nSize) nSize = 0x1000;

    /* 生成唯一管道名称 */
    PipeId = InterlockedIncrement(&ProcessPipeId);
    _swprintf(Buffer,
              L"\\Device\\NamedPipe\\Win32Pipes.%p.%08x",
              NtCurrentTeb()->ClientId.UniqueProcess,
              PipeId);
    RtlInitUnicodeString(&PipeName, Buffer);

    /* 初始化对象属性 */
    InitializeObjectAttributes(&ObjectAttributes,
                               &PipeName,
                               OBJ_CASE_INSENSITIVE | 
                               (lpPipeAttributes && lpPipeAttributes->bInheritHandle ? OBJ_INHERIT : 0),
                               NULL,
                               lpPipeAttributes ? lpPipeAttributes->lpSecurityDescriptor : NULL);

    /* 创建命名管道(读端) */
    Status = NtCreateNamedPipeFile(&ReadPipeHandle,
                                   GENERIC_READ | FILE_WRITE_ATTRIBUTES | SYNCHRONIZE,
                                   &ObjectAttributes,
                                   &StatusBlock,
                                   FILE_SHARE_READ | FILE_SHARE_WRITE,
                                   FILE_CREATE,
                                   FILE_SYNCHRONOUS_IO_NONALERT,
                                   FILE_PIPE_BYTE_STREAM_TYPE,
                                   FILE_PIPE_BYTE_STREAM_MODE,
                                   FILE_PIPE_QUEUE_OPERATION,
                                   1,
                                   nSize,
                                   nSize,
                                   &DefaultTimeout);
    
    if (!NT_SUCCESS(Status))
    {
        BaseSetLastNTError(Status);
        return FALSE;
    }

    /* 打开写端 */
    Status = NtOpenFile(&WritePipeHandle,
                        FILE_GENERIC_WRITE,
                        &ObjectAttributes,
                        &StatusBlock,
                        FILE_SHARE_READ,
                        FILE_SYNCHRONOUS_IO_NONALERT | FILE_NON_DIRECTORY_FILE);
    
    if (!NT_SUCCESS(Status))
    {
        NtClose(ReadPipeHandle);
        BaseSetLastNTError(Status);
        return FALSE;
    }

    *hReadPipe = ReadPipeHandle;
    *hWritePipe = WritePipeHandle;
    return TRUE;
}

源码位置dll/win32/kernel32/client/file/npipe.c#L117(file:///d:/reactos/dll/win32/kernel32/client/file/npipe.c#L117)

5.13.4.2 CreateNamedPipe命名管道

命名管道支持跨进程、跨机器通信:

c 复制代码
HANDLE
WINAPI
CreateNamedPipeW(LPCWSTR lpName,
                 DWORD dwOpenMode,
                 DWORD dwPipeMode,
                 DWORD nMaxInstances,
                 DWORD nOutBufferSize,
                 DWORD nInBufferSize,
                 DWORD nDefaultTimeOut,
                 LPSECURITY_ATTRIBUTES lpSecurityAttributes)
{
    UNICODE_STRING NamedPipeName;
    OBJECT_ATTRIBUTES ObjectAttributes;
    HANDLE PipeHandle;
    ACCESS_MASK DesiredAccess;
    ULONG CreateOptions = 0;
    ULONG WriteModeMessage, ReadModeMessage, NonBlocking;
    LARGE_INTEGER DefaultTimeOut;

    /* 验证实例数 */
    if (nMaxInstances == 0 || nMaxInstances > PIPE_UNLIMITED_INSTANCES)
    {
        SetLastError(ERROR_INVALID_PARAMETER);
        return INVALID_HANDLE_VALUE;
    }

    /* 转换路径名称 */
    if (!RtlDosPathNameToNtPathName_U(lpName, &NamedPipeName, NULL, NULL))
    {
        SetLastError(ERROR_PATH_NOT_FOUND);
        return INVALID_HANDLE_VALUE;
    }

    /* 设置访问权限 */
    DesiredAccess = SYNCHRONIZE | (dwOpenMode & (WRITE_DAC | WRITE_OWNER | ACCESS_SYSTEM_SECURITY));

    /* 转换创建选项 */
    if (dwOpenMode & FILE_FLAG_WRITE_THROUGH)
        CreateOptions |= FILE_WRITE_THROUGH;
    if (!(dwOpenMode & FILE_FLAG_OVERLAPPED))
        CreateOptions |= FILE_SYNCHRONOUS_IO_NONALERT;

    /* 设置共享模式 */
    ULONG ShareAccess = 0;
    if (dwOpenMode & PIPE_ACCESS_OUTBOUND)
    {
        ShareAccess |= FILE_SHARE_READ;
        DesiredAccess |= GENERIC_WRITE;
    }
    if (dwOpenMode & PIPE_ACCESS_INBOUND)
    {
        ShareAccess |= FILE_SHARE_WRITE;
        DesiredAccess |= GENERIC_READ;
    }

    /* 转换管道模式 */
    WriteModeMessage = (dwPipeMode & PIPE_TYPE_MESSAGE) ? 
                       FILE_PIPE_MESSAGE_TYPE : FILE_PIPE_BYTE_STREAM_TYPE;
    ReadModeMessage = (dwPipeMode & PIPE_READMODE_MESSAGE) ?
                      FILE_PIPE_MESSAGE_MODE : FILE_PIPE_BYTE_STREAM_MODE;
    NonBlocking = (dwPipeMode & PIPE_NOWAIT) ?
                  FILE_PIPE_COMPLETE_OPERATION : FILE_PIPE_QUEUE_OPERATION;

    /* 设置超时 */
    if (nDefaultTimeOut)
        DefaultTimeOut.QuadPart = nDefaultTimeOut * -10000LL;
    else
        DefaultTimeOut.QuadPart = -500000;

    /* 创建管道 */
    Status = NtCreateNamedPipeFile(&PipeHandle,
                                   DesiredAccess,
                                   &ObjectAttributes,
                                   &Iosb,
                                   ShareAccess,
                                   FILE_OPEN_IF,
                                   CreateOptions,
                                   WriteModeMessage,
                                   ReadModeMessage,
                                   NonBlocking,
                                   nMaxInstances,
                                   nInBufferSize,
                                   nOutBufferSize,
                                   &DefaultTimeOut);

    RtlFreeHeap(RtlGetProcessHeap(), 0, NamedPipeName.Buffer);

    if (!NT_SUCCESS(Status))
    {
        BaseSetLastNTError(Status);
        return INVALID_HANDLE_VALUE;
    }

    return PipeHandle;
}

源码位置dll/win32/kernel32/client/file/npipe.c#L246(file:///d:/reactos/dll/win32/kernel32/client/file/npipe.c#L246)

5.13.4.3 管道模式

模式 说明
字节流模式 数据作为连续字节流传输
消息模式 数据以消息为单位传输,保留边界
阻塞模式 读/写操作会等待
非阻塞模式 读/写操作立即返回

5.13.4.4 管道服务器示例

c 复制代码
#include <windows.h>
#include <stdio.h>

#define PIPE_NAME L"\\\\.\\pipe\\MyPipe"
#define BUFFER_SIZE 1024

void PipeServer()
{
    HANDLE hPipe;
    char buffer[BUFFER_SIZE];
    DWORD bytesRead, bytesWritten;
    
    while (TRUE)
    {
        // 创建命名管道
        hPipe = CreateNamedPipeW(PIPE_NAME,
                                 PIPE_ACCESS_DUPLEX,
                                 PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT,
                                 PIPE_UNLIMITED_INSTANCES,
                                 BUFFER_SIZE,
                                 BUFFER_SIZE,
                                 0,
                                 NULL);
        
        if (hPipe == INVALID_HANDLE_VALUE)
        {
            printf("CreateNamedPipe failed: %d\n", GetLastError());
            return;
        }
        
        printf("Waiting for client...\n");
        
        // 等待客户端连接
        if (!ConnectNamedPipe(hPipe, NULL))
        {
            printf("ConnectNamedPipe failed: %d\n", GetLastError());
            CloseHandle(hPipe);
            continue;
        }
        
        printf("Client connected!\n");
        
        // 读取客户端数据
        if (ReadFile(hPipe, buffer, BUFFER_SIZE, &bytesRead, NULL))
        {
            printf("Received: %s\n", buffer);
            
            // 发送响应
            WriteFile(hPipe, "OK", 3, &bytesWritten, NULL);
        }
        
        // 断开连接
        DisconnectNamedPipe(hPipe);
        CloseHandle(hPipe);
    }
}

5.13.5 邮槽机制

5.13.5.1 CreateMailslot

邮槽是一种单向广播通信机制:

c 复制代码
HANDLE
WINAPI
CreateMailslotW(IN LPCWSTR lpName,
                IN DWORD nMaxMessageSize,
                IN DWORD lReadTimeout,
                IN LPSECURITY_ATTRIBUTES lpSecurityAttributes)
{
    OBJECT_ATTRIBUTES ObjectAttributes;
    UNICODE_STRING MailslotName;
    HANDLE MailslotHandle;
    LARGE_INTEGER DefaultTimeOut;

    /* 转换路径名称 */
    if (!RtlDosPathNameToNtPathName_U(lpName, &MailslotName, NULL, NULL))
    {
        SetLastError(ERROR_PATH_NOT_FOUND);
        return INVALID_HANDLE_VALUE;
    }

    /* 初始化对象属性 */
    InitializeObjectAttributes(&ObjectAttributes,
                               &MailslotName,
                               OBJ_CASE_INSENSITIVE | 
                               (lpSecurityAttributes && lpSecurityAttributes->bInheritHandle ? OBJ_INHERIT : 0),
                               NULL,
                               lpSecurityAttributes ? lpSecurityAttributes->lpSecurityDescriptor : NULL);

    /* 设置超时 */
    if (lReadTimeout == MAILSLOT_WAIT_FOREVER)
    {
        DefaultTimeOut.QuadPart = 0xFFFFFFFFFFFFFFFFLL;
    }
    else
    {
        DefaultTimeOut.QuadPart = lReadTimeout * -10000LL;
    }

    /* 创建邮槽 */
    Status = NtCreateMailslotFile(&MailslotHandle,
                                  GENERIC_READ | SYNCHRONIZE | WRITE_DAC,
                                  &ObjectAttributes,
                                  &Iosb,
                                  FILE_WRITE_THROUGH,
                                  0,
                                  nMaxMessageSize,
                                  &DefaultTimeOut);

    RtlFreeHeap(RtlGetProcessHeap(), 0, MailslotName.Buffer);

    if (!NT_SUCCESS(Status))
    {
        BaseSetLastNTError(Status);
        return INVALID_HANDLE_VALUE;
    }

    return MailslotHandle;
}

源码位置dll/win32/kernel32/client/file/mailslot.c#L37(file:///d:/reactos/dll/win32/kernel32/client/file/mailslot.c#L37)

5.13.5.2 邮槽特性

特性 说明
单向通信 只能写入邮槽,邮槽服务器只能读取
广播能力 同名邮槽可以在多台机器上创建,实现广播
消息边界 保留消息边界,每条消息独立
最大消息大小 通常限制为400字节(网络传输)

5.13.5.3 邮槽与管道的区别

特性 邮槽 命名管道
通信方向 单向 双向
广播能力 支持 不支持
消息大小 有限制(400字节) 无限制
可靠性 不保证送达 保证送达
适用场景 通知、广播 数据交换、请求响应

5.13.5.4 邮槽示例

c 复制代码
#include <windows.h>
#include <stdio.h>

#define MAILSLOT_NAME L"\\\\.\\mailslot\\MyMailslot"

// 邮槽服务器
void MailslotServer()
{
    HANDLE hMailslot;
    char buffer[256];
    DWORD bytesRead, messageCount, nextSize;
    
    hMailslot = CreateMailslotW(MAILSLOT_NAME,
                                0,              // 最大消息大小(0=不限)
                                MAILSLOT_WAIT_FOREVER,
                                NULL);
    
    if (hMailslot == INVALID_HANDLE_VALUE)
    {
        printf("CreateMailslot failed: %d\n", GetLastError());
        return;
    }
    
    printf("Mailslot server running...\n");
    
    while (TRUE)
    {
        // 获取邮槽信息
        GetMailslotInfo(hMailslot, NULL, &nextSize, &messageCount, NULL);
        
        if (messageCount > 0)
        {
            // 读取消息
            ReadFile(hMailslot, buffer, nextSize, &bytesRead, NULL);
            printf("Received: %s\n", buffer);
        }
        else
        {
            Sleep(100);
        }
    }
    
    CloseHandle(hMailslot);
}

// 邮槽客户端
void MailslotClient(LPSTR message)
{
    HANDLE hMailslot;
    DWORD bytesWritten;
    
    hMailslot = CreateFileW(MAILSLOT_NAME,
                           GENERIC_WRITE,
                           FILE_SHARE_READ,
                           NULL,
                           OPEN_EXISTING,
                           FILE_ATTRIBUTE_NORMAL,
                           NULL);
    
    if (hMailslot == INVALID_HANDLE_VALUE)
    {
        printf("Open mailslot failed: %d\n", GetLastError());
        return;
    }
    
    WriteFile(hMailslot, message, strlen(message) + 1, &bytesWritten, NULL);
    
    CloseHandle(hMailslot);
}

5.13.6 LPC机制

5.13.6.1 LPC概述

LPC(Local Procedure Call,本地过程调用)是 Windows 内核提供的高性能 IPC 机制:

  • 高性能:直接在内核中传递消息,无需文件系统介入;
  • 请求-响应模型:支持同步的请求-响应交互;
  • 安全验证:支持 SID 验证和安全上下文传递;
  • Section 共享:可以在连接时传递 Section 用于大数据传输。

5.13.6.2 NtCreatePort/NtConnectPort

创建服务端端口:

c 复制代码
NTSTATUS
NTAPI
NtCreatePort(OUT PHANDLE PortHandle,
             IN POBJECT_ATTRIBUTES ObjectAttributes,
             IN ULONG MaxConnectInfoLength,
             IN ULONG MaxDataLength,
             IN ULONG MaxPoolUsage)
{
    /* 调用内部API */
    return LpcpCreatePort(PortHandle,
                          ObjectAttributes,
                          MaxConnectInfoLength,
                          MaxDataLength,
                          MaxPoolUsage,
                          FALSE);
}

NTSTATUS
NTAPI
LpcpCreatePort(OUT PHANDLE PortHandle,
               IN POBJECT_ATTRIBUTES ObjectAttributes,
               IN ULONG MaxConnectionInfoLength,
               IN ULONG MaxMessageLength,
               IN ULONG MaxPoolUsage,
               IN BOOLEAN Waitable)
{
    PLPCP_PORT_OBJECT Port;
    HANDLE Handle;

    /* 创建端口对象 */
    Status = ObCreateObject(PreviousMode,
                            LpcPortObjectType,
                            ObjectAttributes,
                            PreviousMode,
                            NULL,
                            sizeof(LPCP_PORT_OBJECT),
                            0, 0,
                            (PVOID*)&Port);
    
    if (!NT_SUCCESS(Status)) return Status;

    /* 初始化端口 */
    RtlZeroMemory(Port, sizeof(LPCP_PORT_OBJECT));
    Port->ConnectionPort = Port;
    Port->Creator = PsGetCurrentThread()->Cid;
    InitializeListHead(&Port->LpcDataInfoChainHead);
    InitializeListHead(&Port->LpcReplyChainHead);

    /* 设置端口类型 */
    if (CapturedObjectName.Buffer == NULL)
    {
        Port->Flags = LPCP_UNCONNECTED_PORT;
        Port->ConnectedPort = Port;
        Port->ServerProcess = NULL;
    }
    else
    {
        Port->Flags = LPCP_CONNECTION_PORT;
        Port->ServerProcess = PsGetCurrentProcess();
        ObReferenceObject(Port->ServerProcess);
    }

    /* 初始化消息队列 */
    Status = LpcpInitializePortQueue(Port);
    
    /* 设置最大消息大小 */
    Port->MaxMessageLength = MaxMessageLength;
    Port->MaxConnectionInfoLength = MaxConnectionInfoLength;

    /* 插入对象 */
    Status = ObInsertObject(Port, NULL, PORT_ALL_ACCESS, 0, NULL, &Handle);
    
    *PortHandle = Handle;
    return Status;
}

源码位置ntoskrnl/lpc/create.c#L222(file:///d:/reactos/ntoskrnl/lpc/create.c#L222)

5.13.6.3 NtConnectPort客户端连接

c 复制代码
NTSTATUS
NTAPI
NtConnectPort(OUT PHANDLE PortHandle,
              IN PUNICODE_STRING PortName,
              IN PSECURITY_QUALITY_OF_SERVICE SecurityQos,
              IN OUT PPORT_VIEW ClientView OPTIONAL,
              IN OUT PREMOTE_PORT_VIEW ServerView OPTIONAL,
              OUT PULONG MaxMessageLength OPTIONAL,
              IN OUT PVOID ConnectionInformation OPTIONAL,
              IN OUT PULONG ConnectionInformationLength OPTIONAL)
{
    /* 调用安全连接API */
    return NtSecureConnectPort(PortHandle,
                               PortName,
                               SecurityQos,
                               ClientView,
                               NULL,
                               ServerView,
                               MaxMessageLength,
                               ConnectionInformation,
                               ConnectionInformationLength);
}

源码位置ntoskrnl/lpc/connect.c#L777(file:///d:/reactos/ntoskrnl/lpc/connect.c#L777)

5.13.6.4 PORT_MESSAGE结构

c 复制代码
typedef struct _PORT_MESSAGE {
    union {
        struct {
            CSHORT DataLength;      // 数据长度
            CSHORT TotalLength;     // 总长度(含头部)
        } s1;
        ULONG Length;               // 长度(合并)
    } u1;
    
    union {
        struct {
            CSHORT Type;            // 消息类型
            CSHORT DataInfoOffset;  // 数据信息偏移
        } s2;
        ULONG ZeroInit;             // 初始化为零
    } u2;
    
    CLIENT_ID ClientId;             // 客户端ID
    ULONG MessageId;                // 消息ID
    ULONG ClientViewSize;           // 客户端视图大小
} PORT_MESSAGE, *PPORT_MESSAGE;

消息类型:

类型 说明
LPC_DATAGRAM 数据报消息,无需响应
LPC_REQUEST 请求消息,需要响应
LPC_REPLY 响应消息
LPC_CONNECTION_REQUEST 连接请求
LPC_PORT_CLOSED 端口关闭通知
LPC_CLIENT_DIED 客户端死亡通知

5.13.6.5 NtRequestPort/NtReplyPort

发送请求:

c 复制代码
NTSTATUS
NTAPI
NtRequestPort(IN HANDLE PortHandle,
              IN PPORT_MESSAGE LpcRequest)
{
    PLPCP_PORT_OBJECT Port, QueuePort;
    PLPCP_MESSAGE Message;
    ULONG MessageType;

    /* 获取消息类型 */
    MessageType = CapturedLpcRequest.u2.s2.Type | LPC_DATAGRAM;

    /* 引用端口对象 */
    Status = ObReferenceObjectByHandle(PortHandle,
                                       0,
                                       LpcPortObjectType,
                                       PreviousMode,
                                       (PVOID*)&Port,
                                       NULL);
    
    /* 分配消息 */
    Message = LpcpAllocateFromPortZone();
    
    /* 复制消息内容 */
    LpcpMoveMessage(&Message->Request,
                    &CapturedLpcRequest,
                    LpcRequest + 1,
                    MessageType,
                    &Thread->Cid);

    /* 获取锁 */
    KeAcquireGuardedMutex(&LpcpLock);

    /* 确定目标端口 */
    if ((Port->Flags & LPCP_PORT_TYPE_MASK) != LPCP_CONNECTION_PORT)
    {
        QueuePort = Port->ConnectedPort;
    }
    else
    {
        QueuePort = Port;
    }

    /* 生成消息ID */
    Message->Request.MessageId = LpcpNextMessageId++;
    
    /* 插入消息队列 */
    InsertTailList(&QueuePort->MsgQueue.ReceiveHead, &Message->Entry);

    /* 释放锁并唤醒等待者 */
    KeReleaseGuardedMutex(&LpcpLock);
    LpcpCompleteWait(QueuePort->MsgQueue.Semaphore);

    return STATUS_SUCCESS;
}

源码位置ntoskrnl/lpc/send.c#L440(file:///d:/reactos/ntoskrnl/lpc/send.c#L440)

5.13.6.6 Windows子系统应用

LPC 在 Windows 子系统(csrss.exe)中广泛使用:

LPC端口 用途
\Windows\ApiPort Win32 API 调用
\Windows\SbApiPort 会话管理器API
\Windows\SmApiPort 子系统管理
\Console\LpcPort 控制台输入输出

5.13.7 句柄复制

5.13.7.1 DuplicateHandle

句柄复制允许跨进程共享句柄:

c 复制代码
NTSTATUS
NTAPI
NtDuplicateObject(IN HANDLE SourceProcessHandle,
                  IN HANDLE SourceHandle,
                  IN HANDLE TargetProcessHandle OPTIONAL,
                  OUT PHANDLE TargetHandle OPTIONAL,
                  IN ACCESS_MASK DesiredAccess,
                  IN ULONG HandleAttributes,
                  IN ULONG Options)
{
    PEPROCESS SourceProcess, TargetProcess, Target;
    HANDLE hTarget;
    KPROCESSOR_MODE PreviousMode = ExGetPreviousMode();

    /* 探测输出参数 */
    if ((TargetHandle) && (PreviousMode != KernelMode))
    {
        ProbeForWriteHandle(TargetHandle);
        *TargetHandle = NULL;
    }

    /* 引用源进程 */
    Status = ObReferenceObjectByHandle(SourceProcessHandle,
                                       PROCESS_DUP_HANDLE,
                                       PsProcessType,
                                       PreviousMode,
                                       (PVOID*)&SourceProcess,
                                       NULL);
    if (!NT_SUCCESS(Status)) return Status;

    /* 引用目标进程 */
    if (TargetProcessHandle)
    {
        Status = ObReferenceObjectByHandle(TargetProcessHandle,
                                           PROCESS_DUP_HANDLE,
                                           PsProcessType,
                                           PreviousMode,
                                           (PVOID*)&TargetProcess,
                                           NULL);
        if (NT_SUCCESS(Status))
            Target = TargetProcess;
        else
            Target = NULL;
    }
    else
    {
        Status = STATUS_SUCCESS;
        Target = NULL;
    }

    /* 执行句柄复制 */
    Status = ObDuplicateObject(SourceProcess,
                               SourceHandle,
                               Target,
                               &hTarget,
                               DesiredAccess,
                               HandleAttributes,
                               Options,
                               PreviousMode);

    /* 返回新句柄 */
    if (TargetHandle)
    {
        *TargetHandle = hTarget;
    }

    /* 释放引用 */
    if (Target) ObDereferenceObject(Target);
    ObDereferenceObject(SourceProcess);
    return Status;
}

源码位置ntoskrnl/ob/obhandle.c#L3410(file:///d:/reactos/ntoskrnl/ob/obhandle.c#L3410)

5.13.7.2 权限要求

操作 所需权限
源进程访问 PROCESS_DUP_HANDLE
目标进程访问 PROCESS_DUP_HANDLE
对象访问 根据对象类型和请求的访问权限

5.13.7.3 句柄复制选项

选项 说明
DUPLICATE_SAME_ACCESS 使用源句柄的访问权限
DUPLICATE_CLOSE_SOURCE 复制后关闭源句柄
0 使用指定的访问权限

5.13.7.4 句柄复制示例

c 复制代码
#include <windows.h>
#include <stdio.h>

// 进程A:创建共享句柄
void ProcessA()
{
    HANDLE hMutex = CreateMutex(NULL, FALSE, NULL);
    HANDLE hProcessB = OpenProcess(PROCESS_DUP_HANDLE, FALSE, processBId);
    
    HANDLE hRemoteMutex;
    
    // 复制互斥体句柄到进程B
    DuplicateHandle(GetCurrentProcess(),    // 源进程
                    hMutex,                  // 源句柄
                    hProcessB,               // 目标进程
                    &hRemoteMutex,           // 目标句柄(在进程B中)
                    SYNCHRONIZE,             // 请求的访问权限
                    FALSE,                   // 不继承
                    DUPLICATE_CLOSE_SOURCE); // 关闭源句柄
    
    CloseHandle(hProcessB);
}

// 进程B:使用共享句柄
void ProcessB(HANDLE hSharedMutex)
{
    // 等待互斥体
    WaitForSingleObject(hSharedMutex, INFINITE);
    
    // 执行共享操作...
    
    ReleaseMutex(hSharedMutex);
    CloseHandle(hSharedMutex);
}

5.13.8 IPC机制对比与选择

5.13.8.1 性能对比

IPC机制 传输速度 适用数据量 CPU开销
共享内存 最高 大量数据 低(仅同步开销)
LPC 中等数据
命名管道 中等数据
匿名管道 小量数据
邮槽 小消息
ReadProcessMemory 小量数据

5.13.8.2 适用场景分析

IPC机制 最佳适用场景
共享内存 大数据共享、高频数据交换、图像处理
LPC 本地服务请求-响应、子系统通信
命名管道 客户端-服务器模型、网络通信
匿名管道 父子进程通信、命令行管道
邮槽 广播通知、状态更新
ReadProcessMemory 调试器、进程监控

5.13.8.3 选择指南矩阵

复制代码
┌─────────────────────────────────────────────────────────────────────────────┐
│                        IPC 选择矩阵                                          │
├──────────────────┬──────────┬──────────┬──────────┬──────────┬─────────────┤
│ 需求             │ 共享内存 │ LPC      │ 命名管道 │ 邮槽     │ 直接内存    │
├──────────────────┼──────────┼──────────┼──────────┼──────────┼─────────────┤
│ 大数据量         │ ★★★★★  │ ★★★☆☆  │ ★★★☆☆  │ ★☆☆☆☆  │ ★★☆☆☆     │
│ 高频率           │ ★★★★★  │ ★★★★☆  │ ★★★☆☆  │ ★★☆☆☆  │ ★☆☆☆☆     │
│ 双向通信         │ ★★★★★  │ ★★★★★  │ ★★★★★  │ ★☆☆☆☆  │ ★★★★★     │
│ 网络支持         │ ★☆☆☆☆  │ ★☆☆☆☆  │ ★★★★★  │ ★★★★☆  │ ★☆☆☆☆     │
│ 同步支持         │ 需配合   │ 内置     │ 内置     │ 无       │ 无         │
│ 安全验证         │ ★★★☆☆  │ ★★★★★  │ ★★★★☆  │ ★★★☆☆  │ ★★★★★     │
│ 易用性           │ ★★★☆☆  │ ★★☆☆☆  │ ★★★★★  │ ★★★★☆  │ ★★☆☆☆     │
└──────────────────┴──────────┴──────────┴──────────┴──────────┴─────────────┘

5.13.9 设计哲学问答

Q1:为什么Windows需要多种IPC机制?

A:不同的 IPC 机制针对不同的使用场景设计:

  • 共享内存针对高性能大数据传输;
  • 管道针对结构化的流式通信;
  • 邮槽针对广播通知;
  • LPC针对请求-响应模式;
  • 直接内存访问针对调试和监控。

单一机制无法同时满足性能、易用性、安全性和网络支持等多方面需求。

Q2:为什么进程隔离如此严格?

A:进程隔离是现代操作系统安全的基础:

  1. 安全性:防止恶意进程破坏其他进程;
  2. 稳定性:进程崩溃不会影响其他进程;
  3. 隐私性:进程数据不会被其他进程窃取;
  4. 资源管理:每个进程的资源可独立管理。

IPC 机制是在受控条件下打破隔离的安全通道。

Q3:为什么共享内存需要额外的同步机制?

A:共享内存本身只提供数据共享能力,不提供同步:

  • 多进程同时写入可能导致数据损坏;
  • 读进程可能读到部分更新的数据;
  • 需要配合互斥体、信号量或临界区来保证一致性。

这是性能与复杂性的权衡:共享内存提供最高性能,但使用者需要负责同步。

Q4:为什么LPC只在本地使用?

A:LPC 设计为本地高性能 IPC:

  • LPC 消息直接在内核中传递,不经过文件系统;
  • LPC 使用共享内存池分配消息,减少复制开销;
  • LPC 连接建立后,后续通信非常高效。

网络通信需要额外的协议栈开销,LPC 的设计目标就是避免这些开销。对于网络通信,命名管道或 RPC 更合适。

Q5:为什么邮槽消息大小有限制?

A:邮槽设计为广播机制,限制消息大小是为了:

  1. 网络效率:小消息可以快速广播到多台机器;
  2. 可靠性:大消息广播可能导致网络拥塞;
  3. 历史原因:早期网络带宽有限。

400字节限制是历史遗留,现代系统可以放宽,但建议保持小消息以维持广播特性。

Q6:为什么ReadProcessMemory需要特殊权限?

A:跨进程内存访问是高风险操作:

  • 可能读取敏感数据(密码、密钥);
  • 可能破坏进程稳定性;
  • 可能侵犯隐私。

PROCESS_VM_READ 权限通常只授予调试器和系统工具,普通应用不应使用。

Q7:为什么句柄复制需要PROCESS_DUP_HANDLE权限?

A:句柄复制涉及敏感操作:

  • 复制句柄可能授予不应有的访问权限;
  • 可能泄露敏感资源(文件、进程);
  • 可能被恶意软件利用。

PROCESS_DUP_HANDLE 权限需要显式请求,防止意外或恶意复制。

Q8:为什么匿名管道只能用于父子进程?

A:匿名管道没有名称,无法被其他进程打开:

  • 管道句柄通过继承传递给子进程;
  • 父进程创建管道后,将句柄传递给子进程;
  • 其他进程无法找到或访问匿名管道。

命名管道通过名称解决这个问题,任何进程都可以通过名称连接。

Q9:为什么LPC使用消息队列而不是直接内存访问?

A:LPC 的消息队列设计有以下优势:

  1. 结构化通信:消息有明确的边界和类型;
  2. 请求-响应:支持同步的请求-响应模式;
  3. 安全验证:每条消息可以验证发送者身份;
  4. 顺序保证:消息按顺序处理。

直接内存访问虽然更快,但缺乏这些结构化特性。

Q10:为什么Windows子系统使用LPC而不是其他IPC?

A:Windows 子系统(csrss.exe)使用 LPC 的原因:

  • 性能需求:Win32 API 调用频繁,需要高性能;
  • 请求-响应模式:API 调用是典型的请求-响应;
  • 安全验证:需要验证调用者身份和权限;
  • 内核集成:LPC 直接在内核中处理,效率最高。

其他 IPC 机制无法同时满足这些需求。


总结

跨进程操作是 Windows 操作系统中实现进程间协作的核心机制。本章介绍了:

  1. IPC 机制分类:直接内存访问、共享内存、消息传递、句柄传递;
  2. 跨进程内存操作:VirtualAllocEx、ReadProcessMemory、MmCopyVirtualMemory;
  3. 共享内存机制:Section对象、CreateFileMapping、MapViewOfFile;
  4. 管道通信:匿名管道、命名管道、字节流和消息模式;
  5. 邮槽机制:单向广播通信、CreateMailslot;
  6. LPC机制:NtCreatePort、NtConnectPort、PORT_MESSAGE;
  7. 句柄复制:DuplicateHandle、权限要求;
  8. IPC选择指南:性能对比、适用场景分析。

核心要点回顾

  1. 进程隔离是安全基础,IPC 是受控的打破隔离的通道;
  2. 共享内存性能最高,但需要额外同步机制;
  3. LPC 是本地高性能 IPC,适合请求-响应模式;
  4. 管道适合结构化通信,邮槽适合广播通知;
  5. 跨进程内存操作需要特殊权限,应谨慎使用。

本章代码索引

文件 内容
dll/win32/kernel32/client/virtmem.c(file:///d:/reactos/dll/win32/kernel32/client/virtmem.c) VirtualAllocEx、VirtualFreeEx
dll/win32/kernel32/client/file/filemap.c(file:///d:/reactos/dll/win32/kernel32/client/file/filemap.c) CreateFileMapping、MapViewOfFile
dll/win32/kernel32/client/file/npipe.c(file:///d:/reactos/dll/win32/kernel32/client/file/npipe.c) CreatePipe、CreateNamedPipe
dll/win32/kernel32/client/file/mailslot.c(file:///d:/reactos/dll/win32/kernel32/client/file/mailslot.c) CreateMailslot
ntoskrnl/mm/ARM3/virtual.c(file:///d:/reactos/ntoskrnl/mm/ARM3/virtual.c) NtReadVirtualMemory、NtWriteVirtualMemory、MmCopyVirtualMemory
ntoskrnl/ob/obhandle.c(file:///d:/reactos/ntoskrnl/ob/obhandle.c) NtDuplicateObject
ntoskrnl/lpc/create.c(file:///d:/reactos/ntoskrnl/lpc/create.c) NtCreatePort
ntoskrnl/lpc/connect.c(file:///d:/reactos/ntoskrnl/lpc/connect.c) NtConnectPort
ntoskrnl/lpc/send.c(file:///d:/reactos/ntoskrnl/lpc/send.c) NtRequestPort
ntoskrnl/lpc/reply.c(file:///d:/reactos/ntoskrnl/lpc/reply.c) NtReplyPort
ntoskrnl/lpc/listen.c(file:///d:/reactos/ntoskrnl/lpc/listen.c) NtListenPort
相关推荐
shen121382 小时前
【cdp】windows持久化运行cdp浏览器
windows·agent·cdp
W优化大师2 小时前
Windows电脑频繁弹广告怎么彻底清除?从定位来源到卸载残留的完整方法
windows·电脑
拼搏的小浣熊2 小时前
【通用教程】Windows\+Linux\+银河麒麟系统 固定静态IP地址|解决打印机扫描IP变动、网络掉线问题
linux·网络·windows·麒麟·固定ip·麒麟系统·统信系统
w3296362712 小时前
使用 OpenCode 在 Windows 上加速安装 Playwright 的完整指南
windows·git
dabidai3 小时前
Docker PostgreSQL Windows 权限问题总结
windows·docker·postgresql
l齐天3 小时前
Ubuntu 中编译 Go + PBC 程序为 Windows 11 可运行文件
windows·ubuntu·golang
caimouse3 小时前
Reactos 第 5 章 进程与线程 — 5.14 Windows线程间的相互作用
windows
晓py3 小时前
Windows 本地挂载阿里云 ECS,并使用 Claude 操作挂载路径学习文档
windows·学习·阿里云
阿昭L3 小时前
Windows堆dword shoot
windows·安全·漏洞·堆溢出