Windows内核驱动重要知识
Windows系统简单分层:

用户模式和内核模式
Windows从总体上分为内核模式(Kernel Mode)和用户模式(User Mode)。谈到操作系统的内核模式和用户模式,一般会和CPU的特权层联系起来。一般来说,用户模式的程序运行在r3层,而系统内核与驱动运行在r0层。

如果应用程序想进行一些敏感操作,如直接访问物理内存、物理端口,应用程序需要向内核模式下的组件提出请求。在Windows中,用户模式和内核模式的切换是通过软件中断实现的。
Windows系统总体架构

下面分层说说每一层的具体内容。
win32子系统
win32子系统是真正的Windows子系统,提供了大量的API函数供应用程序和其它子系统使用。Windows API分为三类,分别是USER函数、GDI函数和KERNEL函数。
- USER函数:这类函数管理窗口、菜单、对话框和控件。
- GDI函数:这类函数在物理设备上执行绘图操作。
- KERNEL函数:这类函数管理非GUI资源,例如,进程、线程、文件和同步服务等。
读者可以发现Windows系统目录中有对应的三个系统文件,分别是USER32.dll、GDI32.dll和KERNEL32.dll。这三个文件提供了以上三类API的接口。当应用程序加载的时候,操作系统除了将应用程序加载到内存中,同时将以上三个DLL文件加载到内存中。

native api
大部分Win32子系统的API,都通过Native API实现的。Native API的函数一般都是在Win32 API上加上Nt两个字母。例如,CreateFile函数对应着NtCreateFile函数。所有Native API都是在Ntdll.dll中实现的。在win32子系统下面再设置一层native api调用,主要是为了兼容性考虑。
Native API是从用户模式进入内核模式的大门,它通过软件中断方式进入到内核模式,并调用内核的系统服务。
Native API从用户模式穿越进入内核模式,调用系统服务。从用户模式进入到内核模式,是通过软中断的方式进入的。
系统服务
软中断会将Native API中的参数和系统服务号的参数一同传进内核模式,不同的Native API会对应不同的系统服务号。在系统服务组件中,有一个系统服务描述符表(System Service Descriptor Table)。根据这个系统服务号为索引,从表中可以查出对应系统服务函数的函数地址:

