19、Windows内存管理

1、虚拟地址

Windows的所有程序(ring0,ring3),可以操作的都是虚拟内存。CPU中寄存器CR0一个位PG位来告诉系统是否分页的。1为允许分页。DDK中宏PAGE_SIZE记录着分页大小,一般为4K4GB的虚拟内存会被分割成1M个单元。

wps_clip_image-3412

图 物理内存的映射 P120

2、两种模式

4G虚拟地址中,低2G为用户模式,高2G为内核模式。Windows规定用户态程序只能访问用户模式地址,而内核态程序可以访问整个4G虚拟地址。[1]

进程切换时,所有进程的内核地址映射完全一致,进程切换时改变,只是改变用户模式地址的映射。

wps_clip_image-9322

图 两种模式 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指向下一个元素。

wps_clip_image-7593

IsListEmpty

InitializeListHead

typedef struct _LIST_ENTRY {

struct _LIST_ENTRY *Flink;

struct _LIST_ENTRY *Blink;

} LIST_ENTRY, *PLIST_ENTRY;程序员需要自己定义链表中每个元素的数据类型,并将LIST_ENTRY结构作为自定义结构的一个子域。LIST_ENTRY的作用是将自定义的数据结构串与一个链表。

InsertHeadList

wps_clip_image-23641

图 插入前链表状态

wps_clip_image-12080

图 插入后

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 }

4Lookaside结构

如果驱动程序频繁地从内存中申请回收固定大小的内存,可以使用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++特性分配内存

不能直接使用newdelete。因为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、其它

1NTSTATUS含义

ScreenShot003

NTSTATUS含义 P141

2)检查内存可用性

ProbeForRead

ProbeForWrite

如果不满足条件时将是引发异常。用异常处理机制进行捕获。

3)结构化异常处理

异常概念类似于中断

Atry-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

Btry-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驱动开发详解

原文地址:https://www.cnblogs.com/mydomain/p/1863027.html