Reactos 第 6 章 进程间通信

第 6 章 进程间通信

本节深入剖析 Windows/ReactOS 中进程间通信(IPC)机制的核心实现,重点讲解共享内存区(Section)对象和线程等待/唤醒机制。

概述

进程间通信(IPC, Inter-Process Communication)是操作系统提供的重要功能,允许不同进程之间交换数据和协调执行。在现代操作系统中,由于进程拥有独立的地址空间,直接内存访问是不可能的,必须通过操作系统提供的IPC机制来实现。

为什么需要进程间通信?

进程是资源分配的基本单位,每个进程都有独立的虚拟地址空间。这种隔离确保了进程的稳定性和安全性,但也带来了进程间协作的挑战。IPC机制就是在保证隔离性的同时,提供进程间通信的能力。

进程间通信的需求场景

复制代码
进程间通信需求场景
┌─────────────────────────────────────────────────────────────────┐
│                                                                 │
│   1. 父子进程数据共享                                           │
│      父进程创建子进程后,需要共享某些数据                       │
│      例:Web服务器fork后,子进程共享连接池                      │
│                                                                 │
│   2. 进程间大数据传输                                          │
│      避免大量数据通过管道/套接字复制                            │
│      例:图像处理程序共享大尺寸图像数据                        │
│                                                                 │
│   3. 进程间状态同步                                             │
│      多个进程观察同一资源状态变化                               │
│      例:多个进程监视同一文件变化                               │
│                                                                 │
│   4. 进程间事件通知                                            │
│      一个进程需要通知其他进程某个事件发生                       │
│      例:配置更新通知、设备插入通知                             │
│                                                                 │
│   5. 进程池/线程池资源共享                                     │
│      工作进程从共享队列获取任务                                 │
│      例:数据库连接池、HTTP工作进程池                          │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

Windows IPC机制分类

Windows提供了多种IPC机制,按功能和性能特点分类:

类型 机制 特点 适用场景
共享内存 Section, Memory-Mapped Files 最高效,适合大数据传输 进程池共享数据、大文件访问
进程同步 Mutex, Semaphore, Event 跨进程同步 资源互斥、信号通知
消息传递 管道(Pipe), 邮件槽(Mailslot) 字节流/数据报 命令传递、简单通信
网络通信 套接字(Socket) 跨主机通信 网络应用、分布式系统
远程调用 RPC, ALPC 透明调用 客户端/服务器应用
进程挂靠 Debug, Hook 控制其他进程 调试器、监控工具

Section对象在IPC中的核心地位

在Windows的所有IPC机制中,Section(共享内存区)对象是最基础、最高效的机制。它是其他多种IPC机制(管道、共享内存、文件映射等)的底层实现基础:

复制代码
Section对象在IPC架构中的地位
┌──────────────────────────────────────────────────────────────────────────────┐
│                                                                              │
│   ┌────────────────────────────────────────────────────────────────────┐    │
│   │                    高层IPC机制                                       │    │
│   │  ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐           │    │
│   │  │ 管道     │ │ 共享内存  │ │文件映射  │ │ 邮槽     │           │    │
│   │  └─────┬────┘ └─────┬────┘ └─────┬────┘ └─────┬────┘           │    │
│   └────────┼─────────────┼────────────┼────────────┼─────────────────┘    │
│            │             │            │            │                        │
│            └─────────────┴─────┬──────┴────────────┘                        │
│                                │                                            │
│   ┌────────────────────────────────────────────────────────────────────┐    │
│   │                   Section对象 (核心)                                 │    │
│   │  ┌────────────────────────────────────────────────────────────┐  │    │
│   │  │ • 内存区域抽象              • 跨进程地址空间映射            │  │    │
│   │  │ • 支持文件后备存储          • 可选页面交换支持              │  │    │
│   │  └────────────────────────────────────────────────────────────┘  │    │
│   └────────────────────────────────────────────────────────────────────┘    │
│                                │                                            │
│   ┌────────────────────────────────────────────────────────────────────┐    │
│   │                   内存管理器 (MM)                                   │    │
│   │  • 虚拟地址空间管理    • 物理页面分配    • 页面换入/换出          │    │
│   └────────────────────────────────────────────────────────────────────┘    │
│                                                                              │
└──────────────────────────────────────────────────────────────────────────────┘

本章内容概览

  1. 6.1 概述:IPC需求、机制分类、Section地位
  2. 6.2 共享内存区(Section):Section对象结构、创建、映射机制
  3. 6.3 线程的等待/唤醒机制:跨进程等待、Alertable等待

学习目标

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

  • 理解进程间通信的核心需求和设计权衡
  • 掌握Section对象的内核实现机制
  • 分析内存映射的完整流程
  • 理解跨进程等待的实现原理
  • 根据实际需求选择合适的IPC机制

涉及的内核子系统

子系统 职责
kernel32 用户态IPC API(CreateFileMapping、MapViewOfFile等)
ntdll NtCreateSection、NtMapViewOfSection等Native API
ntoskrnl/mm Section对象管理、内存映射、页面管理
ntoskrnl/ob 对象管理器、句柄转换、跨进程对象访问
ntoskrnl/ke 等待机制、调度器、APC

6.1 概述

6.1.1 进程间通信的基本概念

进程间通信涉及两个核心问题:

  1. 如何标识通信双方:进程通过句柄引用内核对象,但句柄是进程私有的
  2. 如何传输数据:进程地址空间隔离,需要内核协助

内核对象的进程间共享:

Windows通过命名对象继承句柄两种机制实现跨进程对象访问:

复制代码
跨进程对象共享机制
┌──────────────────────────────────────────────────────────────────────────────┐
│                                                                              │
│   方式1: 命名对象                                                           │
│   ┌────────────────────────────────────────────────────────────────────┐    │
│   │ Process A                        Process B                         │    │
│   │ CreateMutex("Global\\MyMutex")   OpenMutex("Global\\MyMutex")    │    │
│   │        │                                │                           │    │
│   │        └────────────┬─────────────────┘                           │    │
│   │                     │                                              │    │
│   │                     ▼                                              │    │
│   │              ┌──────────────┐                                      │    │
│   │              │  Object Mgr  │                                      │    │
│   │              │ (全局命名空间) │                                      │    │
│   │              └──────────────┘                                      │    │
│   └────────────────────────────────────────────────────────────────────┘    │
│                                                                              │
│   方式2: 句柄继承                                                           │
│   ┌────────────────────────────────────────────────────────────────────┐    │
│   │ Process A                        Process B (子进程)                 │    │
│   │ CreateMutex(...)                  (继承父进程句柄)                  │    │
│   │        │                                │                           │    │
│   │        ▼                                ▼                           │    │
│   │   ┌────────┐                     ┌────────┐                       │    │
│   │   │Handle A│ ──── fork ────►   │Handle B│                       │    │
│   │   └────────┘                     └────────┘                       │    │
│   └────────────────────────────────────────────────────────────────────┘    │
│                                                                              │
└──────────────────────────────────────────────────────────────────────────────┘

6.1.2 Section对象概述

Section(节/共享内存区)是Windows内核中表示一块可以映射到进程地址空间的内存区域的对象。Section可以:

  1. 映射文件内容:将文件的部分或全部内容映射到内存
  2. 创建匿名内存区域:分配一块可共享的物理内存(页面文件支持)
  3. 跨进程共享:多个进程可以映射同一个Section

