中断请求级(摘自 windows内核情景分析)

Windows的内核还有个明显的不同,就是其内核的相当一部分页面是可倒换的。内存管理即虚存技术的重要内容之一是物理页面的倒换,就是可以将已经有映射但是暂时不受到访问的页面倒换到外存中去,到实际受到访问时再倒换进来,使外存成为内存的扩充。但是,在Linux中,属于内核的页面无论是用于代码还是数据(用于文件内容缓存的除外),是不受倒换的。这一方面是为了简化内核的设计和实现,一方面也是因为觉得价值不大,因为内核毕竟是全局的,所有进程都公用同一个系统空间,即使要倒换也油水不大。但是,Windows的内核却不同,其相当一部分页面是可倒换的。究其原因,另一方面可能是来自VMS的影响,另一方面也可以从Windows内核的体积得到一些解释。如前所述,微软把图形操作/视窗服务的实现也搬到了内核中,这就使内核的体积增加了很多。再说每个线程的系统空间堆栈也因此而增大了许多,当线程数量很大时也确实不可忽视。所以,在这样的情况下,使内核的部分页面成为可倒换的确实有其合理的一面。不过当然不是所有页面都可倒换,有些页面注定是不可倒换的。例如,与中断和异常有关的代码和数据所在的页面,以及与页面倒换有关的代码和数据所在的页面,就显然是不能被倒换出去的,否则就无法把这些页面倒换回来了。这样,我们就大致可以推断,Windows内核中“内核”层及其以下应该是不允许倒换的。

       与此相关,Windows为CPU的运行状态定义了许多“IRQ级别”,即IRQL。在任一时间中,CPU总是运行于其中的某一个级别,这个级别就表明了什么事情可以做、什么事情不可以做。下面是这些级别的定义:

#define PASSIVE_LEVEL                          0

#define LOW_LEVEL                                0

#define APC_LEVEL                                 1

#define DISPATCH_LEVEL                        2

#define PROFILE_LEVEL                         27

#define CLOCK1_LEVEL                          28

#define CLOCK2_LEVEL                          28

#define IPI_LEVEL                                    29

#define POWER_LEVEL                           30

#define HIGH_LEVEL                                31

其基本的意图是,如果CPU从而一个线程已经处于某个级别,其操作就不能受同级或更低级别的操作所干扰。

       这里的PASSIVE_LEVEL是级别最低的,但是却对应着系统结构中较高的层次。当CPU运行于用户空间,或者虽然进入了内核但还只是运行于管理层的时候,其运行级别就是PASSIVE_LEVEL。比其略高的是APC_LEVEL,那是在(内核中)为APC函数(见本书“进程与线程”一章)的执行进行准备时的运行级别,APC请求相当于对用户空间程序的(软件)中断。注意IRQL在x86系统结构中并没有硬件的支持(CPU中并没有这么一个寄存器)而只是一个变量。与CPU只能通过特殊的指令或中断/异常才能进入系统态不同,IRQL是CPU可以自由设置的,每当CPU进入更底层、更核心的层次时就提高IRQL,反之则降低IRQL。不过,表明IRQL的变量在内核中,运行于用户空间时是无法改变IRQL的。

       再高一级是DISPATCH_LEVEL,这大致相当于CPU运行于Windows内核中的核心层,即“内核”层。线程的切换只能发生于CPU行将从DISPATCH_LEVEL级别下降的时候。

       IRQL级别3及以上用于硬件中断。显然,设计者的意图是采用中断优先级,即优先级较高的中断源可以中断优先级较低的中断服务。但是x86的系统结构并不支持中断优先级,所以这实际上是来自VMS的遗迹,因为VAX和PDP的系统结构都是支持中断优先级的。

       回到页面换出的问题上,只要CPU的IRQL级别不高于APC_LEVEL的层次,其代码都是允许倒换的,但是从DISPATCH_LEVEL开始就不允许了。显然,如果在这一点上搞错了,后果是很严重的。所以在管理层的代码中几乎每个函数的开头都要放上一个宏操作PAGED_CODE(),说明代码作者的意图是让这个函数所占的页面可以被倒换出去。这个宏操作的定义如下:

#ifdef DBG

#define PAGED_CODE() { /

    if (KeGetCurrentIrql() > APC_LEVEL) { /

    KdPrint( ("NTDDK: Pageable code called at IRQL > APC_LEVEL (%d)/n",KeGetCurrentIrql() )); /

    ASSERT(FALSE); /

  } /

}

#else

#define PAGED_CODE()

#endif

       在Debug模式下,这个宏操作检查CPU当前的运行级别,如果发现高于APC_LEVEL就说明这个函数有可能在DISPATCH_LEVEL或更高的级别上受到调用,因而是不应该被倒换出去的,所以就发出警告。至于在正式运行的版本中,则这个宏操作定义为空。

       当然,光是在程序中引用宏操作PAGED_CODE()不会使一个函数所在的页面可倒换,真正使其可倒换的是编译指示“#pragma alloc_text()”。例如NtQueryObject()中的第一行就是PAGED_CODE(),与此相应,这个函数所在的源文件中就有这么一行:

#pragma alloc_text(PAGE, NtQueryObject)

      正是这一行编译指示让编译工具将为此函数生成的可执行代码放在可被倒换的区间。

posted @ 2012-07-26 17:55  友学友  阅读(1241)  评论(0编辑  收藏  举报