理解和使用NT驱动程序的执行上下文(一)
翻译:李华谊 horily@163.com
理解Windows NT驱动程序最重要的概念之一就是驱动程序运行时所处的“执行上下文”。理解并小心地应用这个概念可以帮助你构建更快、更高效的驱动程序。
NT标准内核模式驱动程序编程中的一个重要观念是某个特定的驱动程序函数执行时所处的“上下文”。传统上文件系统开发者最关注这个问题,但所有类型的NT内核模式驱动程序的编写者都能从对执行上下文的深刻理解中获益。小心谨慎地使用执行上下文的概念能帮助构建更高性能、更低开销的驱动程序设计。
在本文中,我们将探寻执行上下文的概念。作为对概念的示范,本文在结尾描述了一个能让用户程序在内核模式下运行并拥有其中所有权限的驱动程序。在这个过程中,我们也将讨论设备驱动程序中执行上下文的实际用法。
什么是上下文?
当提及一个例程的上下文时,我们是指它的线程和进程的执行环境。在NT中,这个环境由当前的线程环境块(TEB)和进程环境块(PEB)建立。上下文因此包括虚拟内存的设置(告诉我们那个物理内存页面对应那个虚拟内存地址),句柄转换(因为句柄是基于进程的),分派器信息,堆栈,以及通用和浮点寄存器的设置。当我们问到一个特定的内核例程运行在那个上下文时,我们实际在问,“那一个是(NT内核)分派器建立的当前线程?”因为每一个线程只属于一个进程,当前线程确定了当前进程。当前线程和当前进程在一起确定了唯一标识线程和进程的所有事情(句柄、虚拟内存、调度器状态和寄存器)。
虚拟内存也许是上下文中对内核模式驱动程序编写者最有用的一个方面。还记得NT把用户进程映射到虚拟地址空间的低2GB,把操作系统自身的代码映射到虚拟地址空间的高2GB吗?当一个用户进程中的线程执行时,它的虚拟地址范围是0到2GB,2GB以上的所有地址均被设置为“no access”,以此防止用户直接访问操作系统代码和结构。当操作系统代码执行时,它的虚拟地址范围是2到4GB,而当前用户进程(如果有的话)的地址映射到0到2GB。在NT3.51和V4.0中,映射到高2GB地址的代码从不变化。然而,映射到低2GB地址的代码会变化,取决于当前进程是那一个。
此外,在NT特殊的虚拟内存排布策略中,进程P内一个合法的虚拟地址X(X小于等于2GB)和内核虚拟地址X对应于相同的物理内存位置。当然,这只有在进程P是当前进程并且(也因此)进程P的物理页面映射到操作系统的低2GB虚拟地址空间时才能成立。上面这句话的另一个说法就是,“这只在P是当前进程时才能成立。”所以在同一个进程上下文中,用户虚拟地址和2GB以上的内核虚拟地址指向相同的物理位置。
上下文中另一个让内核模式驱动程序编写者感兴趣的方面是线程调度上下文。当一个线程在等待时(例如通过发出Win32函数WaitForSingleObject(…)来等待一个没有被激发的对象),这个线程的调度上下文对象被用来存储关于线程定义所等待的对象的信息。当发出未满足的等待时,这个线程就从就绪队列中被移出,只有当等待被满足(指定的分派器对象被激发)时才被移回。
上下文也影响到句柄的使用。因为句柄是针对一个特定的进程的,在一个进程中创建的句柄在其他进程上下文中是没有用的。
不同类型的上下文
内核模式的例程运行在下面三种不同的上下文之一:
- 系统进程上下文
- 特定用户线程(和进程)上下文
- 任意用户线程(和进程)上下文
在执行过程中,每一个内核模式驱动程序的各部分可能运行在上面三种上下文之一。例如,一个驱动程序的DriverEntry(…)函数总是运行在系统进程的上下文中。系统进程上下文无用户上下文无关(因此没有TEB),并且也没有用户进程映射到内核虚拟地址空间的低2GB中。另一方面,DPCs(例如一个驱动程序为ISR服务的DPC或者定时器到期函数)运行在任意用户线程的上下文中。这意味者在一个DPC的执行过程中,任何用户线程都可以成为“当前”线程,因此任何用户进程都可以映射到内核虚拟地址空间的低2GB中。