Section的工作原理:

复制代码
Section内存映射工作原理
┌──────────────────────────────────────────────────────────────────────────────┐
│                                                                              │
│   ┌────────────────────────────────────────────────────────────────────┐    │
│   │                     文件后备的Section                                │    │
│   │                                                                     │    │
│   │   ┌─────────────┐          ┌─────────────┐                        │    │
│   │   │  文件内容    │          │   Section   │                        │    │
│   │   │  on disk    │◄────────►│  对象       │                        │    │
│   │   └─────────────┘          └──────┬──────┘                        │    │
│   │                                   │                                 │    │
│   │   ┌─────────────┐               │                                 │    │
│   │   │  Process A   │               │                                 │    │
│   │   │  ┌────────┐ │               │                                 │    │
│   │   │  │ 0x4000 │◄┴───────────────┘                                 │    │
│   │   │  └────────┘ │                                                  │    │
│   │   └─────────────┘                                                  │    │
│   │                                   │                                 │    │
│   │   ┌─────────────┐               │                                 │    │
│   │   │  Process B   │◄──────────────┘                                 │    │
│   │   │  ┌────────┐ │                                                  │    │
│   │   │  │ 0x6000 │ │ (不同虚拟地址,相同物理页面)                      │    │
│   │   │  └────────┘ │                                                  │    │
│   │   └─────────────┘                                                  │    │
│   └────────────────────────────────────────────────────────────────────┘    │
│                                                                              │
│   ┌────────────────────────────────────────────────────────────────────┐    │
│   │                     页面文件支持的Section                             │    │
│   │                                                                     │    │
│   │   ┌─────────────┐          ┌─────────────┐                        │    │
│   │   │  Page File  │          │   Section   │                        │    │
│   │   │  (页面文件)  │◄────────►│  对象       │                        │    │
│   │   └─────────────┘          └──────┬──────┘                        │    │
│   │                                   │                                 │    │
│   │   进程A映射到0x4000     进程B映射到0x5000                          │    │
│   │   共享物理页面P1              共享物理页面P1                        │    │
│   └────────────────────────────────────────────────────────────────────┘    │
│                                                                              │
└──────────────────────────────────────────────────────────────────────────────┘

6.1.3 等待/唤醒机制在IPC中的作用

等待/唤醒机制是IPC中不可或缺的组成部分,用于:

  1. 同步访问共享数据:确保多个进程对共享Section的安全访问

  2. 事件通知:一个进程通知其他进程数据已准备好

  3. 资源协调:管理对有限资源的竞争访问

    IPC中的等待/唤醒机制
    ┌──────────────────────────────────────────────────────────────────────────────┐
    │ │
    │ ┌────────────────────────────────────────────────────────────────────┐ │
    │ │ 共享内存通信模式 │ │
    │ │ │ │
    │ │ Process A Process B │ │
    │ │ ┌────────────────┐ ┌────────────────┐ │ │
    │ │ │ 写入数据 │ │ 等待数据就绪 │ │ │
    │ │ │ SetEvent(hEvent)│──────────────►│ WaitFor... │ │ │
    │ │ └────────────────┘ └────────────────┘ │ │
    │ │ │ │
    │ │ ┌────────────────┐ ┌────────────────┐ │ │
    │ │ │ 读取确认 │◄───────────────│ 读取数据 │ │ │
    │ │ │ WaitFor... │ │ SetEvent │ │ │
    │ │ └────────────────┘ └────────────────┘ │ │
    │ └────────────────────────────────────────────────────────────────────┘ │
    │ │
    └──────────────────────────────────────────────────────────────────────────────┘


6.2 共享内存区(Section)

Section对象是Windows内核中最重要的高层抽象之一,它提供了将文件或物理内存映射到进程虚拟地址空间的能力。

6.2.1 Section对象的类型

Section对象按其后备存储分类:

类型 后备存储 创建方式 特点
文件映射 文件内容 NtCreateSection + FileHandle 内容来自文件,支持持久化
页面文件Section 页面文件 NtCreateSection 无FileHandle 内容在页面文件中,不持久化
物理内存Section 物理内存 特定API 映射物理内存(驱动使用)

6.2.2 SECTION_OBJECT_POINTERS结构

文件对象通过SECTION_OBJECT_POINTERS引用Section对象:

c 复制代码
typedef struct _SECTION_OBJECT_POINTERS {
    PVOID DataSectionObject;      // 数据Section对象(文件数据)
    PVOID SharedCacheMap;        // 共享缓存映射
    PVOID ImageSectionObject;     // 图像Section对象(可执行文件)
} SECTION_OBJECT_POINTERS, *PSECTION_OBJECT_POINTERS;

结构关系图:

复制代码
文件对象的Section指针
┌──────────────────────────────────────────────────────────────────────────────┐
│                                                                              │
│   FILE_OBJECT                                                                │
│   ┌────────────────────────────────────────────────────────────────────┐    │
│   │  SectionObjectPointers                                               │    │
│   │  ┌────────────────────────────────────────────────────────────┐  │    │
│   │  │  DataSectionObject ──────────────► SECTION_OBJECT ───────►  │  │    │
│   │  │  SharedCacheMap    ──────────────► SHARED_CACHE_MAP         │  │    │
│   │  │  ImageSectionObject ─────────────► SECTION_OBJECT ───────►  │  │    │
│   │  └────────────────────────────────────────────────────────────┘  │    │
│   └────────────────────────────────────────────────────────────────────┘    │
│                                                                              │
└──────────────────────────────────────────────────────────────────────────────┘

6.2.3 NtCreateSection系统调用

NtCreateSection创建或打开一个Section对象:

c 复制代码
NTSTATUS
NTAPI
NtCreateSection(OUT PHANDLE SectionHandle,
                IN ACCESS_MASK DesiredAccess,
                IN POBJECT_ATTRIBUTES ObjectAttributes OPTIONAL,
                IN PLARGE_INTEGER MaximumSize OPTIONAL,
                IN ULONG SectionPageProtection,
                IN ULONG AllocationAttributes,
                IN HANDLE FileHandle OPTIONAL)
{
    // ... 参数验证 ...

    PAGED_CODE();
    Status = MmCreateSection(SectionHandle,
                            DesiredAccess,
                            ObjectAttributes,
                            MaximumSize,
                            SectionPageProtection,
                            AllocationAttributes,
                            FileHandle,
                            NULL);
    return Status;
}

关键参数说明:

参数 说明 常见值
SectionHandle 输出:创建的Section句柄 -
DesiredAccess 访问权限 SECTION_ALL_ACCESS, SECTION_MAP_READ/WRITE
ObjectAttributes 对象属性(含名称) 可指定全局名称用于跨进程
MaximumSize Section最大字节数 指定文件或页面文件区域大小
SectionPageProtection 页面保护 PAGE_READONLY, PAGE_READWRITE, PAGE_WRITECOPY
AllocationAttributes 分配属性 SEC_COMMIT, SEC_RESERVE, SEC_IMAGE, SEC_FILE
FileHandle 文件句柄(可选) 有则为文件映射,无则为页面文件Section

