Linux 背景工程师维护 Windows 服务、或设计跨平台中间件时,常把 NT 内核当成”另一个 Linux”——结果在 I/O 模型、进程句柄、配置存储上踩坑。本文从 Russinovich、Solomon、Ionescu 所著 Windows Internals(第 7 版)与 Microsoft 官方文档出发,对照本站 Linux 内核系列 已有篇章,说明 NT 与 Linux 在架构、对象、I/O、进程、配置五条线上的关键分叉。
读完本文,你应能回答三个工程问题:
- 为什么 Windows 高性能网络服务普遍用 IOCP,而 Linux 传统路径是 epoll?
- 为什么 WSL2 选择跑真 Linux 内核的轻量 VM,而不是延续 WSL1 的 syscall 翻译?
- 跨平台中间件抽象 I/O 事件循环时,哪些语义不能照搬?
一、NT 分层与 Linux 子系统对照
1.1 三层内核 + 子系统
Windows NT 内核不是”一个平铺的 kernel/
目录”,而是明确分为三层,再向上承接环境子系统(Environment
Subsystem):
flowchart TD
subgraph user [用户态]
APP[Win32 / .NET 应用]
DLL[子系统 DLL<br/>kernel32 / ntdll]
end
subgraph nt [Windows NT 内核态]
EXEC[Executive<br/>对象管理/内存/IO/进程/安全]
KRNL[Kernel<br/>调度/中断/DPC/同步原语]
HAL[HAL<br/>APIC/时钟/DMA/总线]
end
HW[硬件]
APP --> DLL
DLL -->|Nt*/Zw* 系统服务| EXEC
EXEC --> KRNL --> HAL --> HW
classDef exec fill:#388bfd22,stroke:#388bfd,color:#adbac7;
classDef kern fill:#3fb95022,stroke:#3fb950,color:#adbac7;
classDef hal fill:#f0883e22,stroke:#f0883e,color:#adbac7;
class EXEC exec
class KRNL kern
class HAL hal
| NT 层 | 核心组件 | 职责摘要 | Linux 近似路径 |
|---|---|---|---|
| Executive | Object Manager、Memory Manager、Process Manager、I/O Manager、Cache Manager、Security Reference Monitor | 策略与对象语义:一切皆对象、IRP 分发、虚拟内存策略、访问控制 | mm/、fs/、kernel/(进程部分)、security/、drivers/base/ |
| Kernel | 调度器、DPC、定时器、IRQL 管理、内核同步 | 机制层:在 DISPATCH_LEVEL 及以下调度线程 | kernel/sched/、kernel/irq/、kernel/time/ |
| HAL | 中断控制器、时钟源、DMA、固件接口 | 屏蔽主板/芯片组差异 | arch/x86/、drivers/acpi/、drivers/clocksource/ |
Windows Internals, Part 1, Ch. 2 将 Executive 描述为”向子系统提供策略服务”的层;Kernel 提供”不依赖 Executive 策略”的底层机制;HAL 则把硬件差异收敛到少量接口。
Linux
是宏内核:sched、mm、fs、net、drivers
同处内核态,没有 NT 那样严格的”Executive 服务”边界。对照 宏内核
vs 微内核 vs 混合内核:NT
常被归类为混合内核——Executive
子系统像一组紧耦合的内核服务,驱动通过 I/O Manager 的 IRP
模型接入,而不是像 Minix 那样以用户态服务器 +
消息传递为主。
1.2 Executive 子系统地图
Executive 不是单一模块,而是一组互相依赖的内核组件:
| Executive 组件 | 管理对象/资源 | 与 Linux 对照 |
|---|---|---|
| Object Manager | 内核对象、handle、命名空间 | fd 表 + VFS inode/dentry(部分语义) |
| Memory Manager | 虚拟地址、工作集、段对象 | mm_struct、VMA、do_mmap |
| Process Manager | EPROCESS、ETHREAD、Job |
task_struct、cgroups v2 |
| I/O Manager | IRP、设备栈、完成端口 | VFS + block layer + VFS I/O 路径 |
| Cache Manager | 文件数据缓存、懒写 | page cache、页缓存深入 |
| Security Reference Monitor | token、ACL、完整性级别 | uid/gid、capabilities、SELinux |
| Plug and Play Manager | 设备枚举、驱动加载 | driver core、udev
用户态协作 |
| Power Manager | 休眠、设备电源状态 | drivers/base/power/、电源管理 |
Linux 工程师第一次读 Windows 文档时,常见误区是把 “Executive” 翻译成”执行体”后不知对应哪段代码——实际上它大致等于”除纯调度/中断外的内核策略层”。
1.3 Kernel 层:IRQL 与 Linux 抢占
NT Kernel 层用 IRQL(Interrupt Request Level) 约束可抢占性与锁使用:
| IRQL | 含义 | 典型活动 |
|---|---|---|
| PASSIVE_LEVEL | 普通线程上下文 | 系统服务、大部分 Executive 代码 |
| APC_LEVEL | 异步过程调用 | 用户 APC、部分 I/O 完成 |
| DISPATCH_LEVEL | 调度器、DPC | 线程调度、延迟过程调用 |
| DIRQL | 设备中断级别 | 设备 ISR 上半部 |
在 DISPATCH_LEVEL
及以上不能发生线程切换,也不能访问分页内存。Linux
用
preempt_disable()、spin_lock_irqsave()、in_interrupt()
等组合表达类似约束,但没有统一的数字等级刻度。
对照 软中断与
tasklet 与 线程化
IRQ:Linux 把大量中断下半部推到 ksoftirqd
或 thread_irq;NT 用 DPC(Deferred
Procedure Call) 在 DISPATCH_LEVEL
执行,语义接近”必须在锁内快速完成或再排队”的下半部。
1.4 HAL:为什么 Windows 需要这一层
HAL 存在的直接原因是 NT 要支持多种 CPU 架构与 OEM 硬件组合(历史上 x86、Alpha、MIPS、ARM64 等)。它提供:
- 中断控制器抽象(APIC/IOAPIC)
- 高精度时钟与性能计数器
- DMA 适配器对象
- 固件表解析入口
Linux 在 arch/ 下为每种架构维护代码,在
drivers/ 下堆叠设备驱动——没有命名为 HAL
的单层,但功能等价物分散在 arch 与 platform
代码中。
对应用开发者,HAL 几乎不可见;对驱动开发者,通过 WDM/KMDF 接口间接触达。
1.5 系统服务入口:ntdll 与 syscall
Win32 应用大多调用 kernel32.dll,最终经
ntdll.dll 的 Nt* /
Zw* 例程进入内核。这与 Linux 的
libc → syscall 指令路径类似。对照
系统调用
ABI:
| 方面 | Windows NT | Linux x86_64 |
|---|---|---|
| 入口 DLL | ntdll.dll |
libc.so |
| 系统服务号 | 服务号表(因版本变化) | syscall + __NR_* |
| 稳定接口 | Win32 API(用户态) | POSIX + glibc 扩展 |
| 内核文档 | WDK / Windows Internals | man2、Documentation/ |
跨平台中间件若直接依赖 NtReadFile 等 NT 原生
API,可移植性低于 Win32/POSIX 抽象层。
二、Object Manager:一切皆对象
2.1 设计动机
NT 把进程、线程、文件、事件、互斥体、信号量、节(section)、注册表键、定时器、Job 等统一为内核对象(Kernel Object),由 Object Manager 负责:
- 分配与回收对象体
- 维护引用计数
- 解析对象路径命名空间
- 在打开/句柄操作时执行访问检查
Windows Internals, Ch. 7 强调:这种统一性让安全子系统可以对任意对象类型套用同一套 ACL 语义,也让句柄表成为进程资源视图的中心。
Linux 没有等价的”全局对象管理器”。文件用 fd +
struct file;进程用
task_struct;同步原语分散在 futex、per-CPU
队列、eventfd
等机制中——Unix 哲学是”一切皆文件”,但进程/线程并不是
fd。
2.2 OBJECT_HEADER 与对象体
每个内核对象在对象体前都有 OBJECT_HEADER(布局因版本略有差异,以下为概念结构):
// 概念结构 — 详见 Windows Internals / WDK
typedef struct _OBJECT_HEADER {
LONG PointerCount; // 引用计数
LONG HandleCount; // 句柄计数
UCHAR TypeIndex; // 对象类型
UCHAR Flags;
// ...
struct _OBJECT_TYPE *Type;
// SecurityDescriptor 指针等
} OBJECT_HEADER;对象类型由 OBJECT_TYPE
描述,包含类型名、回调例程(关闭、删除、安全方法等)。例如
File 类型对应文件对象,Process
类型对应 EPROCESS。
Linux 对照:struct file 有
f_count 引用计数,但没有跨越
file/process/event 的统一
header;kref/refcount_t
在各子系统内局部使用。
2.3 句柄表 vs 文件描述符
用户态看到的 HANDLE
是进程句柄表中的索引,不是内核指针:
CreateFile() → ObOpenObjectByName → 分配 HANDLE → 指向 FILE_OBJECT
ReadFile(HANDLE) → 句柄表解析 → FILE_OBJECT → I/O Manager 创建 IRP
| 方面 | Windows HANDLE | Linux fd |
|---|---|---|
| 类型 | 索引(通常 4 的倍数,低 2 位标志) | 小整数 0,1,2… |
| 可表示资源 | 文件、进程、线程、事件、注册表键… | 主要是文件式对象;pidfd 等是后补 |
| 继承 | bInheritHandle、句柄复制 |
FD_CLOEXEC、fork 继承规则 |
| 跨进程共享 | DuplicateHandle |
sendmsg
SCM_RIGHTS、pidfd_getfd |
| 内核结构 | 句柄表项 → 对象指针 + 访问掩码 | fd 表 → struct file |
Linux 用 fd 统一大量 I/O
与事件源;Windows 用 HANDLE
统一更广的内核对象集合。设计跨平台抽象时,int fd
与 HANDLE
的生命周期与继承语义必须分开建模。
2.4 对象命名空间
命名对象可通过路径访问,例如:
\BaseNamedObjects\MyAppEvent— 会话级命名事件\Device\HarddiskVolume2\...— 设备命名空间\Registry\Machine\Software\...— 注册表(也是对象)
Object Manager
维护目录对象构成的树。CreateMutex(NULL, FALSE, L"Global\\MyMutex")
在命名空间中创建或打开互斥体。
Linux
用文件系统路径命名对象:/dev/shm/、/run/、抽象
Unix domain socket 路径。没有单一的 \
根命名空间;namespace
按维度(mount、pid、net)切分视图,而非 NT
式的对象目录树。
flowchart LR
subgraph ntns [NT 对象命名空间]
ROOT[\]
ROOT --> BASE[\BaseNamedObjects]
ROOT --> DEV[\Device]
ROOT --> REG[\Registry]
BASE --> EVT[Event/Mutex]
DEV --> VOL[Volume]
end
subgraph linuxns [Linux 命名惯例]
SLASH[/]
SLASH --> DEV2[/dev]
SLASH --> RUN[/run]
SLASH --> TMP[/tmp]
DEV2 --> SHM[shm, pts]
end
2.5 安全描述符与打开检查
每个对象可有 SECURITY_DESCRIPTOR:所有者
SID、DACL(自主访问控制列表)、SACL(审计)。打开对象时
SeAccessCheck 根据调用线程的 access
token 与请求的 GENERIC_READ
等权限做决策。
Linux 默认 DAC(owner/group/other rwx)+ 可选 MAC(SELinux/AppArmor)+ capabilities。NT 的 token 把用户 SID、组 SID、特权(privilege)打包,比单纯 uid/gid 更细,也更复杂。
Mandatory Integrity Control(MIC) 给对象和 token 标注完整性级别(Low/Medium/High/System),与 SELinux 的 type/enforce 不同,但都属于强制策略层。
三、I/O 模型:IRP 与驱动栈
3.1 IRP 是什么
I/O Request Packet(IRP) 是 I/O Manager
在驱动栈之间传递的请求包。一次 ReadFile
大致路径:
- I/O Manager 为线程创建
IRP,
MajorFunction = IRP_MJ_READ - IRP 下发到文件系统驱动(如 NTFS)
- 可能经过卷管理器、磁盘类驱动、端口/miniport 驱动
- 完成时调用 completion routine,最终线程从
ReadFile返回或收到 IOCP 完成包
sequenceDiagram
participant App as 应用程序
participant IO as I/O Manager
participant FS as 文件系统驱动
participant Vol as 卷管理器
participant Disk as 磁盘驱动
participant HW as 硬件
App->>IO: ReadFile(HANDLE, buf, len)
IO->>IO: IoAllocateIrp / 初始化 IRP
IO->>FS: 驱动栈顶部分发 IRP
FS->>Vol: 转发(可能 STATUS_PENDING)
Vol->>Disk: 转发
Disk->>HW: 编程 DMA / 端口
HW-->>Disk: 完成中断
Disk-->>IO: IoCompleteRequest
IO-->>App: 同步返回或 IOCP 完成
对照 VFS I/O
路径:Linux read() 走 vfs_read
→ 具体文件系统 → page cache → block layer →
驱动;没有与 IRP
一一对应的单一内核数据结构贯穿全栈,但 request queue / bio
承担类似”请求载体”角色。
3.2 设备栈与 Filter Driver
驱动以设备对象(DEVICE_OBJECT) 栈形式叠加:
[Filter] → [Filter] → [文件系统] → [卷] → [磁盘] → [总线]
Filter Driver 可附加在栈的任意层:
- 杀毒软件 minifilter(文件扫描)
- 加密筛选驱动(EFS、BitLocker 层)
- 存储重定向、重复数据删除
Linux 有 fs/stack、block layer 的
blk-mq 插入点、FUSE
用户态文件系统,但 NT 的 Filter
Manager(FltMgr) 框架提供标准化的 pre/post
回调、上下文挂载与实例管理——第三方筛选驱动生态更成熟。
Filter 可以:
- 完成 IRP 而不向下传递(短路径)
- 向下传递并在完成时拦截(pending + completion routine)
- 创建新的 IRP 向下层发送
3.3 IRP MajorFunction 与 Linux 对照
| IRP MajorFunction | 含义 | Linux 近似 |
|---|---|---|
| IRP_MJ_CREATE | 打开/创建 | open → vfs_open |
| IRP_MJ_READ / WRITE | 读写 | vfs_read / vfs_write |
| IRP_MJ_DEVICE_CONTROL | IOCTL | ioctl |
| IRP_MJ_PNP | 即插即用 | uevent + driver probe |
| IRP_MJ_POWER | 电源 | dev_pm_ops |
DeviceIoControl 对应
ioctl;Winsock 的 WSAIoctl 等网络
IOCTL 最终也走 I/O 栈或辅助库路径。
3.4 同步、异步与 STATUS_PENDING
驱动处理 IRP 时可返回:
STATUS_SUCCESS— 同步完成STATUS_PENDING— 异步处理,完成后调用IoCompleteRequest- 错误 NTSTATUS 码
用户态重叠 I/O 使用 OVERLAPPED
结构;完成可通过事件句柄或 IOCP 投递。
Linux 传统 read/write
以同步阻塞为主;异步路径包括 io_uring、libaio、epoll
+ 非阻塞 fd 的组合。
3.5 Cache Manager 与内存映射 I/O
NT Cache Manager 与 Memory Manager、I/O Manager 协作,把文件数据缓存在系统工作集中,支持:
- 懒写(lazy write)
- 内存映射文件(
CreateFileMapping/MapViewOfFile) - 预读策略
Linux page cache 与 mmap
提供等价能力;对照 页缓存。差异在策略细节:NT
面向 GUI 与服务器混合负载长期优化;Linux 在 direct
I/O、O_DIRECT、fadvise
上给数据库等场景更多显式控制。
四、完成通知:IOCP、epoll、io_uring
4.1 就绪 vs 完成
高性能 I/O 多路复用的核心分叉是通知语义:
| 模型 | 代表 API | 通知含义 | 典型后续动作 |
|---|---|---|---|
| 就绪(Readiness) | select/poll/epoll |
fd 可能可读/可写 | 用户态再调 read/write,可能 EAGAIN |
| 完成(Completion) | IOCP、io_uring CQE | I/O 已结束 | 直接使用缓冲区,无需再次 syscall 读 |
epoll
内部 中 EPOLLIN 表示 socket
接收队列有数据,不保证一次
read 读完。高并发下常见模式:epoll 唤醒 → 循环
read 直到 EAGAIN → 重新
epoll_ctl。
IOCP 的 GetQueuedCompletionStatus
返回时,关联的 OVERLAPPED I/O
已完成,缓冲区已有确定字节数(或错误状态)。
4.2 IOCP 机制
I/O Completion Port(IOCP) 创建与使用流程:
// 创建完成端口;ConcurrentThreads 通常设为 CPU 数
HANDLE iocp = CreateIoCompletionPort(
INVALID_HANDLE_VALUE, NULL, 0, concurrency);
// 将文件/socket 句柄关联到 IOCP;CompletionKey 用于区分连接
CreateIoCompletionPort(file_or_socket, iocp, completion_key, 0);
// 发起异步 I/O(需 FILE_FLAG_OVERLAPPED)
ReadFile(h, buf, len, NULL, &overlapped);
// 工作线程池等待完成包
ULONG bytes;
ULONG_PTR key;
OVERLAPPED *ovl;
GetQueuedCompletionStatus(iocp, &bytes, &key, &ovl, INFINITE);关键设计(Windows Internals + Microsoft Docs I/O Completion Ports):
- 单队列多生产者:多个设备完成时统一入队到 IOCP
- 并发度控制:
ConcurrentThreads限制同时唤醒的线程数,减轻惊群 - 与线程池集成:
BindIoCompletionCallback、SetThreadpoolWait等 API 可复用 IOCP 语义
Linux 直到 io_uring 才在主线提供类似的完成队列抽象;传统 epoll 需要应用自己实现线程池与读写状态机。
4.3 三种模型对照表
| 维度 | IOCP | epoll | io_uring |
|---|---|---|---|
| 通知类型 | 完成 | 就绪 | 完成(可混合 OP_POLL) |
| 系统调用频率 | 异步提交 + 完成 dequeue | epoll_wait + 读写 syscall |
批量提交 SQE、批量取 CQE |
| 线程模型 | 内核感知并发度 | 完全用户态管理 | SQPOLL 可选内核轮询 |
| 缓冲区管理 | 用户分配 OVERLAPPED 缓冲 |
用户管理 | 可注册固定缓冲(buffer ring) |
| 跨平台 | Windows | Linux/BSD | Linux 5.1+ |
| 与 sendfile 零拷贝 | 驱动/栈支持 | splice | IORING_OP_SEND 等 |
4.4 延迟与吞吐:引用数据,非本机 benchmark
本文不编造 QPS 倍数。公开资料中的共识(设计层,非特定硬件承诺):
- Microsoft Docs 说明 IOCP 面向高并发服务器,通过完成端口合并 I/O 完成通知,减少线程上下文切换。
- io_uring 论文(Axboe, 2019; Linux 文档)报告在部分 micro-benchmark 中相对 libaio/epoll 显著降低 syscall 开销;实际业务受 workload、块大小、是否 SQPOLL 影响。
- 就绪模型在 CPU 充足、连接数中等时常”足够好”;瓶颈往往在应用协议解析而非 epoll 本身——这与 fs notify benchmark 中”先定位瓶颈层”的方法论一致。
跨平台网关(如 .NET Kestrel、Nginx、Envoy)的正确抽象是区分 ReadinessLoop 与 CompletionLoop,而不是假设 API 名字可一一映射。
4.6 IOCP 并发度参数的工程含义
创建 IOCP 时的 NumberOfConcurrentThreads
参数告诉内核:同一完成端口上最多允许多少线程同时
runnable 处理完成包。设得过低会浪费
CPU;设得过高可能重现惊群后的锁竞争。
典型服务器进程池大小与 CPU 核数同阶,但 IO 密集 workload 可能用更多线程掩盖延迟——这与 Linux 上”epoll 线程数 vs worker 线程数”调参类似,都没有单一公式,需结合 profiling。
4.7 io_uring 作为 Linux 侧”完成阵营”参照
io_uring
深入 中,提交队列 SQE 可表达
READ、WRITE、ACCEPT
等操作,完成队列 CQE
返回结果——语义上接近”把操作提交给内核,完成后统一
dequeue”。与 IOCP 的差异包括:io_uring 可在同一
ring 混合多种操作;IOCP 仍依赖各
API(ReadFile、WSARecv)发起异步
I/O,完成统一到端口队列。
设计跨平台运行时,io_uring 与 IOCP 的相似度高于 epoll 与
IOCP——但 io_uring 在 Linux
版本、权限(IORING_SETUP_*)与可移植性上仍受限。
4.8 何时 epoll 仍然足够
连接数万级、协议解析占主导、内核 5.1 以下、或依赖成熟
epoll 库(libevent、nginx)时,epoll 仍是 Linux
默认答案。Windows 侧没有 epoll 等价物——Winsock 的
select 性能差,迫使高性能路径走向 IOCP。
4.9 设计启示
| 场景 | Windows 常见选型 | Linux 常见选型 |
|---|---|---|
| 高并发 TCP 服务器 | IOCP + 重叠 WSARecv | epoll ET + 非阻塞;或 io_uring |
| 数据库引擎 IO | IOCP / 扩展页缓存 | O_DIRECT + libaio/io_uring |
| 跨平台运行时 | 抽象 IOCP/IOCP-like | 抽象 epoll/kqueue/uring |
| 静态链接嵌入式服务 | 不适用 | 有时直接阻塞 IO |
中间件作者应把”事件循环类型”写进架构文档,避免 Windows 与 Linux 分支在代码审查时被当成”实现细节”而忽视语义差异。
五、进程与线程:EPROCESS 与 KTHREAD
5.1 进程与线程是独立对象
Windows
进程(EPROCESS)与线程(ETHREAD,调度实体为
KTHREAD)是独立内核对象:
EPROCESS:地址空间(EPROCESS 指向的页目录)、句柄表、安全 token、Job 关联、调试端口KTHREAD:内核栈、TEB 指针、调度状态、等待对象
创建进程:CreateProcess 分配新
EPROCESS
和初始线程;创建线程:CreateThread
在已有进程内新 ETHREAD。
Linux task_struct:线程即任务。clone()
创建新 task_struct;共享 mm_struct
时为线程,独立 mm 时为进程。pid 与
tgid 区分线程 ID 与进程组 ID。
flowchart TB
subgraph win [Windows]
EP[EPROCESS<br/>地址空间/句柄表/token]
ET1[ETHREAD 1]
ET2[ETHREAD 2]
EP --> ET1
EP --> ET2
end
subgraph lin [Linux]
TG[task_struct 主线程<br/>tgid = pid]
T1[task_struct 线程1]
T2[task_struct 线程2]
MM[mm_struct]
TG --- MM
T1 --- MM
T2 --- MM
end
5.2 字段级对照(概念)
| 概念 | Windows | Linux |
|---|---|---|
| 调度实体 | KTHREAD |
task_struct |
| 地址空间 | EPROCESS.VadRoot / 页表 |
mm_struct、VMA |
| 打开的文件 | 句柄表中的 FILE_OBJECT | files_struct → fd 表 |
| 安全上下文 | Access Token | cred(uid/gid/cap) |
| 父子关系 | EPROCESS 树 |
parent/children 链表 |
| 创建 API | CreateProcess |
fork + execve |
Linux 没有 Windows
式”进程句柄”的一等对象;OpenProcess 在 Linux
上需通过 ptrace、/proc/pid 权限或
pidfd 等组合模拟。
5.3 Job Object 与 cgroup
Job Object 将多个进程绑定到资源策略:
HANDLE job = CreateJobObject(NULL, NULL);
JOBOBJECT_EXTENDED_LIMIT_INFORMATION info = {0};
info.BasicLimitInformation.LimitFlags =
JOB_OBJECT_LIMIT_PROCESS_MEMORY | JOB_OBJECT_LIMIT_JOB_TIME;
SetInformationJobObject(job, JobObjectExtendedLimitInformation, &info, sizeof(info));
AssignProcessToJobObject(job, hProcess);限制可包括:进程内存、CPU 时间、UI 访问、子进程创建、亲和性等。
对照 cgroups v2:Linux 的 pids、memory、cpu、io、cpuset 控制器粒度更细;Job Object 与 容器安全 中的进程边界都可用于沙箱,但 cgroup 是树形层级,Job 是 Win32 句柄对象。
Windows 容器(基于 Job + 命名空间隔离 + silo)与 Linux namespace + cgroup 的组合在概念上平行,实现路径不同。
5.4 线程池与 Linux 对照
Windows 提供系统级线程池
API(QueueUserWorkItem、PTP 回调),与
IOCP 深度集成。Linux 常用 pthread +
应用自建池,或由运行时(如 .NET ThreadPool、Go
scheduler)管理。这不是内核能力差距,而是默认编程模型差异。
六、Registry 与 Linux 配置哲学
6.1 Registry 结构
Windows Registry 是层次化数据库,以 hive 文件持久化:
| Hive | 路径前缀 | 内容 |
|---|---|---|
| HKLM | HKEY_LOCAL_MACHINE |
机器级驱动、服务、软件 |
| HKCU | HKEY_CURRENT_USER |
当前用户设置 |
| HKU | HKEY_USERS |
多用户配置 |
| HKCR | HKEY_CLASSES_ROOT |
类型关联、COM |
键值类型包括
REG_DWORD、REG_SZ、REG_BINARY
等;权限通过 ACL 控制。
6.2 与 /etc、sysctl、/proc 对照
| 方面 | Windows Registry | Linux |
|---|---|---|
| 配置存储 | 二进制 hive + API | /etc 文本、.conf |
| 内核可调参数 | 部分在 Registry | sysctl / procfs / sysfs |
| 原子更新 | RegReplaceKey 等(有限) |
文件替换 + SIGHUP reload |
| 损坏影响 | 集中数据库损坏影响面大 | 单文件损坏相对隔离 |
| 可脚本化 | PowerShell Get-ItemProperty |
shell、sysctl -w |
Linux 哲学:文本配置、可
diff、可版本管理。Windows
哲学:类型化键值、统一
API——组策略、安装程序、服务依赖 Registry。运维
Windows 服务器时,reg query、.reg
导出与备份恢复是必备技能。
6.3 服务与驱动配置
Windows 内核驱动与服务参数常存于
HKLM\SYSTEM\CurrentControlSet\...。Linux
模块参数通过 modprobe.d、kernel cmdline、sysfs
module/parameters/ 暴露。
七、WSL1 与 WSL2:为何选虚拟机
7.1 WSL1:lxss 翻译层
WSL1 在 NT 上实现 lxss 子系统,将 Linux syscall 翻译为 NT 内核调用。未运行 Linux 内核。
后果(Microsoft 官方架构文档):
- 部分
ioctl、epoll边缘语义与真 Linux 不一致 - 无法加载 Linux 内核模块、eBPF、io_uring 的完整特性
- 同文件系统路径上,某些元数据操作语义不同
优势:纯文件路径跨 Windows/Linux 无 VM 边界,小文件 IO 有时更快。
7.2 WSL2:Utility VM + 真内核
flowchart TB
subgraph host [Windows 主机]
WIN[NT 内核 / Hyper-V]
FS9P[Plan 9 / virtio-fs 协议]
end
subgraph vm [轻量 Utility VM]
LK[Linux 内核]
DIST[发行版用户态]
end
WIN -->|Hyper-V 分区| vm
LK --> DIST
DIST <-->|\\wsl$\ 挂载| FS9P
WIN --- FS9P
微软文档说明:WSL2 在 utility VM 内运行完整 Linux 内核,通过虚拟网卡、virtio、9P 协议访问 Windows 文件系统。代价:跨 OS 文件 IO 经虚拟化层;收益:syscall 兼容性与真内核行为。
这与 FreeBSD 与 OpenBSD 对比 中”跑真内核”的论点一致:Linux 行为无法靠翻译层完美复刻。需要 Docker、eBPF、完整 PREEMPT_RT 实验时,应选 WSL2 而非 WSL1。
7.4 网络与 localhost 转发
WSL2 使用虚拟交换机;localhost 在 Windows 与
WSL2 之间默认不互通,需 localhostForwarding
或显式端口代理(Microsoft Docs WSL
networking)。这与 Linux network
namespace 下的 veth
配对类似——跨命名空间访问要单独配置。
7.5 文件系统性能边界
微软文档指出:跨 OS 边界访问文件(Windows 路径访问 Linux
内文件,或 \\wsl$\ 反向)性能低于同一 VM 内
ext4 本地路径。编译大型 C/C++ 工程、跑
git status 密集仓库时,应把源码放在 Linux
文件系统内——这是 WSL2 运维的常见实践,不是可选优化。
7.6 与 KVM 的平行
对照 KVM 架构:WSL2 的 utility VM 是专为开发场景优化的轻量分区,不是通用云 VM,但“主机内核 + 客户机内核”的分界与 KVM 相同。
八、安全模型速览
| 方面 | Windows | Linux |
|---|---|---|
| 主体 | Access Token(用户+组 SID+特权) | uid/gid + capabilities |
| 强制完整性 | MIC 级别 | SELinux/AppArmor label |
| 沙箱 | AppContainer、Job、受限 token | namespace + seccomp |
| 驱动签名 | 64 位生产环境强制 | 可选 lockdown、MOK |
| 漏洞缓解 | CFG、ACG、HVCI | KASLR/KPTI 等 |
九、工程案例
9.1 .NET / Kestrel
ASP.NET Core 在 Windows 上默认利用 IOCP 完成 socket
I/O;在 Linux 上使用 epoll(及正在推进的 io_uring
路径)。跨平台抽象 SocketAsyncEventArgs
背后是不同的内核通知语义——性能调优不能假设”异步”一词等价。
9.3 IIS 与 HTTP.sys
Windows HTTP.sys 内核驱动在 IOCP 之上提供 HTTP 解析与队列管理,IIS 可共享内核监听队列。Linux 上 Nginx/Envoy 在用户态完成同类工作,依赖 epoll/io_uring。两侧”把协议解析放哪一层”的架构选择不同,影响 CPU 占用与 TLS 终结部署。
9.4 案例边界
以上案例来自 Microsoft 与厂商公开架构描述(B 级),用来说明 I/O 路径差异,不构成性能排名。具体 QPS、延迟需在相同硬件与 workload 下实测,或引用第三方 benchmark 并标注来源。
十、内存管理:VAD 与 Linux VMA
10.1 虚拟地址描述符
Windows Memory Manager 用 VAD(Virtual Address Descriptor) 树描述进程用户态虚拟地址布局。每个 VAD 记录一段连续虚拟地址的范围、保护属性(读/写/执行)、映射类型(私有提交、映射文件、映像节)等。
VirtualAlloc
分配私有提交内存;CreateFileMapping +
MapViewOfFile 建立文件后备映射——语义上对应
Linux mmap/munmap 与 mm_struct /
VMA。
| 操作 | Windows API | Linux |
|---|---|---|
| 匿名映射 | VirtualAlloc |
mmap(MAP_ANONYMOUS) |
| 文件映射 | MapViewOfFile |
mmap 文件 fd |
| 保护位 | PAGE_READWRITE 等 |
PROT_READ/PROT_WRITE |
| 工作集修剪 | SetProcessWorkingSetSize |
madvise(MADV_PAGEOUT) 等 |
10.2 工作集与 page cache 交互
进程 工作集(Working Set) 是该进程近期驻留物理页集合。Memory Manager 在内存压力下按 LRU 类策略修剪工作集;Cache Manager 的文件页与工作集统计交织——Windows Internals Ch. 5 花大量篇幅描述这一交叉。
Linux 用 active/inactive LRU 列表管理 page cache 与匿名页,回收子系统 在 direct reclaim 与 kswapd 中执行。两侧都区分文件页与匿名页回收优先级,但数据结构与 sysctl 调参接口完全不同。
10.3 大页与 NUMA
Windows 支持大页(Large
Pages、MEM_LARGE_PAGES,需
SeLockMemoryPrivilege);Linux 有 HugeTLB 与
THP(Huge
Pages)。两侧在数据库、科学计算场景中都会启用大页以降低
TLB miss——启用方式与权限模型不同,不宜假设配置可移植。
NT 在 NUMA 机器上通过 NUMA 节点偏好 与进程亲和性影响分配;对照 NUMA 内存。
十一、同步对象与 futex 对照
11.1 内核同步对象
Windows 提供命名/非命名同步对象,均为 Object Manager 管理的内核对象:
| 对象 | 用途 | Linux 近似 |
|---|---|---|
| Event | 事件信号 | eventfd、futex 等待 |
| Mutex | 互斥(可命名) | pthread_mutex、futex PI |
| Semaphore | 计数信号量 | sem_t |
| SRWLock | slim 读写锁 | pthread_rwlock |
CreateEvent /
WaitForSingleObject
将线程置于等待状态,由内核调度器在信号时唤醒。Linux pthread
底层大量使用 futex 系统调用;Windows 等待路径走
KiWaitFor* 系列内核例程。
11.2 临界区 vs 轻量锁
用户态 CRITICAL_SECTION 先自旋再进入内核等待,类似 Linux 的 adaptive mutex。跨平台锁代码若假设”轻量锁永不进内核”在两侧都可能翻车——高竞争下都会落内核。
11.3 I/O 完成与事件
重叠 I/O 可将 OVERLAPPED.hEvent
设为手动重置事件,在 GetQueuedCompletionStatus
之外等待完成。Linux io_uring
用 CQ ring 取完成事件;epoll
不能原生表达”读完成”,只能表达”可读”。
十二、NTSTATUS 与 errno
Windows 内核与用户态广泛使用 NTSTATUS
码(32 位,严重性/设施/代码编码)。用户态 Win32 API 常映射为
BOOL + GetLastError()。
| 概念 | Windows | Linux |
|---|---|---|
| 成功/失败 | NT_SUCCESS(status) |
返回值 0 / -1 |
| 错误码 | NTSTATUS / WIN32 错误 | errno |
| 可重试 | STATUS_PENDING |
EINTR、EAGAIN |
| 传播 | IoCompleteRequest 带状态 | 负 errno 返回用户态 |
驱动开发必须理解 STATUS_PENDING
与完成例程配对;Linux 驱动返回 0 或负 errno,异步 bio 用
->end_io 回调——模式相似,类型系统不同。
十三、Winsock 与 BSD socket
Winsock2 提供与 BSD socket 相似的
socket/bind/listen/accept,但:
- 初始化需
WSAStartup SOCKET类型实为UINT_PTR,不是 fd 整数WSAIoctl、AcceptEx、ConnectEx等与 IOCP 集成- 错误用
WSAGetLastError()而非 errno(除非链接 POSIX 兼容层)
跨平台网络库(libuv、Boost.Asio、Go net)在 Windows 分支用 IOCP,在 Linux 用 epoll/kqueue。对照 select/poll/epoll 演进。
十四、调试与可观测性
| 工具 | Windows | Linux |
|---|---|---|
| 内核调试 | WinDbg / KD | kgdb / crash |
| 用户态栈 | Visual Studio、WinDbg | gdb、perf |
| 内核跟踪 | ETW(Event Tracing for Windows) | ftrace、perf |
| 句柄泄漏 | Process Explorer 句柄视图 | /proc/pid/fd |
ETW 是 NT 上高性能跟踪的基础设施;Linux 的 tracepoint + eBPF 在可编程性上更强。两侧都可回答”谁在阻塞 I/O”,但接口完全不同。
14.1 性能计数器
Windows Performance Counters(PerfMon)
暴露进程、内存、磁盘、网络计数器;Linux 用
/proc/stat、/proc/diskstats、perf
counters。跨平台监控代理(Prometheus
node_exporter、Windows
Exporter)必须分别映射指标名,不能假设字段一一对应。
14.2 驱动与符号
Windows 内核驱动需通过 WHQL/Attestation 签名策略(64
位生产环境);Linux 模块可 insmod
自建模块。分析崩溃转储时,Windows 需符号服务器(Microsoft
公共符号);Linux 需 debuginfo 包与 kdump。两侧”符号是否可用”决定
post-mortem 分析深度。
十五、选型检查清单
维护跨平台服务时,可用下面清单自检:
- I/O 循环:是否区分就绪与完成?Windows 是否用 IOCP?Linux 是否误把 epoll 当完成端口?
- 句柄生命周期:
CloseHandle与close(fd)是否成对?子进程继承规则是否一致? - 配置路径:Registry 键 vs
/etc文件是否文档化? - 进程模型:Windows 是否误用线程当进程隔离?Linux 是否误用多进程代替 Job/cgroup?
- WSL:需要完整 Linux 行为时是否强制 WSL2?
15.1 与 POSIX 兼容层
Windows 上的 POSIX 子系统已退役;今天通过 WSL、MSYS2、Cygwin 提供 Unix 环境,但它们是附加层而非 NT 原生进程模型。Cygwin 在 Win32 上模拟 fork;WSL2 在 Linux 内核上提供真 fork。移植 Linux 守护进程时,路径选择决定 syscall 语义。
15.2 服务与 SCM
Windows 服务控制管理器(SCM)
管理长期运行进程的生命周期、恢复策略、依赖顺序——类似
systemd unit,但配置在 Registry 与 XML/JSON
服务清单中。Linux 进程生命周期
由 init 系统 + unit 文件描述;跨平台 PaaS
需抽象”安装、启动、失败重启”语义。
15.3 容器边界再述
Windows Server 容器与 Linux 容器都称”容器”,但隔离机制不同:Windows 用 silo、Job、命名空间过滤;Linux 用 namespace + cgroup。镜像格式、层文件系统、网络 dataplane 均不兼容——不能假设 Kubernetes 上”同一个 Pod 规范”在两侧行为一致。
15.4 文件系统与路径语义
NTFS 支持 ACL、压缩、重解析点(reparse point,含
junction、symlink);Linux ext4/xfs 用 inode 模式位与 xattr。\\?\
长路径前缀、MAX_PATH 历史限制与 Linux
PATH_MAX 行为不同。WSL2 跨边界访问
\\wsl$\
时,权限映射与大小写敏感性需单独测试——Microsoft
文档明确列出若干不支持或语义不同的操作。
15.5 时间与时钟
Windows 文件时间常用 FILETIME(100 ns
间隔,自 1601 年起);Linux 用
struct timespec(Unix
纪元)。日志关联与跨平台备份工具必须做时间基准转换。高精度计时:Windows
QueryPerformanceCounter;Linux
clock_gettime(CLOCK_MONOTONIC),对照 clocksource。
十六、小结
| 主题 | Windows NT | Linux |
|---|---|---|
| 架构 | Executive / Kernel / HAL 分层 | 宏内核平铺子系统 |
| 资源抽象 | Object Manager + HANDLE | fd + task_struct + 分散机制 |
| I/O | IRP 驱动栈 + IOCP 完成 | VFS + epoll 就绪 / io_uring 完成 |
| 进程模型 | EPROCESS + ETHREAD | task_struct + mm_struct |
| 配置 | Registry | /etc + sysctl |
| Linux 兼容 | WSL2 真内核 VM | 原生 |
维护跨平台服务时,先把完成 vs 就绪、句柄 vs fd、注册表 vs 文本配置三条分叉搞清楚,再谈性能调优。
16.1 进一步阅读(本站)
| 主题 | 篇章 |
|---|---|
| Linux I/O 完成模型 | io_uring 深入 |
| 就绪多路复用 | epoll 内部 |
| 进程与线程实体 | task_struct |
| 虚拟化边界 | KVM 架构 |
| 容器隔离 | 容器安全 |
16.2 进一步阅读(书外)
Windows Internals Part 2 覆盖 I/O、存储、网络驱动细节;Windows Kernel Programming 适合需要写 WDM/KMDF 驱动的读者。Linux 侧则继续沿本系列 VFS 抽象 与 系统调用边界 深入。
16.3 术语对照速查
| Windows | Linux |
|---|---|
| HANDLE | fd(部分资源) |
| IRP | bio / request(分散) |
| IOCP | io_uring CQ(近似) |
| EPROCESS | 进程 = 多个 task_struct 共享 mm |
| Registry | /etc + /proc + /sys |
| Access Token | cred |
| Job Object | cgroup |
| Filter Driver | kernel module stack / FUSE |
| ETW | tracepoint + perf + eBPF |
| NTSTATUS | errno |
本表仅供快速联想,不能替代上文语义差异——尤其 完成 vs 就绪 与 对象统一性 两处,速查表无法压缩。
最后强调 WSL 选型:若工作负载依赖 Linux
内核特性(eBPF、特定 io_uring
op、自定义模块),WSL2 是唯一正解;若只需 GNU
工具链且极度依赖跨文件系统小文件 IO,才评估 WSL1
历史场景——微软已将默认安装导向 WSL2,新环境不应再按 WSL1
设计。
NT 与 Linux 的差异是系统性的:从对象模型到 I/O 完成语义,再到配置存储。跨平台工程师应把本篇当作对照地图,而不是 Windows 驱动教程——驱动开发请直接阅读 Windows Internals 与 WDK。
参考文献
书籍
- Russinovich, M. E., Solomon, D. A., Ionescu, A. Windows Internals, Part 1, 7th ed. Microsoft Press, 2017
- Yosifovich, P. Windows Kernel Programming, 2019
官方文档
- Microsoft, Windows Subsystem for Linux Architecture, learn.microsoft.com
- Microsoft, I/O Completion Ports, learn.microsoft.com
- Microsoft, Object Manager, Windows Kernel-Mode Driver Architecture design guide
- Microsoft, I/O Manager, Windows Kernel-Mode Driver Architecture design guide
源码与对照
- Linux:
fs/eventpoll.c,io_uring/,include/linux/sched.h(对照阅读)
下一篇:实时 OS 巡礼
同主题继续阅读
把当前热点继续串成多页阅读,而不是停在单篇消费。
【操作系统百科】异步 I/O 模型 benchmark
epoll、io_uring、libaio、阻塞线程池——四种异步模型的真实性能对比。本文用统一 workload 量化 echo server、静态文件服务、数据库 I/O 场景下的吞吐、延迟与 CPU 开销。
【操作系统百科】OS 的下一个十年
综合 Windows/RTOS/Unikernel/Rust/机密计算/CXL 等前沿篇章,从 Rust 内核、机密计算、可拆分内存、io_uring 统一 I/O、eBPF 可编程内核五条趋势出发,按现在/三年/十年框架说明应用开发者应调整的假设与可执行建议。
【操作系统百科】io_uring 内核内部
io_uring 用共享内存 ring buffer 实现零 syscall 异步 I/O——SQ/CQ、SQPOLL、IOPOLL、注册 fd/buffer、multishot、安全模型演化。本文深入内核实现与工程实践。
【操作系统百科】epoll 内部
epoll 用红黑树管理 fd、就绪链表 O(1) 返回事件——打败 select/poll 的关键。本文讲 eventpoll 结构、LT/ET 语义、EPOLLEXCLUSIVE 与惊群、级联 epoll、EPOLLONESHOT、与 io_uring 的比较。