第 5 章 进程与线程 --- 5.13 Windows的跨进程操作
本节深入剖析 Windows/ReactOS 中跨进程操作(Inter-Process Communication, IPC)机制的完整实现。
概述
跨进程操作是现代操作系统中实现进程间协作的核心机制。由于 Windows 采用严格的进程隔离模型,每个进程拥有独立的地址空间,进程间无法直接访问彼此的内存。为了实现进程间的数据交换和协作,Windows 提供了多种 IPC 机制。
跨进程操作的本质是什么?
跨进程操作是一种打破进程隔离的受控通道,在保证安全性的前提下,允许进程间进行数据交换、资源共享和同步协调。IPC 机制的设计需要在安全性、性能和易用性之间取得平衡。
想象一个工厂场景:
- 跨进程内存操作:工人(进程)需要查看其他车间(进程)的库存清单,需要特殊权限才能进入;
- 共享内存:多个车间共用一个仓库,可以同时存取货物;
- 管道通信:车间之间通过管道传输原材料,单向或双向流动;
- 邮槽:广播通知系统,向所有车间发送消息;
- LPC:车间之间的电话系统,可以进行复杂的对话和请求-响应交互;
- 句柄复制:将一个车间的工具(句柄)借给另一个车间使用。
本节内容概览
- 5.13.0 框架图:Windows跨进程操作完整架构图;
- 5.13.1 概述:跨进程操作的定义、IPC机制分类;
- 5.13.2 跨进程内存操作:VirtualAllocEx、ReadProcessMemory/WriteProcessMemory;
- 5.13.3 共享内存机制:CreateFileMapping、MapViewOfFile;
- 5.13.4 管道通信:匿名管道和命名管道;
- 5.13.5 邮槽机制:单向广播通信;
- 5.13.6 LPC机制:本地过程调用;
- 5.13.7 句柄复制:DuplicateHandle;
- 5.13.8 IPC机制对比与选择:性能和适用场景分析;
- 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 操作系统采用严格的进程隔离模型:
- 地址空间隔离:每个进程拥有独立的 4GB(32位)虚拟地址空间;
- 资源隔离:进程的句柄、线程、内存等资源默认不共享;
- 权限隔离:进程只能访问自己拥有的资源。
这种隔离带来了安全性,但也产生了协作需求:
- 数据交换:进程间需要传递数据;
- 资源共享:多个进程需要访问同一资源;
- 同步协调:进程间需要协调执行顺序;
- 服务请求:客户端进程需要请求服务端进程的服务。
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:进程隔离是现代操作系统安全的基础:
- 安全性:防止恶意进程破坏其他进程;
- 稳定性:进程崩溃不会影响其他进程;
- 隐私性:进程数据不会被其他进程窃取;
- 资源管理:每个进程的资源可独立管理。
IPC 机制是在受控条件下打破隔离的安全通道。
Q3:为什么共享内存需要额外的同步机制?
A:共享内存本身只提供数据共享能力,不提供同步:
- 多进程同时写入可能导致数据损坏;
- 读进程可能读到部分更新的数据;
- 需要配合互斥体、信号量或临界区来保证一致性。
这是性能与复杂性的权衡:共享内存提供最高性能,但使用者需要负责同步。
Q4:为什么LPC只在本地使用?
A:LPC 设计为本地高性能 IPC:
- LPC 消息直接在内核中传递,不经过文件系统;
- LPC 使用共享内存池分配消息,减少复制开销;
- LPC 连接建立后,后续通信非常高效。
网络通信需要额外的协议栈开销,LPC 的设计目标就是避免这些开销。对于网络通信,命名管道或 RPC 更合适。
Q5:为什么邮槽消息大小有限制?
A:邮槽设计为广播机制,限制消息大小是为了:
- 网络效率:小消息可以快速广播到多台机器;
- 可靠性:大消息广播可能导致网络拥塞;
- 历史原因:早期网络带宽有限。
400字节限制是历史遗留,现代系统可以放宽,但建议保持小消息以维持广播特性。
Q6:为什么ReadProcessMemory需要特殊权限?
A:跨进程内存访问是高风险操作:
- 可能读取敏感数据(密码、密钥);
- 可能破坏进程稳定性;
- 可能侵犯隐私。
PROCESS_VM_READ 权限通常只授予调试器和系统工具,普通应用不应使用。
Q7:为什么句柄复制需要PROCESS_DUP_HANDLE权限?
A:句柄复制涉及敏感操作:
- 复制句柄可能授予不应有的访问权限;
- 可能泄露敏感资源(文件、进程);
- 可能被恶意软件利用。
PROCESS_DUP_HANDLE 权限需要显式请求,防止意外或恶意复制。
Q8:为什么匿名管道只能用于父子进程?
A:匿名管道没有名称,无法被其他进程打开:
- 管道句柄通过继承传递给子进程;
- 父进程创建管道后,将句柄传递给子进程;
- 其他进程无法找到或访问匿名管道。
命名管道通过名称解决这个问题,任何进程都可以通过名称连接。
Q9:为什么LPC使用消息队列而不是直接内存访问?
A:LPC 的消息队列设计有以下优势:
- 结构化通信:消息有明确的边界和类型;
- 请求-响应:支持同步的请求-响应模式;
- 安全验证:每条消息可以验证发送者身份;
- 顺序保证:消息按顺序处理。
直接内存访问虽然更快,但缺乏这些结构化特性。
Q10:为什么Windows子系统使用LPC而不是其他IPC?
A:Windows 子系统(csrss.exe)使用 LPC 的原因:
- 性能需求:Win32 API 调用频繁,需要高性能;
- 请求-响应模式:API 调用是典型的请求-响应;
- 安全验证:需要验证调用者身份和权限;
- 内核集成:LPC 直接在内核中处理,效率最高。
其他 IPC 机制无法同时满足这些需求。
总结
跨进程操作是 Windows 操作系统中实现进程间协作的核心机制。本章介绍了:
- IPC 机制分类:直接内存访问、共享内存、消息传递、句柄传递;
- 跨进程内存操作:VirtualAllocEx、ReadProcessMemory、MmCopyVirtualMemory;
- 共享内存机制:Section对象、CreateFileMapping、MapViewOfFile;
- 管道通信:匿名管道、命名管道、字节流和消息模式;
- 邮槽机制:单向广播通信、CreateMailslot;
- LPC机制:NtCreatePort、NtConnectPort、PORT_MESSAGE;
- 句柄复制:DuplicateHandle、权限要求;
- IPC选择指南:性能对比、适用场景分析。
核心要点回顾:
- 进程隔离是安全基础,IPC 是受控的打破隔离的通道;
- 共享内存性能最高,但需要额外同步机制;
- LPC 是本地高性能 IPC,适合请求-响应模式;
- 管道适合结构化通信,邮槽适合广播通知;
- 跨进程内存操作需要特殊权限,应谨慎使用。
本章代码索引
| 文件 | 内容 |
|---|---|
| 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 |