6.2.4 NtMapViewOfSection系统调用

NtMapViewOfSection将Section映射到进程的虚拟地址空间:

c 复制代码
NTSTATUS
NTAPI
NtMapViewOfSection(IN HANDLE SectionHandle,
                   IN HANDLE ProcessHandle,
                   IN OUT PVOID *BaseAddress,
                   IN ULONG_PTR ZeroBits,
                   IN SIZE_T CommitSize,
                   IN OUT PLARGE_INTEGER SectionOffset OPTIONAL,
                   IN OUT PSIZE_T ViewSize,
                   IN SECTION_INHERIT InheritDisposition,
                   IN ULONG AllocationType,
                   IN ULONG Protect)
{
    // ... 参数验证 ...
    PAGED_CODE();
    Status = ObReferenceObjectByHandle(ProcessHandle,
                                        PROCESS_ALL_ACCESS,
                                        PsProcessType,
                                        KernelMode,
                                        (PVOID*)&Process,
                                        NULL);
    if (!NT_SUCCESS(Status))
        return Status;

    Status = MmMapViewOfSection(SectionHandle,
                               Process,
                               BaseAddress,
                               ZeroBits,
                               CommitSize,
                               SectionOffset,
                               ViewSize,
                               InheritDisposition,
                               AllocationType,
                               Protect);
    ObDereferenceObject(Process);
    return Status;
}

关键参数说明:

参数 说明 常见值
SectionHandle Section句柄 -
ProcessHandle 目标进程句柄 当前进程或目标进程
BaseAddress 输入/输出:映射基地址 NULL(系统分配)或指定地址
ZeroBits 零位个数(地址空间限制) 0表示无限制
SectionOffset Section内偏移 指定映射的起始位置
ViewSize 输入/输出:映射大小 指定映射的字节数
InheritDisposition 继承方式 ViewShare(共享)或ViewUnmap(不继承)

6.2.5 Section的内存映射机制

复制代码
内存映射完整流程
┌──────────────────────────────────────────────────────────────────────────────┐
│                                                                              │
│   1. 创建Section                                                             │
│   ┌────────────────────────────────────────────────────────────────────┐    │
│   │ NtCreateSection(FileHandle, &SectionHandle)                        │    │
│   │     │                                                              │    │
│   │     ▼                                                              │    │
│   │ MmCreateSection ──► 创建SECTION_OBJECT                              │    │
│   │     │                                                              │    │
│   │     ├──► 有FileHandle: 创建文件后备的Section                       │    │
│   │     └──► 无FileHandle: 创建页面文件Section                        │    │
│   └────────────────────────────────────────────────────────────────────┘    │
│                                                                              │
│   2. 映射Section到进程A                                                     │
│   ┌────────────────────────────────────────────────────────────────────┐    │
│   │ NtMapViewOfSection(SectionHandle, ProcessA, &AddrA, ...)            │    │
│   │     │                                                              │    │
│   │     ▼                                                              │    │
│   │ MmMapViewOfSection                                                 │    │
│   │     │                                                              │    │
│   │     ├──► 分配虚拟地址范围                                          │    │
│   │     ├──► 创建虚拟地址描述符(VAD)                                   │    │
│   │     └──► 建立Section与VAD的关联                                    │    │
│   └────────────────────────────────────────────────────────────────────┘    │
│                                                                              │
│   3. 映射Section到进程B                                                     │
│   ┌────────────────────────────────────────────────────────────────────┐    │
│   │ NtMapViewOfSection(SectionHandle, ProcessB, &AddrB, ...)            │    │
│   │     │                                                              │    │
│   │     ▼                                                              │    │
│   │ MmMapViewOfSection                                                 │    │
│   │     │                                                              │    │
│   │     ├──► 分配另一个虚拟地址范围                                    │    │
│   │     ├──► 创建VAD(指向同一Section)                                │    │
│   │     └──► 进程A和进程B共享同一物理页面                              │    │
│   └────────────────────────────────────────────────────────────────────┘    │
│                                                                              │
│   4. 页面访问时                                                             │
│   ┌────────────────────────────────────────────────────────────────────┐    │
│   │ Process A 访问地址 AddrA                                            │    │
│   │     │                                                              │    │
│   │     ▼                                                              │    │
│   │ 页面错误处理                                                       │    │
│   │     │                                                              │    │
│   │     ├──► Section来自文件: 从文件读取页面                           │    │
│   │     └──► Section来自页面文件: 分配物理页面                        │    │
│   │     │                                                              │    │
│   │     ▼                                                              │    │
│   │ 映射物理页面P1                                                     │    │
│   │     │                                                              │    │
│   │ Process B 访问地址 AddrB时,同样映射到P1                          │    │
│   └────────────────────────────────────────────────────────────────────┘    │
│                                                                              │
└──────────────────────────────────────────────────────────────────────────────┘

6.2.6 跨进程共享内存的实现

跨进程共享Section的核心步骤:

复制代码
跨进程共享内存实现流程
┌──────────────────────────────────────────────────────────────────────────────┐
│                                                                              │
│   进程A (创建者)                        进程B (使用者)                     │
│   ┌──────────────────────┐            ┌──────────────────────┐             │
│   │ 1. 创建命名Section    │            │                      │             │
│   │ hSection = CreateSection│           │                      │             │
│   │ ("Global\\MyShared")   │            │                      │             │
│   └──────────────────────┘            └──────────────────────┘             │
│              │                                      │                        │
│              │  Section对象已存在                   │                        │
│              │  于全局命名空间                      │                        │
│              ▼                                      ▼                        │
│   ┌────────────────────────────────────────────────────────────────────┐  │
│   │                      内核对象管理器                                  │  │
│   │                         Section对象                                │  │
│   │                      (全局命名)                                    │  │
│   └────────────────────────────────────────────────────────────────────┘  │
│              │                                      │                        │
│              │                                      │                        │
│              ▼                                      ▼                        │
│   ┌──────────────────────┐            ┌──────────────────────┐             │
│   │ 2. 映射到进程A       │            │ 3. 打开并映射Section │             │
│   │ MapViewOfFile       │            │ hSection = OpenSection│            │
│   │ pA = 0x40000000     │            │ pB = 0x50000000      │             │
│   └──────────────────────┘            └──────────────────────┘             │
│              │                                      │                        │
│              │              ┌────────────────────┘                        │
│              │              │                                             │
│              ▼              ▼                                             │
│   ┌────────────────────────────────────────────────────────────────────┐  │
│   │                    共享物理内存页面                                 │  │
│   │                  (同一页面可被两进程访问)                          │  │
│   └────────────────────────────────────────────────────────────────────┘  │
│                                                                              │
└──────────────────────────────────────────────────────────────────────────────┘

6.2.7 文件映射与匿名Section

文件映射Section:

c 复制代码
// 创建文件映射Section
HANDLE hFile = CreateFile("data.bin", ...);
HANDLE hSection = CreateFileMapping(hFile, NULL, PAGE_READWRITE, 0, 0, "MyMapping");

// 映射到地址空间
PVOID pData = MapViewOfFile(hSection, FILE_MAP_ALL_ACCESS, 0, 0, 0);

