以下内容全部来自《Windows驱动开发技术详解》,作者张帆、史彩成等,属摘抄型笔记。
///
1.频繁的申请、回收内存会导致在内存上产生大量的内存“空洞”,以致于其他申请内存失败。
2.Lookaside对象作为内存池,先向Windows申请一块较大的内存,之后程序每次申请内存的时候,不直接向Windows申请内存,而是向Lookaside对象申请内存。
3.Lookaside对象内部的内存不够用时,会向操作系统申请更多内存,当Lookaside对象内部有大量的未使用的内存时,它会自动让Windows回收一部分内存。
4.Lookaside适用于以下情况:程序员每次申请固定大小的内存,申请和回收的操作十分频繁。
5.首先需要初始化Lookaside对象,以下函数分别对非分页和分页Lookaside对象进行初始化。
下面两个函数分别是对非分页和分页Lookaside对象进行初始化。
NTKERNELAPI VOID ExInitializeNPagedLookasideList(IN PNPAGED_LOOKASIDE_LIST Lookaside,
IN PALLOCATE_FUNCTION Allocate,
IN PFREE_FUNCTION Free,
IN ULONG Flags,
IN SIZE_T Size,
IN ULONG Tag,
IN USHORT Depth);
NTKERNELAPI VOID ExInitializePagedLookasideList(IN PPAGED_LOOKASIDE_LIST Lookaside,
IN PALLOCATE_FUNCTION Allocate,
IN PFREE_FUNCTION Free,
IN ULONG Flags,
IN SIZE_T Size,
IN ULONG Tag,
IN USHORT Depth);
初始化完Lookaside对象后,可以进行申请内存的操作,下面两个函数分别对非分页内存和分页内存申请。
__inline PVOID ExAllocateFromNPagedLookasideList(IN PNPAGED_LOOKASIDE_LIST Lookaside);
NTKERNELAPI PVOID ExAllocateFromPagedLookasideList(IN PPAGED_LOOKASIDE_LIST Lookaside);
使用如下函数进行内存回收。
NTKERNELAPI VOID ExFreeToPagedLookasideList(IN PPAGED_LOOKASIDE_LIST Lookaside,
IN PVOID Entry);
__inline VOID ExFreeToPagedLookasideList(IN PPAGED_LOOKASIDE_LIST Lookaside,
IN PVOID Entry);
最后不使用该对象后,要进行内存释放,分别使用如下函数。
NTKERNELAPI VOID ExDeleteNPagedLookasideList(IN PNPAGED_LOOKASIDE_LIST Lookaside);
NTKERNELAPI VOID ExDeletePagedLookasideList(IN PPAGED_LOOKASIDE_LIST Lookaside);
PS:如果后续频繁使用Lookaside对象,会对inline的内容再次进行学习的,现在就定性的了解下,有需要再说了。
6.运行时函数,这个地方还是对比应用程序的运行时函数,在需要的时候Google吧,原理性的东西不多就不一一列举了。
7.返回值
NTSTATUS 是一个32位整数,每位有不同的含义,具体参考NTDDK说明。
8.内存可用性
在驱动程序中,如果某段内存是只读的,而驱动程序试图去进行写操作,会导致系统的崩溃。DDK提供函数,检测内段可读写特性。
NTKERNELAPI VOID NTAPI ProbeForRead(IN CONST VOID *Address,
IN SIZE_T Length,
IN ULONG Alignment);
NTKERNELAPI VOID NTAPI ProbeForWrite(IN PVOID Address,
IN SIZE_T Length,
IN ULONG Alignment);
三个参数分别为:内存地址,被检测内存的长度,该段内存的对齐方式。
但是该函数没有返回值,对于这个函数的使用,还需要参考机构化异常处理。
9.结构化异常处理(try-except块)
机构化异常处理(Structured Exception Handling,SEH)是微软编译器提供的独特处理机制,这种处理方式能在一定程度上在出现错误的情况下,避免程序崩溃。
a.异常:有点类似中断的概念,当程序中某种错误触发一个异常,操作系统会寻找处理这个异常的处理函数,如果程序提供错误处理函数,则进入错误处理函数;如果没有提供处理函数,则由操作系统的默认错误处理函数处理。而在内核中,错误处理往往很简单,直接BSOD。
b.回卷:程序执行到摸个地方出现异常错误时,系统会寻找出错点是否处于一个try()块中,并进入try块提供的异常处理代码,如果当前try块没有提供异常处理,则会像更外一层的try块,寻找异常处理函数。直到最外层try()块也没有提供异常处理代码,则交由操作系统处理。(这种向外层寻找异常处理的机制,被称为回卷)
__try
{
}
__except(filter_value)
{
}
在try块包围的代码块中如果出现异常,会根据filter_value的数值,判断是否需要在__except中处理。
有如下三种可能:
#define EXCEPTION_EXECUTE_HANDLER 1
#define EXCEPTION_CONTINUE_SEARCH 0
#define EXCEPTION_CONTINUE_EXECUTION -1
1).EXCEPTION_EXECUTE_HANDLER,进入到__except块中处理,处理完成后不再转入try中,继续向下执行。
2).EXCEPTION_CONTINUE_SEARCH,不使用__except块中处理,直接向外层回卷,如果在最外层,直接交由操作系统进行处理。
3).EXCEPTION_CONTINUE_EXECUTION,此在驱动中很少见,重复先前错误的指令。
#include <ntddk.h>
VOID ProbeTest()
{
PVOID badPointer = NULL;
KdPrint(("Enter ProbeTest\n"));
__try
{
KdPrint(("Enter __try block\n"));
//判断空指针是否可读,触发异常
ProbeForWrite(badPointer, 0x100, 4);
//由于触发异常,后续代码无法被执行
KdPrint(("Leave __try block\n"));
}
__except(EXCEPTION_EXECUTE_HANDLER)
{
KdPrint(("CASE:EXCEPTION_EXECUTE_HANDLER\n"));
}
}
VOID DriverUnload(PDRIVER_OBJECT pDriverObject)
{
KdPrint(("DriverUnload"));
}
NTSTATUS DriverEntry(PDRIVER_OBJECT pDriverObject, PUNICODE_STRING pRegPath)
{
KdPrint(("DriverEntey\n"));
pDriverObject->DriverUnload = DriverUnload;
ProbeTest();
return STATUS_SUCCESS;
}
除了读写内存外,还可以主动出发异常,例如ExRaiseStatus函数,可以指定状态码触发异常。
10.结构化异常处理(try-finally块)
结构化异常处理还有另外一种使用方法,使用try-finally块,强迫函数在退出前执行一段代码。
NTSTATUS TryFinallyTest()
{
NTSTATUS ns = STATUS_SUCCESS;
__try
{
return STATUS_SUCCESS;
}
__finally
{
KdPrint(("Enter Finally block\n"));
}
}
对于finally块,触发异常或者退出时都会执行,较适合用做资源回收。