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