// 使用后清理
UnmapViewOfFile(pData);
CloseHandle(hSection);
CloseHandle(hFile);

匿名Section(页面文件支持):

c 复制代码
// 创建匿名Section(无文件后备)
HANDLE hSection = CreateFileMapping(INVALID_HANDLE_VALUE, NULL,
                                   PAGE_READWRITE, 0, 4096, NULL);

// 映射到地址空间
PVOID pData = MapViewOfFile(hSection, FILE_MAP_ALL_ACCESS, 0, 0, 4096);

// pData指向的内存由页面文件支持,进程退出后内容丢失

6.2.8 Section的访问保护

Section页面的保护属性决定了映射后页面的访问权限:

SectionPageProtection 映射后Protect 说明
PAGE_READONLY PAGE_READONLY 只读访问
PAGE_WRITECOPY PAGE_WRITECOPY 写时复制(共享时各进程独立副本)
PAGE_READWRITE PAGE_READWRITE 读写访问
PAGE_EXECUTE PAGE_EXECUTE 可执行
PAGE_EXECUTE_READ PAGE_EXECUTE_READ 执行+读
PAGE_EXECUTE_READWRITE PAGE_EXECUTE_READWRITE 执行+读写
PAGE_EXECUTE_WRITECOPY PAGE_EXECUTE_WRITECOPY 执行+写时复制

写时复制(COW)机制:

复制代码
写时复制(Copy-On-Write)机制
┌──────────────────────────────────────────────────────────────────────────────┐
│                                                                              │
│   初始状态                                                                   │
│   ┌────────────────────────────────────────────────────────────────────┐    │
│   │ Process A                    Process B                             │    │
│   │ 0x400000: [RO] ────────►   页面P1 (只读共享)                    │    │
│   └────────────────────────────────────────────────────────────────────┘    │
│                                                                              │
│   Process A尝试写入                                                          │
│   ┌────────────────────────────────────────────────────────────────────┐    │
│   │ 页面错误发生                                                         │    │
│   │      │                                                               │    │
│   │      ▼                                                               │    │
│   │ MM检查保护属性: PAGE_WRITECOPY                                        │    │
│   │      │                                                               │    │
│   │      ▼                                                               │    │
│   │ 分配新页面P2                                                         │    │
│   │ 复制P1内容到P2                                                       │    │
│   │ 更新Process A的页表映射: 0x400000 → P2                               │    │
│   │ 设置P2为可读写                                                       │    │
│   └────────────────────────────────────────────────────────────────────┘    │
│                                                                              │
│   写入后状态                                                                │
│   ┌────────────────────────────────────────────────────────────────────┐    │
│   │ Process A                    Process B                              │    │
│   │ 0x400000: [RW] ────────►   页面P2 (独立副本)                     │    │
│   │ (内容已被修改)              页面P1 (保持原样)                       │    │
│   └────────────────────────────────────────────────────────────────────┘    │
│                                                                              │
└──────────────────────────────────────────────────────────────────────────────┘

6.2.9 完整示例代码

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

#define SHARED_DATA_SIZE 4096

// 共享数据结构
typedef struct {
    int dataReady;
    char message[256];
    int counter;
} SharedData;

// 创建共享内存Section(进程A)
void CreateSharedMemoryExample()
{
    HANDLE hSection, hMapFile;
    SharedData *pSharedData;

    // 创建文件映射对象(使用页面文件)
    hSection = CreateFileMapping(
        INVALID_HANDLE_VALUE,    // 使用页面文件
        NULL,                   // 默认安全属性
        PAGE_READWRITE,         // 可读写
        0,                      // 高32位大小
        SHARED_DATA_SIZE,        // 低32位大小
        L"MySharedMemory");      // 命名(用于跨进程)

    if (hSection == NULL)
    {
        printf("CreateFileMapping failed: %d\n", GetLastError());
        return;
    }

    // 检查是否已存在(其他进程可能已创建)
    if (GetLastError() == ERROR_ALREADY_EXISTS)
    {
        printf("Section already exists\n");
    }
    else
    {
        printf("Section created\n");
    }

    // 映射到当前进程地址空间
    pSharedData = (SharedData *)MapViewOfFile(
        hSection,                // 文件映射对象句柄
        FILE_MAP_ALL_ACCESS,    // 读写访问
        0,                      // 高32位偏移
        0,                      // 低32位偏移
        0);                     // 映射整个Section

    if (pSharedData == NULL)
    {
        printf("MapViewOfFile failed: %d\n", GetLastError());
        CloseHandle(hSection);
        return;
    }

    // 初始化共享数据
    pSharedData->dataReady = 0;
    pSharedData->counter = 0;
    strcpy(pSharedData->message, "Hello from Process A");

    printf("Shared memory initialized at %p\n", pSharedData);
    printf("Message: %s\n", pSharedData->message);

    // 保持映射直到不再需要
    // ...

    // 清理
    UnmapViewOfFile(pSharedData);
    CloseHandle(hSection);
}

// 打开共享内存Section(进程B)
void OpenSharedMemoryExample()
{
    HANDLE hSection;
    SharedData *pSharedData;

    // 打开已存在的文件映射
    hSection = OpenFileMapping(
        FILE_MAP_ALL_ACCESS,    // 读写访问
        FALSE,                  // 不继承句柄
        L"MySharedMemory");     // Section名称

    if (hSection == NULL)
    {
        printf("OpenFileMapping failed: %d\n", GetLastError());
        return;
    }

    // 映射到当前进程地址空间
    pSharedData = (SharedData *)MapViewOfFile(
        hSection,
        FILE_MAP_ALL_ACCESS,
        0, 0, 0);

    if (pSharedData == NULL)
    {
        printf("MapViewOfFile failed: %d\n", GetLastError());
        CloseHandle(hSection);
        return;
    }

    printf("Opened shared memory at %p\n", pSharedData);
    printf("Message from Process A: %s\n", pSharedData->message);
    printf("Counter: %d\n", pSharedData->counter);

    // 读取共享数据
    pSharedData->counter++;
    printf("Counter after increment: %d\n", pSharedData->counter);

    // 清理
    UnmapViewOfFile(pSharedData);
    CloseHandle(hSection);
}

// 使用命名Section实现父进程-子进程共享
void ParentChildSharedMemoryExample()
{
    HANDLE hSection;
    SharedData *pSharedData;
    PROCESS_INFORMATION pi;
    STARTUPINFO si;

    // 创建Section
    hSection = CreateFileMapping(
        INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE,
        0, SHARED_DATA_SIZE, NULL);

    if (hSection == NULL)
    {
        printf("CreateFileMapping failed\n");
        return;
    }

    // 映射到父进程
    pSharedData = (SharedData *)MapViewOfFile(
        hSection, FILE_MAP_ALL_ACCESS, 0, 0, 0);
    pSharedData->dataReady = 0;
    pSharedData->counter = 0;

    // 创建子进程(继承句柄)
    ZeroMemory(&si, sizeof(si));
    si.cb = sizeof(si);
    CreateProcess(NULL, L"child.exe", NULL, NULL, TRUE,
                  0, NULL, NULL, &si, &pi);

    // 等待子进程初始化
    while (pSharedData->dataReady == 0)
        Sleep(10);

    printf("Parent: Received data from child: %s\n", pSharedData->message);

    // 清理
    CloseHandle(pi.hProcess);
    CloseHandle(pi.hThread);
    UnmapViewOfFile(pSharedData);
    CloseHandle(hSection);
}