执行体组件
Windows 内核中的执行体(Executive)是 ntoskrnl.exe 中的上层部分,它构建在底层的 微内核(Microkernel)之上。如果说微内核只负责最核心的线程调度、中断和同步原语,那么执行体就是负责实现操作系统高级策略 和资源管理的"大管家"。
1. 架构位置
在 Windows 的混合内核架构中,执行体与微内核位于同一个二进制文件(ntoskrnl.exe)中,运行在 Ring 0(内核模式)。它负责将微内核提供的底层机制(如线程对象)包装成具有丰富语义的执行体对象(如进程对象),并向上层(用户模式子系统、驱动程序)提供服务。
2. 核心组件(管理器)
执行体由一系列功能独立的"管理器"组成,这些也是驱动开发中最常打交道的部分:
| 组件名称 | 核心功能 | 驱动开发中的常见应用 |
|---|---|---|
| I/O 管理器 | 管理所有 I/O 请求,构建 IRP,管理设备栈 | 驱动分发函数、IoCallDriver、IoCompleteRequest |
| 对象管理器 | 创建、管理内核对象(进程、线程、文件、事件等),维护命名空间 | ObReferenceObjectByHandle、ZwCreateFile |
| 进程管理器 | 创建/终止进程和线程,管理进程和线程属性 | PsCreateSystemThread、PsLookupProcessByProcessId |
| 内存管理器 | 实现虚拟内存,管理分页/非分页池,处理缺页异常 | ExAllocatePoolWithTag、MmMapIoSpace |
| 配置管理器 | 实现和管理系统注册表 | ZwOpenKey、ZwQueryValueKey(内核模式) |
| 即插即用管理器 | 枚举硬件、加载驱动、管理设备树 | IoRegisterDeviceInterface、IRP_MJ_PNP |
| 电源管理器 | 协调系统电源状态转换 | IRP_MJ_POWER、PoSetPowerState |
| 安全引用监视器 | 强制本地安全策略,执行访问检查 | SeAccessCheck、SeAssignSecurity |
| 缓存管理器 | 为文件系统提供统一的数据缓存 | 文件系统驱动使用 |
3. 函数命名前缀
在驱动代码中,你可以通过函数前缀快速识别它属于执行体的哪个组件:
- Io :I/O 管理器(
IoCreateDevice,IoGetCurrentIrpStackLocation) - Ob :对象管理器(
ObOpenObjectByPointer) - Ps :进程管理器(
PsGetCurrentProcessId) - Ex :执行体支持库(
ExAllocatePool) - Mm :内存管理器(
MmGetSystemRoutineAddress) - Zw/Nt :系统服务接口(
ZwReadFile)
4. 与微内核的关系
- 微内核(Kernel) :提供机制 。例如,它知道如何切换线程(
KiSwapThread),但不知道线程的优先级策略。 - 执行体(Executive) :提供策略。例如,它决定哪个高优先级的线程应该被调度,然后调用微内核的机制去执行切换。
总结:执行体是 Windows 内核的"业务逻辑层",它封装了硬件细节和底层机制,为驱动程序和系统服务提供了稳定、统一的高级 API 接口。
硬件抽象层
Windows操作系统易于移植到各个硬件平台的操作系统。事实上,Windows已经可以运行在Intel的32位X86CPU上和64位的CPU上,同时可以运行在DEC Alpha的平台,也可以运行在基于MIPS的平台上(但由于种种原因,微软公司放弃了对这个平台的支持计划)。另外,Windows能更好地支持更多的总线结构,例如,PCI总线、CPCI总线、USB总线、老式的ISA总线、EISA总线等。这些良好的移植性和兼容性得益于硬件抽象层(HAL)的设计。Windows的设计者用硬件抽象层隔绝了操作系统和硬件的直接连接,而在中间插入了硬件抽象层。硬件抽象层使操作系统和具体硬件进行通信,其中硬件抽象层对各种硬件操作进行了抽象,这也是硬件抽象层名字的由来。各个平台上的硬件操作不尽相同,不同平台提供不同的硬件抽象层,并对上层提供统一的操作硬件接口。对硬件抽象层操作的不光是操作系统的内核,还有本书介绍的驱动程序。在驱动程序中,应尽量避免使用针对平台的汇编指令,而应该使用与平台无关的HAL函数或宏。例如,对端口操作,驱动程序虽然可以嵌入汇编指令,如IN、OUT汇编指令,但是应该尽量使用硬件抽象层提供的函数,如READ_PORT_BUFFER_UCHAR、WRITE_PORT_BUFFER_UCHAR。
驱动和驱动对象
IO管理器收到应用程序的请求后,创建相应的IRP,将其传递到对应的驱动程序中进行处理。一般有如下几种处理方法:
- 根据IRP的请求,直接操作具体硬件,然后完成此IRP,并返回。
- 将此IRP的请求,转发到更底层的驱动中去,并等待底层驱动的返回。
- 接受到IRP请求后,不是急于完成。而是分配新的IRP发到其他驱动程序中,并等待返回。
驱动程序处理IRP的过程往往不是单独的操作,而是将以上这几种操作结合在一起。
每个驱动程序都有唯一的驱动对象与之对应,驱动对象是驱动在内核中的表示。驱动对象用DRIVER_OBJECT数据结构表示,它作为驱动的一个实例被内核加载,并且内核对一个驱动只加载一个实例驱动对象。确切来说,驱动对象是由内核的对象管理器负责创建,IO管理器负责加载的,驱动程序需要在DriverEntry中进行初始化(由系统进程调用)。

动对象结构体中有一个设备对象链表的表头,
设备和设备对象
打开Windows的设备管理器,可以发现这里罗列着计算机里安装的所有设备。这些设备有的是真实的物理设备,例如,网卡设备、显卡设备等。有些设备则是虚拟设备,比如我们自己创建的设备。Windows的设计者们为了简化对不同设备的操作,实现对不同设备统一接口,将所有设备以普通文件看待。也就是说在Windows中,无论何种设备,都用操作文件的办法去操作设备。
设备对象是设备在内核中的表现形式,设备对象结构体定义如下:

