1.Kernel Mode驱动程序基础
1.1 KMD结构概述
1.1.1 主要组成部分
根据地址空间、代码权限和职责的不同,Windows NT内部划分为两个截然不同的部分。
地址空间的享用方式也非常容易理解,整个32位系统的4GB内容被划分为两个相等的部分,用户模式(user-mode)的进程使用的地址空间被映射到低 位的2GB上(地址范围00000000 - 7FFFFFFFh),而高位的2GB(地址范围80000000h - 0FFFFFFFFh)则供操作系统的组成部分来使用,如设备驱动程序、系统内存池、系统使用的数据结构等,在这部分中,内存共享的权限和职责等方面就要 复杂一点了。
下面就是用户模式进程的一些简单分类:
◎ 系统支持进程--如Logon进程(位于/%SystemRoot%/System32/Winlogon.exe)
◎ 服务进程--如Spooler进程(位于/%SystemRoot%/System32/spoolsv.exe)
◎ 用户应用程序--任何Win32、Windows 3.1、DOS、POSIX或者OS/2程序
◎ 子系统--Windows内置3个子系统:Win32(位于/%SystemRoot%/System32/Csrss.exe)、POSIX子系统(位 于/%SystemRoot%/System32/Psxss.exe)和OS/2子系统(位于/%SystemRoot%/System32/ Os2ss.exe),
在Windows XP以及后续的操作系统中,POSIX和OS/2子系统已经被去掉了。
而下面是内核模式的一些模块:
◎ 运行模块--内存管理、进程和线程的管理、安全机制等
◎ 内核--线程调度、中断、异常的分派等(运行模块和内核位于/%SystemRoot%/System32/Ntoskrnl.exe)
◎ 设备驱动程序--硬件设备驱动程序、文件系统和网络驱动程序
◎
硬件抽象层(Hardware Abstraction Layer, HAL)--将内核、设备驱动程序和运行模块和具体的硬件平台隔离开(位于/%SystemRoot%/System32/Hal.dll)
◎ 窗口和图形系统--实现GUI函数,如处理窗口、用户界面的控制和绘画等(位于/%SystemRoot%/System32/Win32k.sys)
图1.1 Windows NT结构简图
1.1.2 内核模式和用户模式
Intel x86体系结构的处理器定义了4个级别的权限(称为Ring),Windows系统使用了Ring0(供特权模式使用)和Ring3(供用户模式使用), Windows系统只使用了2个级别的权限级别的原因是为了和其他一些硬件系统兼容,这些硬件系统只有2个级别的权限,如Compaq Alpha和Silicon Graphics MIPS等。
每个用户模式的进程有其私有的地址空间,这些进程在最低的权限级别下运行(称为Ring3或者用户模式),它们不允许执行CPU的特权指令,对系统所属的 数据、地址空间以及硬件等的访问也是被严格限制的,例如,如果某个用户程序访问4G地址空间中的高位2G,那么系统就会立即将其终止执行。要注意的是,进 程调用系统功能的时候,可以切换到内核模式执行,但是调用结束后,就返回到用户模式了。
用户模式的进程总是被认为是对操作系统稳定性的潜在威胁,所以它们的权限被严格地限制,任何触及这些限制的举动都将使进程被终止。
而内核模式的组件则可以共享这些受保护的内核模式内存空间,在特权级别下运行(也称为Ring0),允许执行任何CPU指令,包括特权指令,可以无限制地访问系统数据、代码和硬件资源。
内核模式代码运行在系统地址空间中,并总是被认为是可信任的,一旦被装载运行后,驱动程序就是系统的一部分,可以无限制地做任何事情。
总的来说,用户模式程序被完全从操作系统隔离,这对操作系统的完整性来说是件好事情,但对某些种类的应用程序来说就太头痛了,比如Debug工具。幸运地 是,这些在用户模式几乎不可能完成的任务完全可以通过内核模式的驱动程序来完成,因为这些驱动程序的操作是不受限制的。因此,如果你打算从用户模式存取操 作系统内部的数据结构或者函数的话,唯一的方法就是将一个内核模式驱动程序装载到系统的地址空间中(并调用它),这是很简单的事情,操作系统完全支持这样 的操作。
1.2 Windows NT设备驱动程序
1.2.1 设备驱动程序的分类
Windows NT支持的设备驱动程序的范围很广,它们的分类如下:
用户模式的驱动程序:
◎ 虚拟设备驱动程序(Virtual Device Drivers/VDD)--用户模式的组件,用于为16位的MS-DOS应用程序提供虚拟的执行环境,虽然和Windows 95/98里面的VxD从功能上看起来是差不多的,但实际上两者根本不同。
◎ 打印驱动程序--将与设备无关的图形转换到和打印机相关的指令
内核模式驱动程序:
◎ 文件系统驱动程序--实现标准的文件系统模型
◎ 传统设备驱动程序--用于在没有其他驱动程序帮助的情况下控制硬件设备,它们是为老版本的Windows NT系统所写的,但是也可以不加修改地运行在Windows 2000/XP/2003系统上
◎ 视频驱动程序--不用多介绍了
◎
流驱动程序--支持多媒体设备,如声卡
◎ WDM驱动程序--即Windows Driver Model,WDM包括对Windows NT电源管理和即插即用的支持,WDM可以在Windows 2000、Windows 98和Windows ME下实现,所以在这些操作系统下,WDM驱动程序在源代码级别是兼容的,在有些情况下,在二进制代码级别上也是兼容的
在不同的资料中,对驱动程序的分类方法可能完全不同,但这并不是问题。
从名称理解,设备驱动程序是用于控制某个设备的,但这个"设备"并不一定指的是物理上存在的设备,它也可以是虚拟设备。
从文件结构上讲,设备驱动程序就是一个普普通通的PE格式文件,就像其他EXE或者DLL文件一样。
设备驱动程序是一个可装载的内核模式模块,一般以 SYS为扩展名。他们之间的不同点在于两种的装载方法是完全不同的。实际上,我们可以把设备驱动程序理解成一个内核模式的DLL,用于完成在用户模式下所 不能完成的功能,本质上的不同就在于我们无法直接存取设备驱动程序的代码和数据(注:DLL的代码和数据是可以被直接存取的,这方面的资料可以参考 《Windows环境下32位汇编语言程序设计一书》中的DLL一章),唯一的存取方式是通过I/O管理器,它提供了简单的驱动程序管理环境。
刚开始学习KMD的开发的时候,内核提供了完全不同的函数和数据结构, 以至于你要从头开始了解,而且资料奇缺无比,一般情况下,可供参考的只有头文件。
1.2.2 分层的和单层的设备驱动程序
大部分控制硬件设备的驱动程序是分层的驱动程序,分层驱动的概念就是当用户模式发出一个请求时,每个请求从高层次的驱动程序逐层处理并流传到低层次的驱动 程序中,一个I/O请求的处理可能分步在多个驱动程序中,例如,如果一个应用程序发出读盘请求,处理请求会在多个驱动程序中流过,在其中你也可以再加入n 多个过滤驱动程序(比如插入一个加解密的模块)。
单层的驱动程序是最简单的一类驱动程序,这一类驱动程序通常并不依赖于其他已装载的驱动程序,他们的接口仅仅针对用户模式的应用程序,开发和调试这一类驱动程序是非常简单的,我们即将开始讨论的就是这类程序,其他类型的驱动程序将在以后讨论。
1.3 线程上下文(Thread Context)
在大多数情况下,我们的系统中只安装了一个CPU,所以,对于所有这些运行中的程序来说,操作系统对每个进程中的线程所使用的CPU时间进行调度,循环为 每个线程分配时间片,这就造成了多个程序同时执行的假象。如果系统中安装了多个CPU,那么操作系统的调度算法将复杂得多,因为它要将各CPU上的线程进 行平衡。如果Windows检测到一个新线程要开始运行了,它将进行一次上下文切换(context switch)(注:上下文(Content)实际上就是线程运行的环境,也就是运行时各寄存器和其他东东的状态,更自然的理解就是"线程状态")。
所谓 上下文切换就是保存线程运行时的机器状态,然后将另一个线程的状态恢复并重新开始执行。如果重新开始执行的线程属于另一个进程,那么该进程的地址空间也将 被同时切换过来(通过在CR3寄存器中装入页表)。
每个用户进程都有私有的地址空间,所以他们的页表都是不同的,CPU通过切换页表来将虚拟地址映射到物理地址,设备驱动程序并不需要直接做这些工作。上下文切换比较耗CPU时间,所以驱动程序一般不创建它们自己的线程,它们一般在下列环境中的一个中运行:
1. 在发起I/O请求的用户线程中运行
2. 在内核模式下的系统线程中运行
3. 作为中断运行(并不处于哪个特定的进程或线程中,因为它们都被暂时挂起了)
在处理I/O请求包(IRPs)时,我们总是运行在和用户模式的调用者相同的进程上下文中运行,这样我们就能对用户程序的地址空间进行寻址。但是当驱动程序被加载或者卸载的时候,我们将在系统进程中运行,这时存取的只能是系统的地址空间。
1.4 中断请求级别
中断是任何操作系统都少不了的组成部分,
中断使处理器打断正常的程序流程来首先处理它们,中断分硬件中断和软件中断两种,中断是分优先级的,一个高优先级的中断可以打断低优先级的中断的执行。
Windows中把中断优先级称为IRQLs(interrupt request levels),在系统中表示为从0(被动)到31(高级)的整数,其中大的数值对应高优先级的中断。注意IRQL值的含义和线程调度优先级的含义是完全两码事情。
严格来说,IRQL=0的中断并不是中断,因为它无法打断任何其他代码的执行(因为没有比0更低级的代码了),
所有的用户模式线程在这个级别上运行,该级 别也称为被动级别(passive level,IRQL=0)
。我们后面要讨论的驱动程序代码也在这个级别上运行,注意这并不意味着其他的驱动程序也在被动级别下运行。
因此这里还有两个重要的结论:
首先:当驱动程序运行于用户模式程序的线程中时,代码的执行可能被高IRQL级别的代码打断,一些函数可以用来获取当前的IRQL值,并可以对其进行提升或者降低。
第二:被动模式IRQL下的代码可以调用任何的内核函数(DDK指明了每个函数允许调用的IRQL级别),可以对已分页的或未分页的内存进行寻址(注:即 已映射过的虚拟地址还是物理内存地址)。反过来,
当在一个比较高的IRQL级别下对分页内存进行寻址时(指等于或高于DISPATCH_LEVEL),系 统将崩溃,因为这时内存管理器的IRQL级别反而比较低,以至于无法处理页错误了。
1.5 系统崩溃
我想每个人都见过著名的蓝屏死机画面,即"Blue Screen Of Death",简称为BSOD,也许根本不需要解释它是怎么出现或者在什么时候出现的,因为在后面的KMD开发过程中,你会很频繁地遇到它们。
在内核模式下,Windows不对任何系统内存进行保护,由于内核模式的驱动程序可以对系统内存和操作系统的地址空间进行任意存取,所以你必须对你开发的驱动程序进行严格的测试,以防它危及到系统的稳定。
你可以把这个作为最基本的原则,另外,如果没有线程上下文、中断优先级、内核模式和用户模式等方面的概念,开发内核模式驱动程序将是不可能的事
1.6 Driver Development Kit
Windows DDK是MSDN专业版和宇宙版的一部分,它也可以从http://www.microsoft.com/ddk/下载,对于开发设备驱动程序来说, DDK是关于Windows NT内部信息,包括系统函数、数据结构等的丰富资源,不幸的是,微软已经停止了免费发放DDK,所以现在只好去买正版的CD了.
除了文档,DDK还包含了一堆的库文件(*.lib),这些库可以在链接的时候用上。这些库有两种版本:普通的版本(称为free build)和特殊的包含Debug信息的版本(称为checked build),它们分别位于%ddk%/libfre/i386和%ddk%/libchk/i386目录下,check build是在编译Windows源代码时加上DEBUG标志后生成的,在开发驱动程序时,它们可以提供更加精确的错误定位,但是你首先要根据你的操作系 统选择合适的lib版本才行。
1.7 汇编程序员使用的KmdKit
KmdKit包含了所有用汇编开发KMD所需要的东西:include文件、lib文件、宏定义、例子文件、工具和一些文章,你可以自己在软件包中找到更多的东西,下一节我们将从这个软件包中包括的一些例子开始学习KMD的编程。
1.8 驱动程序的调试
调试内核模式的代码需要合适的调试器,Compuware的SoftIce是个不错的选择(见 http://www.compuware.com/products/numega/index.htm),当然你也可以使用Microsoft Kernel Debugger,它需要两台计算机:主机和目标机器,目标机器是被调试的机器,主机是运行调试软件的机器。Mark Russinovich ( http://www.sysinternals.com/ ) 也写了一个工具,叫做LiveKd,它允许在单台机器上运行Microsoft Kernel Debugger,而不再需要两台机器了。
1.9 其他参考资料
1. David Solomon, Mark Russinovich,
"Inside Microsoft Windows 2000. Third Edition", Microsoft Press, 2000
5. Walter Oney, "Programming the Microsoft Driver Model", Microsoft Press, 1999
6. Walter Oney, "Programming the Microsoft Windows Driver Model. 2nd edition", Microsoft Press, 2003(这是一本很好的书,它强调的是即插即用驱动程序的开发,但这并不降低了它的重要性,因为驱动开发方面的基本理论都是通用的)
7. Art Baker, Jerry Lozano, "The Windows 2000 Device Driver Book, A Guide for Programmers, Second Edition", Prentice Hall, 2000,这也是一本好书,选题范围和本教程类似
8. Rajeev Nagar, "Windows NT File System Internals. A Developer's Guide", O'Reilly
9. Prasad Dabak, Sandeep Phadke, and Milind Borate, "Undocumented Windows NT", M&T Books, 1999 ,这本书里面包含了n多公开的文档中找不到的东西。
10. Gary Nebbett, " Windows NT-2000 Native API Reference", MacMillan Technical Publishing, 2000,这里也有一堆公开的文档中找不到的函数和数据结构的说明
11. Jeffrey Richter, "Programming Applications for Microsoft Windows. Fourth Edition", Microsoft Press, 1999,这本书和开发设备驱动程序没什么关系,但是也是一本很有趣的书。