1.PC上有三条总线,分别是数据总线、地址总线和控制总线。32位的CPU寻址能力为4GB(2^32)个字节。用户最多可以使用4GB的真实物理内存。PC中会拥有很多设备,其中很多设备提供了自己的设备内存。例如,显卡就会提供自己的显存。这部分内存会映射到PC的物理内存上,也就是读写这段物理地址,其实会读写设备的内存地址,而不会读写物理内存地址。
2.在CPU中有一个重要的寄存器CR0,它是32位的寄存器,其中的一个位(PG位)是负责告诉系统是否分页的。Windows在启动前会将它的PG位置1,即Windows允许分页。DDK中有个宏PAGE_SIZE记录着分页的大小,一般为4KB。
3.虚拟内存中第N个分页单元对应着物理内存第M个分页单元。这种对应不是一一对应,而是多对一的映射,多个虚拟内存页可以映射同一个物理内存页。还有一部分单元会被映射成磁盘上的文件,并标记为脏的。读取这段虚拟内存的时候,系统会发出一个异常,此时会触发异常处理函数。异常处理函数会将这个页的磁盘文件读入内存,并将标记设置为不脏。当让经常不读写的内存页,可以交换成文件,并将此页设置为脏。还有一部分单元什么也没有对应,即空的。
4.Windows这样设计的原因:
①:虚拟的增加了内存的大小。不管PC是否有足够的4GB的物理内存,操作系统总会有4GB的虚拟内存。这就允许使用者申请更多的内存,当物理内存不够的时候,可以通过将不常用的虚拟内存页交换成文件,等需要的时候再去读取。
②:是不同进程的虚拟内存互补干扰,为了让系统可以同时运行不同的进程,Windows操作系统让每个进程看到的虚拟内存都不同。这个方法使不同的进程会有不同的物理内存到虚拟内存的映射。
5.Windows驱动程序里的不同例程运行在不同的进程中。DriverEntry例程和AddDevice例程是运行在系统(System)进程中的。而其中一些例程,例如:IRP_MJ_READ和IRP_MJ_WRITE的派遣函数会运行于应用程序的上下文中,也就是运行在某个进程的环境中,所能访问的虚拟地址是这个进程的虚拟地址。
6.Windows规定有些虚拟内存页面是可以交换到文件中的,这类内存被称为分页内存,而有些虚拟内存页永远不会交换到文件中,这些内存被称为非分页内存。
7.当程序的中断请求级在DISPATCH_LEVEL之上时(包括DISPATCH_LEVEL),程序只能使用非分页内存,否则将导致蓝屏死机。
8.Lookaside结构:
频繁申请和回收内存,会导致在内存上产生大量的内存“空洞”,从而导致最终无法申请内存。DDK为程序猿提供了Lookaside结构来解决这个问题。可以将Lookaside结构想象成一个内存容器。在初始的时候,它先向Windows申请了比较大的内存。以后程序猿每次申请内存的时候,不是直接向Windows申请内存,而是向Lookaside对象申请内存。
初始化Lookaside:
ExInitializeNPagedLookasideList()
ExInitializePagedLookasideList()
申请内存:
ExInitializeNPagedLookasideList()
ExInitializePagedLookasideList()
回收内存:
ExFreeToNPagedLookasideList()
ExFreeToPagedLookasideList()
删除Lookaside对象:
ExDeleteNPagedLookasideList()
ExDeletePagedLookasideList()
实例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | #pragma INITCODE VOID LookasideTest() { //初始化Lookaside对象 PAGED_LOOKASIDE_LIST pageList; ExInitializePagedLookasideList(&pageList,NULL,NULL,0,sizeof(MYDATASTRUCT),'1234',0); #define ARRAY_NUMBER 50 PMYDATASTRUCT MyObjectArray[ARRAY_NUMBER]; //模拟频繁申请内存 for (int i=0;i<ARRAY_NUMBER;i++) { MyObjectArray[i] = (PMYDATASTRUCT)ExAllocateFromPagedLookasideList(&pageList); } //模拟频繁回收内存 for (i=0;i<ARRAY_NUMBER;i++) { ExFreeToPagedLookasideList(&pageList,MyObjectArray[i]); MyObjectArray[i] = NULL; } ExDeletePagedLookasideList(&pageList); //删除Lookaside对象 } |
9.内存间复制:
RtlCopyMemory() 用这个可以复制内存,但内部没有考虑内存重叠的情况。如下:有三段等长的内存,ABCD分别代表三段内存的起始地址和终止地址。如果需要将A到C段的内存复制到B到D这段内存上,这时B到C段的内存就是重叠的部分。
而RtlCopyMemory没有考虑重叠的部分,它不能保证重叠部分是否被复制。memcpy
而memmove这个函数对两个内存是否重叠进行了判断,这种判断却牺牲了速度。如果你能确保复制的部分没有重叠,请选择使用memcpy函数。否则使用memmove函数。DDK提供对其进行了封装,RtlMoveMemory。
10.填充内存:RtlFillMemory
内存清零:RtlZeroMemory
内存比较:RtlCompareMemory/RtlEqualMemory
实例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | #pragma INITCODE VOID RtlTest() { PUCHAR pBuffer = (PUCHAR)ExAllocatePool(PagedPool,BUFFER_SIZE); //用零填充内存 RtlZeroMemory(pBuffer,BUFFER_SIZE); PUCHAR pBuffer2 = (PUCHAR)ExAllocatePool(PagedPool,BUFFER_SIZE); //用固定字节填充内存 RtlFillMemory(pBuffer2,BUFFER_SIZE,0xAA); //内存拷贝 RtlCopyMemory(pBuffer,pBuffer2,BUFFER_SIZE); //判断内存是否一致 ULONG ulRet = RtlCompareMemory(pBuffer,pBuffer2,BUFFER_SIZE); if (ulRet==BUFFER_SIZE) { KdPrint(("The two blocks are same.\n")); } } |
11.重载new和delete操作符
实例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 | //全局new操作符 void * __cdecl operator new(size_t size,POOL_TYPE PoolType=PagedPool) { KdPrint(("global operator new\n")); KdPrint(("Allocate size :%d\n",size)); return ExAllocatePool(PagedPool,size); } //全局delete操作符 void __cdecl operator delete(void* pointer) { KdPrint(("Global delete operator\n")); ExFreePool(pointer); } class TestClass { public: //构造函数 TestClass() { KdPrint(("TestClass::TestClass()\n")); } //析构函数 ~TestClass() { KdPrint(("TestClass::~TestClass()\n")); } //类中的new操作符 void* operator new(size_t size,POOL_TYPE PoolType=PagedPool) { KdPrint(("TestClass::new\n")); KdPrint(("Allocate size :%d\n",size)); return ExAllocatePool(PoolType,size); } //类中的delete操作符 void operator delete(void* pointer) { KdPrint(("TestClass::delete\n")); ExFreePool(pointer); } private: char buffer[1024]; }; void TestNewOperator() { TestClass* pTestClass = new TestClass; delete pTestClass; pTestClass = new(NonPagedPool) TestClass; delete pTestClass; char *pBuffer = new(PagedPool) char[100]; delete []pBuffer; pBuffer = new(NonPagedPool) char[100]; delete []pBuffer; } |
12.检查内存可用性
ProbeForRead/ProbeForWrite
这两个函数不是返回该段内存是否可读写,而是当不可读写的时候,引发一个异常。
13.结构化异常处理
__try
{}
__except(filter_value)
{}
filter_value:
①:EXCEPTION_EXECUTE_HANDLE,该数值为1,进入到__except进行错误处理,处理完后不再回到__try{}块中,转而继续执行。
②:EXCEPTION_CONTINUE_SEARCH,该数值为0,不使用__except中的异常处理,转而向上一层回传,如果已经是最外层,则向操作系统请求异常处理函数。
③:EXCEPTION_CONTINUE_EXECUTION,该数值为-1,重复先前错误的指令,很少用到。
实例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | #pragma INITCODE VOID ProbeTest() { PVOID badPointer = NULL; KdPrint(("Enter ProbeTest\n")); __try { KdPrint(("Enter __try block\n")); //判断空指针是否可读,显然会导致异常 ProbeForWrite(badPointer,100,4); //由于在上面引发异常,所以以后语句不会被执行! KdPrint(("Leave __try block\n")); } __except(EXCEPTION_EXECUTE_HANDLER) { KdPrint(("Catch the exception\n")); KdPrint(("The program will keep going\n")); } //该语句会被执行 KdPrint(("Leave ProbeTest\n")); } |
__try
{}
__finally{} //无论try中运行什么代码即使是return或异常,在程序退出前都会运行finally中的语句。
14.ASSERT断言。