第 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) │ │
│ │ • 虚拟地址空间管理 • 物理页面分配 • 页面换入/换出 │ │
│ └────────────────────────────────────────────────────────────────────┘ │
│ │
└──────────────────────────────────────────────────────────────────────────────┘
本章内容概览
- 6.1 概述:IPC需求、机制分类、Section地位
- 6.2 共享内存区(Section):Section对象结构、创建、映射机制
- 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 进程间通信的基本概念
进程间通信涉及两个核心问题:
- 如何标识通信双方:进程通过句柄引用内核对象,但句柄是进程私有的
- 如何传输数据:进程地址空间隔离,需要内核协助
内核对象的进程间共享:
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可以:
- 映射文件内容:将文件的部分或全部内容映射到内存
- 创建匿名内存区域:分配一块可共享的物理内存(页面文件支持)
- 跨进程共享:多个进程可以映射同一个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中不可或缺的组成部分,用于:
-
同步访问共享数据:确保多个进程对共享Section的安全访问
-
事件通知:一个进程通知其他进程数据已准备好
-
资源协调:管理对有限资源的竞争访问
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的等待机制允许线程阻塞自己,直到指定的条件满足。在进程间通信中,等待机制用于:
-
同步对共享内存的访问:配合互斥体或信号量
-
等待数据准备好通知:生产者-消费者模式
-
协调进程间操作顺序:确保操作按正确顺序执行
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;
}
超时内部处理:
- 每个等待线程关联一个定时器
- 设置等待时,插入定时器到系统定时器队列
- 超时触发时,从等待列表移除线程
- 线程变为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等待的应用场景:
- 异步I/O完成通知:读取操作完成后触发APC
- 定时器回调:定时器到期时触发APC
- 线程池工作项:在线程池等待时处理工作项
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的基础,源于其独特的优势:
-
零拷贝优势:通过内存映射,多个进程可以直接访问同一块物理内存,避免了数据在内核和用户空间之间的多次复制。
-
统一的内存管理:Section复用内存管理器的虚拟地址空间管理、页面换入/换出机制,无需为IPC单独实现一套内存管理。
-
灵活的后备存储:Section可以支持文件后备(持久化)和页面文件后备(临时共享),满足不同场景需求。
-
与其他IPC机制的统一:管道、邮件槽、共享内存等都可以基于Section实现,简化了系统设计。
Q2: 为什么Section映射需要指定进程句柄?
答: NtMapViewOfSection需要指定目标进程句柄,这体现了几个重要设计:
-
跨进程映射能力:允许一个进程为另一个进程创建映射,例如调试器为被调试进程映射共享内存。
-
明确的权限边界:目标进程必须存在且调用者需要有PROCESS_VM_OPERATION权限,确保安全。
-
地址空间隔离:每个进程有独立的虚拟地址空间,系统需要为目标进程分配和验证地址范围。
Q3: 为什么文件映射要区分写时复制(COW)?
答: COW机制解决了两个重要问题:
-
进程独立性:映射同一文件的不同进程可以独立修改数据,不会相互影响。这对于fork后的子进程尤为重要。
-
内存效率:初始映射时所有进程共享同一物理页面,只有在真正写入时才复制,节省了物理内存。
-
数据保护:可以创建只读映射防止意外修改,同时允许需要写入的进程获得私有副本。
Q4: 为什么Windows的命名对象使用"Global\"前缀?
答: "Global\"前缀是Windows会话隔离机制的一部分:
-
会话隔离:在Terminal Services或远程桌面环境中,不同用户会话需要隔离对象命名空间。使用"Global\"可以在会话间共享对象。
-
向后兼容:无前缀的名称在会话内共享,"Global\"在全局命名空间共享,提供了清晰的语义。
-
安全考虑:某些敏感对象使用"Global\"前缀可以跨会话访问,需要谨慎使用。
Q5: 为什么等待函数支持Alertable模式?
答: Alertable等待是Windows异步编程模型的核心:
-
异步I/O完成:当异步I/O操作完成时,系统通过APC通知等待线程。如果等待是Alertable的,线程可以被唤醒处理完成通知。
-
避免轮询:无需忙等待或定时器检查,线程可以阻塞直到有工作要做。
-
线程池集成:线程池依赖Alertable等待来处理工作项和定时器回调。
Q6: 为什么跨进程等待需要对象引用计数?
答: 对象引用计数确保了跨进程等待的安全性:
-
防止对象提前删除:如果对象在等待期间被一个进程删除,引用计数确保对象至少保留到最后一个等待者完成。
-
生命周期管理:等待期间持有引用,唤醒后释放,正确管理对象生命周期。
-
避免悬垂引用:确保线程唤醒时对象仍然有效。
Q7: 为什么WaitForMultipleObjects有数量限制?
答: 64个对象的限制源于多个因素:
-
内核栈空间:每个等待块在栈上分配,太多等待块会消耗大量栈空间。
-
性能考虑:遍历大量对象检查信号状态会产生性能开销。
-
实际需求:大多数应用等待不超过几个对象,超过限制通常意味着设计问题。
-
实现简化:固定的数组大小简化了内核实现。
Q8: 为什么Section需要分配粒度对齐?
答: Section映射地址需要按分配粒度对齐:
-
硬件限制:CPU的内存管理单元(MMU)以页为单位管理内存,地址必须页对齐。
-
性能优化:对齐的地址可以提高TLB命中率,减少地址转换开销。
-
跨平台一致性:不同的CPU架构可能有不同的对齐要求,Windows定义了统一的最小粒度(64KB)。
Q9: 为什么匿名Section使用页面文件而非物理内存?
答: 页面文件支持的匿名Section提供了关键能力:
-
弹性容量:页面文件可以大于物理内存,允许创建大于物理内存的Section。
-
内存覆写:如果物理内存紧张,页面可以被换出到页面文件,之后再换入。
-
进程退出清理:进程终止时,Section页面自然被回收,无需额外清理逻辑。
-
临时共享:用于临时共享数据的Section不需要持久化,页面文件是最合适的后备。
Q10: 为什么Windows提供多种IPC机制?
答: 多样化的IPC机制针对不同场景优化:
| 机制 | 特点 | 适用场景 |
|---|---|---|
| Section/共享内存 | 零拷贝、大数据 | 高性能共享、进程池 |
| 管道 | 字节流、有序 | 命令传递、进程链 |
| 邮件槽 | 广播、简单 | 通知广播 |
| 套接字 | 跨主机、网络 | 网络通信 |
| 消息队列 | 结构化消息 | 任务分发 |
这种设计让开发者可以根据性能、功能、复杂度需求选择最合适的机制,而不是用单一机制应对所有场景。
总结
核心要点回顾
-
进程间通信的需求:进程隔离带来的通信挑战,需要内核支持
-
Section对象的地位:最基础、最高效的IPC机制,是其他IPC的底层实现
-
Section内存映射机制:
- NtCreateSection创建Section对象
- NtMapViewOfSection映射到进程地址空间
- 跨进程共享通过命名Section实现
-
等待机制在IPC中的作用:同步对共享数据的访问,协调进程间操作
-
跨进程等待实现:命名对象、句柄转换、对象引用计数
-
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的进程调度与优先级机制。