第1章 Windows内部概览
从进程、虚拟内存、线程、系统调用到句柄与内核对象,建立 Windows 内核编程的系统坐标。
学习目标
- 能解释进程、线程、虚拟内存、系统服务和句柄各自是什么,以及它们在 Windows 内核架构中的位置
- 能描述用户模式程序通过什么机制访问内核对象,为什么必须用句柄而不是直接指针
- 能回答:两个进程的虚拟地址 0x00400000 指向同一块物理内存吗?为什么?
为什么写内核驱动要先搞懂 Windows 内部
想象你住在一栋大楼里。你的房间是私有的——别人不能随便进来,你也不能随便去别人的房间。大楼有公共设施(电梯、水管),使用它们要遵守规则。Windows 就是这栋大楼。
每个程序运行时,系统给它分配一个独立的房间。房间有自己的地址编号和资源清单。程序在自己房间里可以自由活动,但不能直接闯入别人的房间或大楼的机房。
这一章带你认识这栋大楼的基本结构:房间怎么分配、电梯怎么运行、住户怎么跟物业打交道。搞不懂这些就动手改大楼线路,轻则停电,重则整栋楼塌。
进程:一个隔离的执行环境
↡Windows 中资源隔离的基本单位,拥有独立的虚拟地址空间、句柄表和安全令牌 是 Windows 资源管理的最外层容器。每个进程拥有自己的虚拟地址空间(64 位下约 128TB)、一张句柄表和一个安全令牌。
进程的核心作用是隔离:进程 A 不能直接读写进程 B 的内存,也不能直接访问 B 的句柄。这种隔离由 CPU 的虚拟内存机制硬件保障——每个进程有独立的页表,相同的虚拟地址在不同进程中映射到不同的物理内存。
进程本身不执行代码。真正执行代码的是进程内的线程。一个进程至少有一个线程(主线程),也可以创建多个线程并发执行。在下面的架构图中,进程是最外层的大方框,线程、虚拟地址空间和句柄表都是它内部的组件。
虚拟内存:每个进程都有自己的地址空间
↡一种内存管理机制,使每个进程拥有独立的地址空间,虚拟地址通过页表映射到物理内存 让每个进程都以为自己独占了整个内存空间。程序看到的地址是虚拟地址,不是物理地址。虚拟地址通过页表映射到真实的物理内存。
地址空间按页管理,每页 4KB。页有三种↡虚拟内存页的当前状态:空闲(未分配)、保留(已预留地址但未分配物理内存)、已提交(已分配物理内存,可读写):空闲(Free)、保留(Reserved)、已提交(Committed)。只有已提交的页才真正分配了物理内存,可以读写。
关键点:同一个虚拟地址(如 0x00400000)在进程 A 和进程 B 中指向完全不同的物理内存。这就是进程隔离的底层保障。下面的交互图中,勾选“显示物理地址映射”可以看到这一点。
线程:CPU 实际调度的单位
↡CPU 调度和执行的基本单位,拥有独立的栈和寄存器上下文,共享所属进程的地址空间和资源 才是 CPU 实际执行代码的单位。每个线程有自己的用户态栈、内核态栈和寄存器上下文,但共享所属进程的虚拟地址空间、句柄表和安全令牌。
Windows 是抢占式多任务系统:内核的调度器可以在任意时刻中断当前线程,切换到另一个线程。线程被中断时,寄存器状态被保存到内核栈中;恢复执行时再还原。一个进程可以包含多个线程并发执行,它们共享同一块地址空间。
系统服务:用户模式与内核模式的边界
↡用户模式程序请求内核执行特权操作的受控接口,通过 syscall 指令从 Ring 3 切换到 Ring 0(system call)是用户模式代码访问内核功能的唯一正规通道。
CPU 运行在两个特权级别:用户模式(Ring 3)不能直接访问物理内存、I/O 端口或内核数据结构;内核模式(Ring 0)可以访问所有内存和硬件。用户程序需要读文件、创建线程、查询进程信息时,不能直接操作——必须通过 syscall 指令进入内核模式,由内核代为执行。
这个切换不是普通的函数调用:CPU 硬件会切换栈、切换特权级别,内核会检查参数合法性和调用者权限。在架构图中,这就是用户模式和内核模式之间的那道虚线。
句柄与内核对象:通过门票而不是指针访问
↡由内核对象管理器管理的内部数据结构,如进程对象 EPROCESS、线程对象 ETHREAD、文件对象 FILE_OBJECT,存放在内核空间存放在内核空间,用户代码不能直接访问。↡指向内核对象的间接引用,是进程句柄表中的索引,不是内存指针 是用户程序引用内核对象的方式——它不是指针,而是进程句柄表中的索引。
为什么不直接用指针?三个原因:内核需要在打开对象时检查访问权限(你要读还是写?);在对象使用期间保障生命周期(对象不会被意外销毁);在每次操作时验证安全隔离(即使拿到句柄也只能做授权的操作)。
句柄表是每个进程私有的。同一个内核对象,在不同进程中的句柄值可能不同。在架构图中,句柄表中的条目通过箭头指向内核空间中的对象——这就是“间接引用”的可视化。
动手:探索进程内部结构
下面的可视化展示了 Windows 进程的内部架构剖面。点击图中的不同区域(进程、线程、虚拟地址空间、句柄表、syscall 边界、内核空间),查看对应的详细解释。
猜一猜:勾选“显示物理地址映射”后,观察已提交页的物理地址——同一个虚拟地址 0x00400000 在不同进程中会指向同一块物理内存吗?动手试试再看答案。
进程是 Windows 资源隔离的基本单位。每个进程拥有独立的虚拟地址空间、句柄表和安全令牌。进程本身不执行代码——执行代码的是它内部的线程。
代码对照:用户模式如何访问内核对象
用户模式程序通过句柄访问内核对象的典型流程:打开对象 → 使用句柄操作 → 关闭句柄。整个过程中,用户代码从未直接接触内核对象。
// 1. 打开进程对象,获得句柄(不是指针!)
HANDLE hProcess = OpenProcess(
PROCESS_QUERY_INFORMATION, // 声明需要的访问权限
FALSE,
pid); // 目标进程 ID
// 2. 通过句柄查询信息(内核代为执行)
PROCESS_INFORMATION info;
GetProcessInformation(hProcess, &info, sizeof(info));
// 3. 用完必须关闭,否则句柄表会泄漏
CloseHandle(hProcess);用户模式 内核模式
│ │
│ OpenProcess(pid) │
│ ──── syscall ──────────────────→│ 检查权限 → 查找对象
│ │ → 在句柄表插入条目
│ ←──── 返回句柄值 0x00A4 ────────│
│ │
│ GetProcessInformation(handle) │
│ ──── syscall ──────────────────→│ 通过句柄表找到对象
│ │ → 读取信息 → 复制到用户缓冲区
│ ←──── 返回结果 ─────────────────│
│ │
│ CloseHandle(handle) │
│ ──── syscall ──────────────────→│ 删除句柄表条目
│ │ → 减少对象引用计数
│ ←──── 返回 ─────────────────────│容易踩的坑
小结
- 进程是隔离容器,提供独立地址空间和句柄表,但不执行代码
- 虚拟内存让每个进程有自己的地址空间,相同地址指向不同物理内存
- 线程是 CPU 调度的基本单位,共享进程资源但有独立栈
- 系统服务是用户态进入内核态的受控通道,由 CPU 硬件保障
- 句柄是访问内核对象的门票,不是指针,内核通过它控制权限
练习
问题 1: 在上面的探索器中,勾选“显示物理地址映射”,观察已提交页的物理地址。用一句话解释为什么同一个虚拟地址在不同进程中指向不同的物理内存。
问题 2: 为什么用户模式程序不能直接用指针访问内核对象,而必须通过句柄?
问题 3: 一个进程有 3 个线程,它们共享什么?各自独有什么?
知识点对照
1.2.2 系统内存
系统内存指内核地址空间中的系统池与缓存区域,所有驱动共享并由内存管理器统一分配调度,这也是本章的核心要点。
1.5 系统总体架构
系统总体架构把系统分为用户模式与内核模式两部分,系统服务是两者之间唯一的沟通桥梁,这也是本章的核心要点。
1.6 句柄和对象
句柄和对象是内核资源的主要访问方式:句柄是用户态的引用,对象由对象管理器统一管理,这也是本章的核心要点。
1.6.1 对象名称
对象名称用反斜杠路径表示,存放在对象目录中,供各类内核组件按名称查找并引用对象,这也是本章的核心要点。
1.6.2 访问已经存在的对象
访问已经存在的对象用 ObOpenObjectByName 按名称打开,成功后获得新句柄并增加计数。
术语表
名词解释
本章出现的专业名词,用大白话再讲一遍。
- 进程
Windows 中资源隔离的基本单位。每个进程有独立的虚拟地址空间、句柄表和安全令牌。进程本身不执行代码,执行代码的是它内部的线程。可以理解为“一个程序的一次运行”。
- 虚拟内存
让每个进程拥有独立地址空间的内存管理机制。程序看到的地址是虚拟的,通过页表映射到物理内存。同一个虚拟地址在不同进程中指向不同的物理内存,这就是进程隔离的底层保障。
- 页状态
虚拟内存按页(4KB)管理,每页有三种状态:空闲(未分配)、保留(预留地址但未分配物理内存)、已提交(已分配物理内存,可读写)。
- 线程
CPU 调度和执行代码的基本单位。每个线程有独立的栈和寄存器上下文,但共享所属进程的地址空间和资源。一个进程可以包含多个线程并发执行。
- 系统服务
用户模式程序请求内核执行特权操作的受控接口。通过 syscall 指令从 Ring 3(用户模式)切换到 Ring 0(内核模式),由内核代为执行。这不是普通的函数调用。
- 内核对象
由内核对象管理器管理的内部数据结构,如进程对象(EPROCESS)、线程对象(ETHREAD)、文件对象(FILE_OBJECT)等。存放在内核空间,用户代码不能直接访问。
- 句柄
指向内核对象的间接引用,是进程句柄表中的索引,不是内存指针。用户程序通过句柄让内核代为操作内核对象,内核据此控制访问权限和对象生命周期。