// 文件映射示例
void FileMappingExample()
{
    HANDLE hFile, hSection;
    char *pFileData;

    // 打开文件
    hFile = CreateFile(L"data.bin", GENERIC_READ | GENERIC_WRITE,
                       0, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);

    if (hFile == INVALID_HANDLE_VALUE)
    {
        printf("CreateFile failed\n");
        return;
    }

    // 创建文件映射
    hSection = CreateFileMapping(hFile, NULL, PAGE_READWRITE,
                                 0, 0, NULL);
    if (hSection == NULL)
    {
        printf("CreateFileMapping failed\n");
        CloseHandle(hFile);
        return;
    }

    // 映射视图
    pFileData = (char *)MapViewOfFile(hSection, FILE_MAP_ALL_ACCESS, 0, 0, 0);
    if (pFileData == NULL)
    {
        printf("MapViewOfFile failed\n");
        CloseHandle(hSection);
        CloseHandle(hFile);
        return;
    }

    // 现在pFileData指向文件内容
    // 对pFileData的修改会反映到文件中
    strcpy(pFileData, "Modified via memory mapping");

    printf("File content modified\n");

    // 清理
    UnmapViewOfFile(pFileData);
    CloseHandle(hSection);
    CloseHandle(hFile);
}

6.3 线程的等待/唤醒机制

6.3.1 等待机制回顾

Windows的等待机制允许线程阻塞自己,直到指定的条件满足。在进程间通信中,等待机制用于:

  1. 同步对共享内存的访问:配合互斥体或信号量

  2. 等待数据准备好通知:生产者-消费者模式

  3. 协调进程间操作顺序:确保操作按正确顺序执行

    IPC中的等待机制应用
    ┌──────────────────────────────────────────────────────────────────────────────┐
    │ │
    │ ┌────────────────────────────────────────────────────────────────────┐ │
    │ │ 典型IPC同步模式 │ │
    │ │ │ │
    │ │ 进程A 进程B │ │
    │ │ ┌────────────────┐ ┌────────────────┐ │ │
    │ │ │ 写入共享内存 │ │ │ │ │
    │ │ │ SetEvent写完) │──────────────►│ Wait写完事件 │ │ │
    │ │ └────────────────┘ └────────────────┘ │ │
    │ │ │ │ │
    │ │ ▼ │ │
    │ │ ┌────────────────┐ │ │
    │ │ │ 读取共享内存 │ │ │
    │ │ │ SetEvent读完) │ │ │
    │ │ └────────────────┘ │ │
    │ │ │ │ │
    │ │ ┌────────────────┐ │ │ │
    │ │ │ Wait读完成事件 │◄─────────────┘ │ │
    │ │ └────────────────┘ │ │
    │ └────────────────────────────────────────────────────────────────────┘ │
    │ │
    └──────────────────────────────────────────────────────────────────────────────┘

6.3.2 对象句柄与进程上下文

跨进程等待的关键在于句柄的有效性和对象引用:

复制代码
句柄与进程上下文
┌──────────────────────────────────────────────────────────────────────────────┐
│                                                                              │
│   句柄的进程相关性                                                           │
│   ┌────────────────────────────────────────────────────────────────────┐    │
│   │                                                                     │    │
│   │ Process A                                                          │    │
│   │   hEvent = CreateEvent(...)  // 句柄值可能是0x4                   │    │
│   │   进程表: 0x4 → Event对象                                          │    │
│   │                                                                     │    │
│   │ Process B                                                          │    │
│   │   hEvent = CreateEvent(...)  // 句柄值也可能是0x4                 │    │
│   │   进程表: 0x4 → 不同的Event对象(进程私有)                        │    │
│   │                                                                     │    │
│   └────────────────────────────────────────────────────────────────────┘    │
│                                                                              │
│   命名对象的跨进程访问                                                       │
│   ┌────────────────────────────────────────────────────────────────────┐    │
│   │                                                                     │    │
│   │ Process A                                                          │    │
│   │   CreateEvent(NULL, FALSE, FALSE, "Global\\MyEvent")               │    │
│   │   → 在全局对象管理器中创建命名的Event对象                           │    │
│   │                                                                     │    │
│   │ Process B                                                          │    │
│   │   OpenEvent(EVENT_ALL_ACCESS, FALSE, "Global\\MyEvent")           │    │
│   │   → 获得对同一Event对象的引用                                      │    │
│   │                                                                     │    │
│   │   两个进程可以使用各自的句柄等待/设置同一个Event                     │    │
│   │                                                                     │    │
│   └────────────────────────────────────────────────────────────────────┘    │
│                                                                              │
└──────────────────────────────────────────────────────────────────────────────┘

6.3.3 NtWaitForSingleObject实现

NtWaitForSingleObject等待单个对象变为有信号状态:

c 复制代码
NTSTATUS
NTAPI
NtWaitForSingleObject(IN HANDLE Handle,
                      IN BOOLEAN Alertable,
                      IN PLARGE_INTEGER Timeout OPTIONAL)
{
    PKGUARDED_MUTEX mutex;
    POBJECT_OBJECT Object;
    HANDLE hObject = Handle;
    KPROCESSOR_MODE PreviousMode = KeGetPreviousMode();
    LARGE_INTEGER Time, NewTime;
    NTSTATUS WaitStatus;
    PLARGE_INTEGER DueTime = Timeout;

    PAGED_CODE();

    // 参数验证
    if (Alertable)
    {
        // 检查激活上下文
    }

    // 将句柄转换为对象引用
    Status = ObReferenceObjectByHandle(hObject,
                                       0,
                                       NULL,
                                       PreviousMode,
                                       &Object,
                                       NULL);

    // 检查对象类型
    if (Object->Type == ThreadObject)
    {
        // 等待线程...
    }
    else if (Object->Type == MutantObject)
    {
        // 等待互斥体...
    }
    else if (Object->Type == EventObject)
    {
        // 等待事件...
    }
    else
    {
        // 其他同步对象...
        Status = KeWaitForSingleObject(Object,
                                      WrObject,
                                      PreviousMode,
                                      Alertable,
                                      DueTime);
    }

    // 解除对象引用
    ObDereferenceObject(Object);

    return Status;
}

