Chinaunix首页 | 论坛 | 博客
  • 博客访问: 518883
  • 博文数量: 238
  • 博客积分: 10208
  • 博客等级: 上将
  • 技术积分: 2820
  • 用 户 组: 普通用户
  • 注册时间: 2008-03-12 09:27
文章分类

全部博文(238)

文章存档

2010年(50)

2009年(66)

2008年(122)

我的朋友

分类: WINDOWS

2009-10-21 11:42:50

在windows ddk(DDK是Device Development Kit,设备开发包的。指windows驱动开发包。如果你想开发一个设备程序,如显卡驱动程序,就必须使用DDK)中提供了一个经典的宏,其定义如下:
#define CONTAININT_RECORD(address, type, field) \
             ((type*)((PCHAR)(address) - (PCHAR)(&((type*)0)->field)))
这个宏用于取得内存中任何结构体的首地址,要提供的参数是:结构体中某个成员(field)的地址address、结构体的类型type、提供地址那个成员的名字field。

这个宏基本上可以执行在任何的IRQL,但是当所引用的内存页有可能产生 『page fault』時,就仅能执行在『APC_LEVEL』之下。若要知道现在的IRQL,可以使用『KeGetCurrentIrql』这一个Service 来取得。当然也可以使用 NonPage Pool来防止内存页产生Page Fault。

IRQL是,中断请求级别。它是一个由windows出来的概念,划分在下中断的,这里中断包括了硬中断和软中断,硬中断是由硬件产生,而软中断则是完全虚拟出来的。

在任一时间中,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)

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


阅读(770) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~