电脑中的物理内存是有限的,但是应用程序和操作系统不需要在任何时间都把所有的代码和资源文件都载入到物理内存中,只需要在需要的时候这些代码和资源出现在物理内存中,于是就有了分页文件和缺页中断(page fault),操作系统把物理内存和一些磁盘文件划分为4K大小的地址空间块,这些地址空间块通过操作系统的内存管理组件映射到每个进程的虚拟地址空间,当进程访问某个虚拟内存地址时,操作系统首先在cache中查找这个地址所在的页,如果该页面不在内存中,则产生一个缺页中断(page fault),进程被阻塞,直到要访问的页面从外部存储器复制到内存。
我们知道, 在处理低优先级的中断时,仍可以发生高优先级的中断。这时就出现了一个问题,如果在缺页中断中触发了高优先级中断,而高优先级中断的代码或数据在外部存储器中,这时就会发生递归调用,然后内核崩溃。所以在DISPATCH_LEVEL级别以上(包括DISPATCH_LEVEL层),不能使用分页内存,否则可能内核崩溃。
默认情况下,内核加载器会加载所有的代码部分和全局数据到非分页内存中,而且,加载器是一次加载整个驱动的可执行文件,包括相关的DLL,加载后,内核加载器关闭驱动程序文件,你甚至可以删除当前正在执行的驱动文件。同时,你也可以告诉加载器你希望驱动的那部分是可分页的,哪部分是非分页的。需要做如下定义:
#define PAGEDCODE code_seg("PAGE")
#define LOCKEDCODE code_seg()
#define INITCODE code_seg("INIT")
#define PAGEDDATA data_seg("PAGE")
#define LOCKEDDATA data_seg()
#define INITDATA data_seg("INIT")
//如果将某个函数载入到分页内存中,我们需要在函数的实现中加入以下代码:
#pragma PAGEDCODE
VOID SomeFunction()
{
PAGED_CODE();
//其中,PAGED_CODE是DDK提供的宏,它只在check版本中生效,它会检查这个函数是否运行低于DISPATCH_LEVEL的中断请求级,如果
//等于或高于这个中断请求级,将产生一个断言。
}
#if DBG
#define PAGED_CODE()
{ if (KeGetCurrentIrql() > APC_LEVEL) {
KdPrint(( "EX: Pageable code called at IRQL %d
", KeGetCurrentIrql() ));
ASSERT(FALSE);
}
}
#else
#define PAGED_CODE() NOP_FUNCTION;
#endif
//如果让函数加载到非分页内存中,需要在函数的实现加如下代码:
#pragma LOCKEDCODE
VOID SomeFunction()
{
// 做一些其他事情
}
//还有一种特殊情况,就是某个例程需要在初始化的时候载入内存,然后就可以从内存中卸载掉。这种情况指出现在DriverEntry情况下;
//尤其是NT式的驱动,DriverEntry会很长,占据很大的空间,为了节省内存,需要及时从内存中卸载掉。代码如下:
#pragma INITCODE
extern "C" NTSTATUS DriverEntry (
IN PDRIVER_OBJECT pDriverObject,
IN PUNICODE_STRING pRegistryPath )
{
// 做一些事情
}
注意:一个段定义后的所有变量和函数都受此段定义的影响,影响范围直到下一个段定义生效为止。
没有指定是分页的或非分页的,则认为应用编译器默认配置,视作设定为非分页内存。
默认配置可以用以下宏设定。
#define LOCKEDCODE code_seg()
#define LOCKEDDATA data_seg()