源码位置ntoskrnl/ob/obwait.c(file:///d:/reactos/ntoskrnl/ob/obwait.c)

6.3.4 NtWaitForMultipleObjects实现

NtWaitForMultipleObjects等待多个对象:

c 复制代码
NTSTATUS
NTAPI
NtWaitForMultipleObjects(IN ULONG Count,
                        IN HANDLE Handles[],
                        IN WAIT_TYPE WaitType,
                        IN BOOLEAN Alertable,
                        IN PLARGE_INTEGER Timeout OPTIONAL)
{
    POBJECT_OBJECT *Objects;
    HANDLE Handle;
    ULONG i;
    KPROCESSOR_MODE PreviousMode = KeGetPreviousMode();
    NTSTATUS Status;

    PAGED_CODE();

    // 检查对象数量限制
    if (Count > MAXIMUM_WAIT_OBJECTS)
        return STATUS_INVALID_PARAMETER;

    // 分配对象数组
    Objects = ExAllocatePoolWithTag(NonPagedPool, sizeof(OBJECT_OBJECT*) * Count, 'jboW');
    if (!Objects)
        return STATUS_NO_MEMORY;

    // 将所有句柄转换为对象引用
    for (i = 0; i < Count; i++)
    {
        Handle = Handles[i];
        Status = ObReferenceObjectByHandle(Handle,
                                           0,
                                           NULL,
                                           PreviousMode,
                                           &Objects[i],
                                           NULL);
        if (!NT_SUCCESS(Status))
        {
            // 清理已引用的对象
            while (i > 0)
            {
                i--;
                ObDereferenceObject(Objects[i]);
            }
            ExFreePoolWithTag(Objects, 'jboW');
            return Status;
        }
    }

    // 调用内核等待函数
    Status = KeWaitForMultipleObjects(Count,
                                     Objects,
                                     WaitType,
                                     WrMultipleObjects,
                                     PreviousMode,
                                     Alertable,
                                     Timeout,
                                     NULL);

    // 清理对象引用
    for (i = 0; i < Count; i++)
    {
        ObDereferenceObject(Objects[i]);
    }

    ExFreePoolWithTag(Objects, 'jboW');

    return Status;
}

源码位置ntoskrnl/ob/obwait.c(file:///d:/reactos/ntoskrnl/ob/obwait.c)

6.3.5 跨进程等待的实现

跨进程等待的实现依赖于对象管理器的支持:

复制代码
跨进程等待实现
┌──────────────────────────────────────────────────────────────────────────────┐
│                                                                              │
│   1. 进程A创建命名事件                                                       │
│   ┌────────────────────────────────────────────────────────────────────┐    │
│   │ hEvent = CreateEvent(NULL, FALSE, FALSE, "Global\\IPCEvent");     │    │
│   │     │                                                              │    │
│   │     ▼                                                              │    │
│   │ ObCreateObject ──► 创建EVENT_OBJECT                                │    │
│   │     │                                                              │    │
│   │     ▼                                                              │    │
│   │ ObInsertObject ──► 插入全局对象目录                                │    │
│   │     │                           "Global\\IPCEvent" ──► Event       │    │
│   │     ▼                                                              │    │
│   │ 返回进程A的句柄                                                      │    │
│   └────────────────────────────────────────────────────────────────────┘    │
│                                                                              │
│   2. 进程B打开命名事件                                                       │
│   ┌────────────────────────────────────────────────────────────────────┐    │
│   │ hEvent = OpenEvent(EVENT_ALL_ACCESS, FALSE, "Global\\IPCEvent");   │    │
│   │     │                                                              │    │
│   │     ▼                                                              │    │
│   │ ObOpenObjectByName ──► 在全局对象目录查找                          │    │
│   │     │                           "Global\\IPCEvent"                 │    │
│   │     ▼                                                              │    │
│   │ 找到已存在的Event对象,增加引用计数                                  │    │
│   │     │                                                              │    │
│   │     ▼                                                              │    │
│   │ 返回进程B的句柄(指向同一对象)                                      │    │
│   └────────────────────────────────────────────────────────────────────┘    │
│                                                                              │
│   3. 进程B等待事件                                                           │
│   ┌────────────────────────────────────────────────────────────────────┐    │
│   │ WaitForSingleObject(hEvent, INFINITE);                             │    │
│   │     │                                                              │    │
│   │     ▼                                                              │    │
│   │ ObReferenceObjectByHandle ──► 验证句柄有效性                        │    │
│   │     │                                                              │    │
│   │     ▼                                                              │    │
│   │ KeWaitForSingleObject(Event, ...)                                   │    │
│   │     │                                                              │    │
│   │     ├──► 检查Event->Header.SignalState                             │    │
│   │     └──► 如无信号,链入Event->Header.WaitListHead                   │    │
│   └────────────────────────────────────────────────────────────────────┘    │
│                                                                              │
│   4. 进程A设置事件                                                           │
│   ┌────────────────────────────────────────────────────────────────────┐    │
│   │ SetEvent(hEvent);                                                  │    │
│   │     │                                                              │    │
│   │     ▼                                                              │    │
│   │ KeSetEvent(Event, ...) ──► SignalState = 1                          │    │
│   │     │                                                              │    │
│   │     ▼                                                              │    │
│   │ KiWaitTest ──► 遍历WaitListHead,唤醒所有/一个等待者               │    │
│   │     │                                                              │    │
│   │     ▼                                                              │    │
│   │ KiUnwaitThread ──► 进程B从Waiting变为Ready                          │    │
│   └────────────────────────────────────────────────────────────────────┘    │
│                                                                              │
└──────────────────────────────────────────────────────────────────────────────┘

6.3.6 等待超时处理

等待函数支持超时机制:

c 复制代码
// 超时参数说明
WaitForSingleObject(hEvent, 5000);  // 等待5秒
WaitForSingleObject(hEvent, 100);   // 等待100毫秒
WaitForSingleObject(hEvent, INFINITE); // 无限等待

// 超时返回值
switch (WaitForSingleObject(hEvent, 5000))
{
    case WAIT_OBJECT_0:
        printf("对象有信号\n");
        break;
    case WAIT_TIMEOUT:
        printf("等待超时\n");
        break;
    case WAIT_FAILED:
        printf("等待失败: %d\n", GetLastError());
        break;
}

超时内部处理:

  1. 每个等待线程关联一个定时器
  2. 设置等待时,插入定时器到系统定时器队列
  3. 超时触发时,从等待列表移除线程
  4. 线程变为Ready状态

6.3.7 Alertable等待与APC

Alertable等待允许异步操作(如APC)打断等待:

c 复制代码
// Alertable vs 非Alertable
WaitForSingleObject(hEvent, INFINITE);        // 非Alertable,APC无法打断
WaitForSingleObjectEx(hEvent, INFINITE, TRUE); // Alertable,APC可打断

// APC打断等待的返回值
DWORD result = WaitForSingleObjectEx(hEvent, INFINITE, TRUE);
switch (result)
{
    case WAIT_OBJECT_0:
        printf("对象有信号\n");
        break;
    case WAIT_IO_COMPLETION:
        printf("被APC打断(I/O完成)\n");
        // 需要重新等待或处理结果
        break;
    case WAIT_TIMEOUT:
        printf("超时\n");
        break;
}

Alertable等待的应用场景:

  1. 异步I/O完成通知:读取操作完成后触发APC
  2. 定时器回调:定时器到期时触发APC
  3. 线程池工作项:在线程池等待时处理工作项

6.3.8 完整示例代码

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

// 基本等待示例
void BasicWaitExample()
{
    HANDLE hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);

    // 创建工作线程
    HANDLE hThread = CreateThread(NULL, 0,
        [](LPVOID hEvent) -> DWORD {
            printf("Worker: Starting work...\n");
            Sleep(2000);
            printf("Worker: Work done, setting event\n");
            SetEvent((HANDLE)hEvent);
            return 0;
        }, hEvent, 0, NULL);

    printf("Main: Waiting for worker...\n");
    DWORD result = WaitForSingleObject(hEvent, INFINITE);

    if (result == WAIT_OBJECT_0)
        printf("Main: Worker signaled completion\n");

    CloseHandle(hThread);
    CloseHandle(hEvent);
}

// 跨进程同步示例
void IPCSyncExample()
{
    // 创建命名事件
    HANDLE hDataReady = CreateEvent(NULL, FALSE, FALSE, L"Global\\DataReady");
    HANDLE hDataConsumed = CreateEvent(NULL, FALSE, FALSE, L"Global\\DataConsumed");

    // 创建共享内存
    HANDLE hSection = CreateFileMapping(INVALID_HANDLE_VALUE, NULL,
                                       PAGE_READWRITE, 0, 4096, L"Global\\SharedData");
    char *pData = (char *)MapViewOfFile(hSection, FILE_MAP_ALL_ACCESS, 0, 0, 0);

    // 进程A(生产者)
    void Producer()
    {
        while (TRUE)
        {
            // 等待消费者准备好
            WaitForSingleObject(hDataConsumed, INFINITE);

            // 生成新数据
            sprintf(pData, "Data from %d", GetTickCount());
            printf("Producer: Generated data: %s\n", pData);

            // 通知消费者数据就绪
            SetEvent(hDataReady);
        }
    }

    // 进程B(消费者)- 模拟
    void Consumer()
    {
        // 通知生产者已准备好
        SetEvent(hDataConsumed);

        while (TRUE)
        {
            // 等待数据就绪
            WaitForSingleObject(hDataReady, INFINITE);

            // 处理数据
            printf("Consumer: Received data: %s\n", pData);

            // 通知生产者已处理完成
            SetEvent(hDataConsumed);
        }
    }

    // 清理
    UnmapViewOfFile(pData);
    CloseHandle(hSection);
    CloseHandle(hDataReady);
    CloseHandle(hDataConsumed);
}

// 超时等待示例
void TimeoutWaitExample()
{
    HANDLE hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);

    printf("Waiting for 3 seconds...\n");
    DWORD result = WaitForSingleObject(hEvent, 3000);

    switch (result)
    {
        case WAIT_TIMEOUT:
            printf("Timeout - event not signaled\n");
            break;
        case WAIT_OBJECT_0:
            printf("Event was signaled\n");
            break;
        case WAIT_FAILED:
            printf("Wait failed: %d\n", GetLastError());
            break;
    }

    CloseHandle(hEvent);
}

