19、Windows内存管理
1、虚拟地址
Windows的所有程序(ring0,ring3),可以操作的都是虚拟内存。CPU中寄存器CR0一个位PG位来告诉系统是否分页的。1为允许分页。DDK中宏PAGE_SIZE记录着分页大小,一般为4K,4GB的虚拟内存会被分割成1M个单元。
图 物理内存的映射 P120
2、两种模式
4G虚拟地址中,低2G为用户模式,高2G为内核模式。Windows规定用户态程序只能访问用户模式地址,而内核态程序可以访问整个4G虚拟地址。[1]
进程切换时,所有进程的内核地址映射完全一致,进程切换时改变,只是改变用户模式地址的映射。
图 两种模式 P121
Windwos驱动程序里的不同例程运行在不同的进程中。
打印当前进程的进程名:
void DisplayItsProcessName()
{
PEPROCESS pEProcess = PsGetCurrentProcess();
PTSTR = ProcessName = (PTSTR)((ULONG)pEProcess + 0x174);
KdPrint(("%s\n", ProcessName));
}
3、分页与非分页内存
可以交换到文件中的虚拟内存页面称为分页内存,否则称为非分页内存。当程序的中断请求级大于等于DISPATCH_LEVEL时,程序只能使用非分页内存。
#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")
将PAGEDCODE 等放在函数前来表现是可否可以分页等情况。PAGED_CODE()是DDK提供的宏,它只在check版本中生效,来检查运行是否低于DISPATCH_LEVEL的中断请求级,如果不低于则产生断言。
4、驱动程序不适合递归调用或者局部变量是大型结构体。如果需要大型结构体,请使用堆。
ExAllocatePool
ExAllocatePoolWithTag
ExAllocatePoolWithQuotaTag
ExAllocatePoolWithQuota
5、链表
双向链表有两个指针,BLINK指向前一个元素,FLINK指向下一个元素。
IsListEmpty
InitializeListHead
typedef struct _LIST_ENTRY {
struct _LIST_ENTRY *Flink;
struct _LIST_ENTRY *Blink;
} LIST_ENTRY, *PLIST_ENTRY;程序员需要自己定义链表中每个元素的数据类型,并将LIST_ENTRY结构作为自定义结构的一个子域。LIST_ENTRY的作用是将自定义的数据结构串与一个链表。
InsertHeadList
图 插入前链表状态
图 插入后
InsertTailList
RemoveHeadList
RemoveTailList
和插入链表有两种方法一样,删除链表也有两种方法,一种是从头部删除,另外一种是尾部删除。
不过有一个问题如下:
PLIST_ENTRY RemoveHeadList(
__inout PLIST_ENTRY ListHead
);
RemoveHeadList returns a pointer to the entry removed from the list. If the list is empty, RemoveHeadList returns ListHead.
也就是说这个函数返回的是一个指针,指向从链表中删除下来的元素中的LIST_ENTRY,那么如何得到用户自定义的数据结构的指针呢?;如果 LIST_ENTRY放在一个结构体的首部,那么返回的这个地址就等于用户自定义的数据结构的指针;而如果LIST_ENTRY不放在结构体的首部呢?这时,我们用地址偏移来实现。一个宏如下定义:
PCHAR CONTAINING_RECORD(
[in] PCHAR Address,
[in] TYPE Type,
[in] PCHAR Field
);
如下实现:
#define CONTAINING_RECORD(address, type, field) ((type *)( \
(PCHAR)(address) - \
(ULONG_PTR)(&((type *)0)->field)))
1 VOID LinkListTest()
2 {
3 LIST_ENTRY linkListHead;
4 //初始化链表
5 InitializeListHead(&linkListHead);
6 PMYDATASTRUCT pData;
7 ULONG i = 0;
8 //在链表中插入10个元素
9 KdPrint(("Begin insert to link list"));
10 for (i=0 ; i<10 ; i++)
11 {
12 pData = (PMYDATASTRUCT)
13 ExAllocatePool(PagedPool,sizeof(MYDATASTRUCT));
14 pData->number = i;
15 InsertHeadList(&linkListHead,&pData->ListEntry);
16 }
17
18 //从链表中取出,并显示
19 KdPrint(("Begin remove from link list\n"));
20 while(!IsListEmpty(&linkListHead))
21 {
22 PLIST_ENTRY pEntry = RemoveTailList(&linkListHead);
23 pData = CONTAINING_RECORD(pEntry,
24 MYDATASTRUCT,
25 ListEntry);
26 KdPrint(("%d\n",pData->number));
27 ExFreePool(pData);
28 }
29 }
4、Lookaside结构
如果驱动程序频繁地从内存中申请回收固定大小的内存,可以使用Lookaside对象。可以将Lookaside对象想象成一个自动的内存分配容器。避免产生内存空洞。
1)初始化:
ExInitializeNPagedLookasideList
ExInitializePagedLookasideList
2)初始完内存后,可以申请内存了:
ExAllocateFromNPagedLookasideList
ExAllocateFromPagedLookasideList
3)回收内存
ExFreeToNPagedLookasideList
ExFreeToPagedLookasideList
4)删除Lookaside对象
ExDeleteNPagedLookasideList
ExDeletePagedLookasideList
一个例子如下:
1 VOID LookasideTest()
2 {
3 //初始化Lookaside对象
4 PAGED_LOOKASIDE_LIST pageList;
5 ExInitializePagedLookasideList(&pageList,NULL,NULL,0,sizeof(MYDATASTRUCT),'1234',0);
6 #define ARRAY_NUMBER 50
7 PMYDATASTRUCT MyObjectArray[ARRAY_NUMBER];
8 //模拟频繁申请内存
9 for (int i=0;i<ARRAY_NUMBER;i++)
10 {
11 MyObjectArray[i] = (PMYDATASTRUCT)ExAllocateFromPagedLookasideList(&pageList);
12 }
13 //模拟频繁回收内存
14 for (i=0;i<ARRAY_NUMBER;i++)
15 {
16 ExFreeToPagedLookasideList(&pageList,MyObjectArray[i]);
17 MyObjectArray[i] = NULL;
18 }
19 ExDeletePagedLookasideList(&pageList);
20 //删除Lookaside对象
21 }
5、运行时函数
由编译器提供,不同操作系统实现不同,但是接口一样,如malloc。
1)内存间复制(非重叠)
RtlCopyMemory
2)可重叠复制
RtlMoveMemory
3)填充内存
RtlFillMemory
RtlZeroMemory
3)内存比较
RtlCompareMemory
DDK提供的运行时函数都是RtlXX形式。
6、使用C++特性分配内存
不能直接使用new和delete。因为MS编译器没有提供内核模式下的new操作符,我们可以对其进行重载来使用。
重载有两种方法,一种是类中重载,一种全局重载。
1 //全局new操作符
2 void * __cdecl operator new(size_t size,POOL_TYPE PoolType=PagedPool)
3 {
4 KdPrint(("global operator new\n"));
5 KdPrint(("Allocate size :%d\n",size));
6 return ExAllocatePool(PagedPool,size);
7 }
8 //全局delete操作符
9 void __cdecl operator delete(void* pointer)
10 {
11 KdPrint(("Global delete operator\n"));
12 ExFreePool(pointer);
13 }
14
15 class TestClass
16 {
17 public:
18 //构造函数
19 TestClass()
20 {
21 KdPrint(("TestClass::TestClass()\n"));
22 }
23
24 //析构函数
25 ~TestClass()
26 {
27 KdPrint(("TestClass::~TestClass()\n"));
28 }
29
30 //类中的new操作符
31 void* operator new(size_t size,POOL_TYPE PoolType=PagedPool)
32 {
33 KdPrint(("TestClass::new\n"));
34 KdPrint(("Allocate size :%d\n",size));
35 return ExAllocatePool(PoolType,size);
36 }
37
38 //类中的delete操作符
39 void operator delete(void* pointer)
40 {
41 KdPrint(("TestClass::delete\n"));
42 ExFreePool(pointer);
43 }
44 private:
45 char buffer[1024];
46 };
47
49 void TestNewOperator()
50 {
51 TestClass* pTestClass = new TestClass;
52 delete pTestClass;
53
54 pTestClass = new(NonPagedPool) TestClass;
55 delete pTestClass;
56
57 char *pBuffer = new(PagedPool) char[100];
58 delete []pBuffer;
59
60 pBuffer = new(NonPagedPool) char[100];
61 delete []pBuffer;
62 }
7、其它
1)NTSTATUS含义
图 NTSTATUS含义 P141
2)检查内存可用性
ProbeForRead
ProbeForWrite
如果不满足条件时将是引发异常。用异常处理机制进行捕获。
3)结构化异常处理
异常概念类似于中断
A)try-except块
try-except-statement :
__try
{
compound-statement
}
__except ( expression )
{
compound-statement
}
EXCEPTION_CONTINUE_EXECUTION
EXCEPTION_CONTINUE_SEARCH
EXCEPTION_EXECUTE_HANDLER
例子如下:
1 #pragma INITCODE
2 VOID ProbeTest()
3 {
4 PVOID badPointer = NULL;
5 KdPrint(("Enter ProbeTest\n"));
6 __try
7 {
8 KdPrint(("Enter __try block\n"));
9 //判断空指针是否可读,显然会导致异常
10 ProbeForWrite(badPointer,100,4);
11 //由于在上面引发异常,所以以后语句不会被执行!
12 KdPrint(("Leave __try block\n"));
13 }
14
15 __except(EXCEPTION_EXECUTE_HANDLER)
16 {
17 KdPrint(("Catch the exception\n"));
18 KdPrint(("The program will keep going\n"));
19 }
20 //该语句会被执行
21 KdPrint(("Leave ProbeTest\n"));
22 }
其它引发异常函数:
ExRaiseAccessViolation
ExRaiseDatatypeMisalignment
ExRaiseStatus
B)try-finally 块
try-finally-statement :
__try compound-statement
__finally compound-statement
强迫函数有退出前执行一段代码。常用来一些资源的回收工作。
1 #pragma INITCODE
2 NTSTATUS TryFinallyTest()
3 {
4 NTSTATUS status = STATUS_SUCCESS;
5 __try
6 {
7 //做一些事情
8 return STATUS_SUCCESS;
9 }
10 __finally
11 {
12 KdPrint(("Enter finallly block\n"));
13 return status;
14 }
15 }
4)防止“侧效”错误(也就是多行宏在if等语句中表达的意思出现了不同),在if,while,for等语句中,无论是否只有一句话,都不能省略{}。
ASSERT断言
参考:
【1】 windows驱动开发详解