内核模式编程环境
图3-1显示了Windows NT操作系统的某些组成部分。每个部分都输出一些服务函数,这些函数以两个特别的字母组合开头:
- I/O管理器(Io前缀) 包含许多驱动程序可以使用的服务函数,对这些函数的描述遍及本书。
- 进程结构模块(Ps前缀) 创建并管理内核模式线程。普通的WDM驱动程序应使用一个独立的线程来循检无中断生成能力的设备。
- 内存管理器(Mm前缀) 控制页表,页表定义了虚拟内存到物理内存之间的映射。
- executive (Ex前缀) 提供堆管理和同步服务。本章将讨论堆管理函数。下一章讨论同步服务。
- 对象管理器(Ob前缀) 集中控制Windows NT中的各种数据对象。WDM驱动程序仅需要对象管理器维护对象的参考计数,以防止对象被意外删除。
- 安全参考监视器(Se前缀) 使文件系统驱动程序执行安全检测。I/O请求到达WDM驱动程序前已经做完了安全检测,所以本书不讨论这些函数。
- 运行时间库部件(Rtl前缀) 包含工具例程,例如列表和串管理例程,内核模式驱动程序可以用这些例程来替代常规的ANSI标准例程。大部分例程可以从其名字上直接看出它的功能。
- Win32子系统存在于用户模式中,所以用户模式中的应用程序可以容易地调用其例程。为了方便,Windows NT在内核模式中实现了一些有Zw前缀名的函数,这些函数可以使驱动程序调用Win32子系统例程。Windows 2000 DDK中仅暴露一少部分这样的函数给驱动程序使用,包括访问文件和注册表的函数。我将在本章讨论这些函数。
- Windows NT内核(Ke前缀) 所有多线程和多处理器的低级同步活动都发生在内核中,我将在下一章中讨论KeXxx函数。
- 在操作系统的最底层是硬件抽象层(HAL,Hal前缀)。操作系统把所有关于计算机硬件如何连接的信息都存放在HAL中。HAL了解如何在特定平台上实现中断操作,如何实现自旋锁,如何寻址I/O或内存映射设备,等等。 WDM驱动程序不直接与硬件对话,它通过调用HAL中的函数来达到目的。所以WDM驱动程序能够实现平台无关和总线无关。
图3-1. 内核模式支持例程概观
使用标准运行时间库函数
在历史上,Windows NT的设计者认为,不应该在驱动程序中使用C编译器厂商提供的运行时间库。部分原因是由于Windows NT是在ANSI标准出台前设计的,每个C编译器厂商都有自己的实现方法和品质标准。另一个原因是因为标准运行时间库中的例程有时需要依赖用户模式中的应用程序来初始化,并且有些例程并不是以多线程或多处理器安全的方式实现的。
直到现在,官方认为内核模式驱动程序仅应调用DDK中公开的函数。例如,你不能在驱动程序中调用wcscmp函数,而应该调用RtlCompareUnicodeString。然而,这里有一个公开的秘密,用于创建驱动程序的标准输入库(ntoskrnl.lib)定义了许多函数,而这些函数却是在诸如string.h、stdio.h、stdlib.h,和ctypes.h的头文件中声明的,这些头文件都是应用程序经常使用的头文件。所以,为什么我们不能使用它们?实际上,倘若你了解所有的内部细节,你完全可以调用它们。但你不能总这样做,例如,你不能总用memcpy替代RtlCopyBytes,因为这两者稍有不同。(RtlCopyByte可以保证一个字节一个字节地复制数据而不是以较大的块,大块复制数据在某些RISC平台上会出现麻烦)
注意侧效
驱动程序中使用的许多支持“函数”其实是DDK头文件中定义的宏。我们都知道应该避免在宏的参数中使用带有边效的表达式,原因很明显,宏可以多次使用其参数,见下面代码:
int a = 2, b = 42, c;
c = min(a++, b);
|
a的值是什么?(c的值又是什么?) 让我们看看这个似是而非的min宏:
#define min(x,y) (((x)<(y)) ? (x) : (y))
|
如果你用a++代替x,你将看到a最后等于4,因为表达式a++执行了两次。而“函数”min将返回3而不是2,因为函数的返回值是在第二次计算a++之前提取的a值。
通常,你不能知道DDK什么时候使用宏,什么时候使用真正的外部函数。有时候,一个特殊的服务函数在某些平台上是宏而在其它平台上却是外部函数。此外,Microsoft也可能在将来改变想法。所以,当你写WDM驱动程序时应坚守下面原则:
决不在内核模式服务函数的参数中使用带有侧效的表达式。