// 等待多个对象示例
void MultipleWaitExample()
{
    HANDLE hEvents[3];
    for (int i = 0; i < 3; i++)
        hEvents[i] = CreateEvent(NULL, FALSE, FALSE, NULL);

    // 随机设置一个事件
    int signalIndex = rand() % 3;
    SetEvent(hEvents[signalIndex]);

    // 等待任一事件(WaitAny)
    DWORD result = WaitForMultipleObjects(3, hEvents, FALSE, INFINITE);

    if (result >= WAIT_OBJECT_0 && result < WAIT_OBJECT_0 + 3)
    {
        printf("Event %d was signaled\n", result - WAIT_OBJECT_0);
    }

    // 等待所有事件(WaitAll)
    for (int i = 0; i < 3; i++)
        SetEvent(hEvents[i]);

    result = WaitForMultipleObjects(3, hEvents, TRUE, INFINITE);
    if (result == WAIT_OBJECT_0)
        printf("All events were signaled\n");

    for (int i = 0; i < 3; i++)
        CloseHandle(hEvents[i]);
}

// Alertable等待示例
void AlertableWaitExample()
{
    HANDLE hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);

    printf("Alertable wait starting...\n");

    // Alertable等待,可被APC打断
    DWORD result = WaitForSingleObjectEx(hEvent, 5000, TRUE);

    switch (result)
    {
        case WAIT_OBJECT_0:
            printf("Event signaled\n");
            break;
        case WAIT_IO_COMPLETION:
            printf("Interrupted by I/O completion APC\n");
            break;
        case WAIT_TIMEOUT:
            printf("Timeout\n");
            break;
    }

    CloseHandle(hEvent);
}

// 生产者-消费者示例
void ProducerConsumerExample()
{
    const int BUFFER_SIZE = 10;
    int buffer[BUFFER_SIZE];
    int count = 0;

    HANDLE hItems = CreateSemaphore(NULL, 0, BUFFER_SIZE, NULL);
    HANDLE hSpaces = CreateSemaphore(NULL, BUFFER_SIZE, BUFFER_SIZE, NULL);
    HANDLE hMutex = CreateMutex(NULL, FALSE, NULL);

    // 生产者
    void Producer()
    {
        for (int i = 0; i < 20; i++)
        {
            WaitForSingleObject(hSpaces, INFINITE);  // 等待空闲空间
            WaitForSingleObject(hMutex, INFINITE);

            buffer[count % BUFFER_SIZE] = i;
            count++;

            printf("Produced: %d\n", i);

            ReleaseMutex(hMutex);
            ReleaseSemaphore(hItems, 1, NULL);  // 增加可用项
        }
    }

    // 消费者
    void Consumer()
    {
        for (int i = 0; i < 20; i++)
        {
            WaitForSingleObject(hItems, INFINITE);  // 等待可用项
            WaitForSingleObject(hMutex, INFINITE);

            int item = buffer[(count - 1) % BUFFER_SIZE];
            count--;

            printf("Consumed: %d\n", item);

            ReleaseMutex(hMutex);
            ReleaseSemaphore(hSpaces, 1, NULL);  // 增加空闲空间
        }
    }

    // 创建生产者和消费者线程
    HANDLE hProducer = CreateThread(NULL, 0, [](LPVOID) -> DWORD {
        Producer(); return 0; }, NULL, 0, NULL);
    HANDLE hConsumer = CreateThread(NULL, 0, [](LPVOID) -> DWORD {
        Consumer(); return 0; }, NULL, 0, NULL);

    WaitForSingleObject(hProducer, INFINITE);
    WaitForSingleObject(hConsumer, INFINITE);

    CloseHandle(hProducer);
    CloseHandle(hConsumer);
    CloseHandle(hItems);
    CloseHandle(hSpaces);
    CloseHandle(hMutex);
}

6.4 设计哲学问答(10问为什么)

本节通过问答形式深入探讨进程间通信机制的设计决策。

Q1: 为什么Windows使用Section作为IPC的基础?

答: Section对象之所以成为Windows IPC的基础,源于其独特的优势:

  1. 零拷贝优势:通过内存映射,多个进程可以直接访问同一块物理内存,避免了数据在内核和用户空间之间的多次复制。

  2. 统一的内存管理:Section复用内存管理器的虚拟地址空间管理、页面换入/换出机制,无需为IPC单独实现一套内存管理。

  3. 灵活的后备存储:Section可以支持文件后备(持久化)和页面文件后备(临时共享),满足不同场景需求。

  4. 与其他IPC机制的统一:管道、邮件槽、共享内存等都可以基于Section实现,简化了系统设计。

Q2: 为什么Section映射需要指定进程句柄?

答: NtMapViewOfSection需要指定目标进程句柄,这体现了几个重要设计:

  1. 跨进程映射能力:允许一个进程为另一个进程创建映射,例如调试器为被调试进程映射共享内存。

  2. 明确的权限边界:目标进程必须存在且调用者需要有PROCESS_VM_OPERATION权限,确保安全。

  3. 地址空间隔离:每个进程有独立的虚拟地址空间,系统需要为目标进程分配和验证地址范围。

Q3: 为什么文件映射要区分写时复制(COW)?

答: COW机制解决了两个重要问题:

  1. 进程独立性:映射同一文件的不同进程可以独立修改数据,不会相互影响。这对于fork后的子进程尤为重要。

  2. 内存效率:初始映射时所有进程共享同一物理页面,只有在真正写入时才复制,节省了物理内存。

  3. 数据保护:可以创建只读映射防止意外修改,同时允许需要写入的进程获得私有副本。

Q4: 为什么Windows的命名对象使用"Global\"前缀?

答: "Global\"前缀是Windows会话隔离机制的一部分:

  1. 会话隔离:在Terminal Services或远程桌面环境中,不同用户会话需要隔离对象命名空间。使用"Global\"可以在会话间共享对象。

  2. 向后兼容:无前缀的名称在会话内共享,"Global\"在全局命名空间共享,提供了清晰的语义。

  3. 安全考虑:某些敏感对象使用"Global\"前缀可以跨会话访问,需要谨慎使用。

Q5: 为什么等待函数支持Alertable模式?

答: Alertable等待是Windows异步编程模型的核心:

  1. 异步I/O完成:当异步I/O操作完成时,系统通过APC通知等待线程。如果等待是Alertable的,线程可以被唤醒处理完成通知。

  2. 避免轮询:无需忙等待或定时器检查,线程可以阻塞直到有工作要做。

  3. 线程池集成:线程池依赖Alertable等待来处理工作项和定时器回调。

Q6: 为什么跨进程等待需要对象引用计数?

答: 对象引用计数确保了跨进程等待的安全性:

  1. 防止对象提前删除:如果对象在等待期间被一个进程删除,引用计数确保对象至少保留到最后一个等待者完成。

  2. 生命周期管理:等待期间持有引用,唤醒后释放,正确管理对象生命周期。

  3. 避免悬垂引用:确保线程唤醒时对象仍然有效。

Q7: 为什么WaitForMultipleObjects有数量限制?

答: 64个对象的限制源于多个因素:

  1. 内核栈空间:每个等待块在栈上分配,太多等待块会消耗大量栈空间。

  2. 性能考虑:遍历大量对象检查信号状态会产生性能开销。

  3. 实际需求:大多数应用等待不超过几个对象,超过限制通常意味着设计问题。

  4. 实现简化:固定的数组大小简化了内核实现。

Q8: 为什么Section需要分配粒度对齐?

答: Section映射地址需要按分配粒度对齐:

  1. 硬件限制:CPU的内存管理单元(MMU)以页为单位管理内存,地址必须页对齐。

  2. 性能优化:对齐的地址可以提高TLB命中率,减少地址转换开销。

  3. 跨平台一致性:不同的CPU架构可能有不同的对齐要求,Windows定义了统一的最小粒度(64KB)。

Q9: 为什么匿名Section使用页面文件而非物理内存?

答: 页面文件支持的匿名Section提供了关键能力:

  1. 弹性容量:页面文件可以大于物理内存,允许创建大于物理内存的Section。

  2. 内存覆写:如果物理内存紧张,页面可以被换出到页面文件,之后再换入。

  3. 进程退出清理:进程终止时,Section页面自然被回收,无需额外清理逻辑。

  4. 临时共享:用于临时共享数据的Section不需要持久化,页面文件是最合适的后备。

Q10: 为什么Windows提供多种IPC机制?

答: 多样化的IPC机制针对不同场景优化:

机制 特点 适用场景
Section/共享内存 零拷贝、大数据 高性能共享、进程池
管道 字节流、有序 命令传递、进程链
邮件槽 广播、简单 通知广播
套接字 跨主机、网络 网络通信
消息队列 结构化消息 任务分发

这种设计让开发者可以根据性能、功能、复杂度需求选择最合适的机制,而不是用单一机制应对所有场景。


总结

核心要点回顾

  1. 进程间通信的需求:进程隔离带来的通信挑战,需要内核支持

  2. Section对象的地位:最基础、最高效的IPC机制,是其他IPC的底层实现

  3. Section内存映射机制

    • NtCreateSection创建Section对象
    • NtMapViewOfSection映射到进程地址空间
    • 跨进程共享通过命名Section实现
  4. 等待机制在IPC中的作用:同步对共享数据的访问,协调进程间操作

  5. 跨进程等待实现:命名对象、句柄转换、对象引用计数

  6. Alertable等待:允许APC打断等待,支持异步I/O完成通知

本章代码索引

文件 主要函数 说明
ntoskrnl/mm/section.c(file:///d:/reactos/ntoskrnl/mm/section.c) MmCreateSection, MmMapViewOfSection Section对象创建和映射
ntoskrnl/mm/ARM3/section.c(file:///d:/reactos/ntoskrnl/mm/ARM3/section.c) Section实现 ARM3内存管理器Section实现
ntoskrnl/ob/obwait.c(file:///d:/reactos/ntoskrnl/ob/obwait.c) NtWaitForSingleObject, NtWaitForMultipleObjects 跨进程等待系统调用
ntoskrnl/ke/wait.c(file:///d:/reactos/ntoskrnl/ke/wait.c) KeWaitForSingleObject, KeWaitForMultipleObjects 内核等待函数
sdk/include/xdk/iotypes.h(file:///d:/reactos/sdk/include/xdk/iotypes.h) SECTION_OBJECT_POINTERS Section指针结构定义
dll/win32/kernel32/client/proc.c(file:///d:/reactos/dll/win32/kernel32/client/proc.c) CreateFileMapping, MapViewOfFile, OpenFileMapping 用户态文件映射API

本节完。下一节将讨论Windows的进程调度与优先级机制。

相关推荐
私人珍藏库1 小时前
【PC】ActivePresenter(屏幕录制软件) Pro v10.5.1 多语便携版
windows·pc·工具·软件·多功能
你住过的屋檐2 小时前
【claude code】claude code在windows下安装使用教程,以及在idea中使用claude code自动AI写代码
人工智能·windows·intellij-idea
caimouse2 小时前
Reactos 第 5 章 进程与线程 — 5.13 Windows的跨进程操作
windows
shen121382 小时前
【cdp】windows持久化运行cdp浏览器
windows·agent·cdp
W优化大师3 小时前
Windows电脑频繁弹广告怎么彻底清除?从定位来源到卸载残留的完整方法
windows·电脑
拼搏的小浣熊3 小时前
【通用教程】Windows\+Linux\+银河麒麟系统 固定静态IP地址|解决打印机扫描IP变动、网络掉线问题
linux·网络·windows·麒麟·固定ip·麒麟系统·统信系统
w3296362713 小时前
使用 OpenCode 在 Windows 上加速安装 Playwright 的完整指南
windows·git
dabidai3 小时前
Docker PostgreSQL Windows 权限问题总结
windows·docker·postgresql
l齐天3 小时前
Ubuntu 中编译 Go + PBC 程序为 Windows 11 可运行文件
windows·ubuntu·golang