浅谈Windows内存管理以及链表使用
2015-04-12 16:51
162 查看
/*
author:少仲
blog: http://blog.csdn.net/py_panyu
weibo: http://weibo.com/u/3849478598
欢迎转载,转载请注明出处.
*/
驱动程序和应用程序一样,局部变量是存放在栈空间中的.但是栈的空间不会像应用程序一样大,所以驱动程序不适合用递归调用或者局部变量是大型结构体.如果需要大型数据结构,就要在堆中申请内存.
以下是几个在堆中申请内存的函数:
返回值是分配的内存地址,一定是内核模式地址.如果返回0,则代表分配失败
Tag 是一个4字节标记,便于查找
下面是回收的函数:
P就是要释放的地址
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Lookaside结构
频繁的申请和回收内存,会导致在内存上产生大量的内存“空洞”,从而导致最终无法申请内存.DDK为程序员提供了Lookaside结构来解决这个问题.
我们可以将Lookaside对象看成是一个内存容器.在初始化的时候,它先向Windows申请了一块比较大的内存.以后程序员每次申请内存的时候,不是直接向Windows申请内存,而是想Lookaside对象申请内存.Looaside会智能的避免产生内存“空洞”.如果Lookaside对象内部内存不够用时,它会向操作系统申请更多的内存.
Lookaside一般会在以下情况下使用:
1.程序员每次申请固定大小的内存.
2.申请和回收的操作十分频繁.
初始化Lookaside对象,有两个函数
下面是申请内存的操作:
下面是Lookaside对象回收内存的操作:
下面是删除Lookaside对象的操作:
测试函数:
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
在驱动中使用链表
DDK提供了标准的双向链表.双向链表可以将链表形成一个环.BLINK指针指向前一个元素.FLINK指针指向下一个元素.
链表初始化
每个双向链表都是以一个链表头作为链表的第一个元素.初次使用链表头需要进行初始化.主要是将链表头的FLINK和BLINK两个指针指向自己.表示链表头所代表的链是空链.
链表头初始化函数如下:
上面定义的链表头只有前后指针而没有数据元素.因此我们需要自己定义链表中每个元素的数据类型.并将LIST_ENTRY结构作为自定义结构的一个子域.LIST_ENTRY的作用就是将自定义数据结构串成一个链表.
例如:
元素插入链表
从链表头部插入:
从链表尾部插入:
(1)自定义数据结构的第一个字段就是LIST_ENTRY结构,如下:
(2)自定义数据结构的第一个字段就不是LIST_ENTRY结构,如下:
要得到自定义的数据结构可以如下:
关于这个宏
当知道了某个struct内的某个field 的address, 利用其在结构体中的偏移, 用address减去偏移值,即可反推出这个大的struct 的instance address (结构地址)
这里主要是为了获取PLDR_DATA_TABLE_ENTRY的地址,对这个宏还有疑问的同学
(推荐http://www.cnblogs.com/nbsofer/archive/2013/01/07/2849913.html,这篇写的很详细)
测试代码:
author:少仲
blog: http://blog.csdn.net/py_panyu
weibo: http://weibo.com/u/3849478598
欢迎转载,转载请注明出处.
*/
驱动程序和应用程序一样,局部变量是存放在栈空间中的.但是栈的空间不会像应用程序一样大,所以驱动程序不适合用递归调用或者局部变量是大型结构体.如果需要大型数据结构,就要在堆中申请内存.
以下是几个在堆中申请内存的函数:
PVOID ExAllocatePool( IN POOL_TYPE PoolType, IN SIZE_T NumberOfBytes );
PVOID ExAllocatePoolWithTag( IN POOL_TYPE PoolType, IN SIZE_T NumberOfBytes, IN ULONG Tag );
PVOID ExAllocatePoolWithQuota( IN POOL_TYPE PoolType, IN SIZE_T NumberOfBytes );
PVOID ExAllocatePoolWithQuotaTag( IN POOL_TYPE PoolType, IN SIZE_T NumberOfBytes, IN ULONG Tag );
typedef enum _POOL_TYPE { NonPagedPool, //要求分配非分页内存 PagedPool, //要求分配分页内存 NonPagedPoolMustSucceed, //要求分配非分页内存,必须成功 DontUseThisType, //未指定 NonPagedPoolCacheAligned, //要求分配非分页内存,且必须内存对齐 PagedPoolCacheAligned, //要求分配分页内存,且必须内存对齐 NonPagedPoolCacheAlignedMustS //要求分配非分页内存,且必须内存对齐,必须成功 } POOL_TYPE;NumberOfBytes是分配内存大小,最好是4的整数倍
返回值是分配的内存地址,一定是内核模式地址.如果返回0,则代表分配失败
Tag 是一个4字节标记,便于查找
下面是回收的函数:
VOID ExFreePool( IN PVOID P );
NTKERNELAPI VOID ExFreePoolWithTag( IN PVOID P, IN ULONG Tag );
P就是要释放的地址
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Lookaside结构
频繁的申请和回收内存,会导致在内存上产生大量的内存“空洞”,从而导致最终无法申请内存.DDK为程序员提供了Lookaside结构来解决这个问题.
我们可以将Lookaside对象看成是一个内存容器.在初始化的时候,它先向Windows申请了一块比较大的内存.以后程序员每次申请内存的时候,不是直接向Windows申请内存,而是想Lookaside对象申请内存.Looaside会智能的避免产生内存“空洞”.如果Lookaside对象内部内存不够用时,它会向操作系统申请更多的内存.
Lookaside一般会在以下情况下使用:
1.程序员每次申请固定大小的内存.
2.申请和回收的操作十分频繁.
初始化Lookaside对象,有两个函数
VOID ExInitializeNPagedLookasideList( IN PNPAGED_LOOKASIDE_LIST Lookaside, IN PALLOCATE_FUNCTION Allocate OPTIONAL, IN PFREE_FUNCTION Free OPTIONAL, IN ULONG Flags, IN SIZE_T Size, IN ULONG Tag, IN USHORT Depth );
VOID ExInitializePagedLookasideList( IN PPAGED_LOOKASIDE_LIST Lookaside, IN PALLOCATE_FUNCTION Allocate OPTIONAL, IN PFREE_FUNCTION Free OPTIONAL, IN ULONG Flags, IN SIZE_T Size, IN ULONG Tag, IN USHORT Depth );这两个函数分别是对非分页和分页Lookaside对象进行初始化
下面是申请内存的操作:
PVOID ExAllocateFromNPagedLookasideList( IN PNPAGED_LOOKASIDE_LIST Lookaside );
PVOID ExAllocateFromPagedLookasideList( IN PPAGED_LOOKASIDE_LIST Lookaside );这两个函数分别是对非分页内存和分页内存的申请
下面是Lookaside对象回收内存的操作:
VOID ExFreeToNPagedLookasideList( IN PNPAGED_LOOKASIDE_LIST Lookaside, IN PVOID Entry );
VOID ExFreeToPagedLookasideList( IN PPAGED_LOOKASIDE_LIST Lookaside, IN PVOID Entry );这两个函数分别是对非分页内存和分页内存的回收
下面是删除Lookaside对象的操作:
VOID ExDeleteNPagedLookasideList( IN PNPAGED_LOOKASIDE_LIST Lookaside );
VOID ExDeletePagedLookasideList( IN PPAGED_LOOKASIDE_LIST Lookaside );
测试函数:
VOID LookasideTest() { int i = 0; DbgPrint("enter LookasideTest"); PAGED_LOOKASIDE_LIST PageList; //初始化Lookaside对象 ExInitializePagedLookasideList(&PageList,NULL,NULL,0,sizeof(MYDATASTRUCT),'tset',0); PMYDATASTRUCT MyObjectArray[50]; //PMYDATASTRUCT定义见后文 //模拟频繁申请 for (i = 0; i <50; i++) { MyObjectArray[i] = (PMYDATASTRUCT)ExAllocateFromPagedLookasideList(&PageList); } //模拟频繁回收 for (i = 0; i <50; i++) { ExFreeToPagedLookasideList(&PageList,MyObjectArray[i]); MyObjectArray[i] = NULL; } //删除Lookaside对象 ExDeletePagedLookasideList(&PageList); }
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
在驱动中使用链表
DDK提供了标准的双向链表.双向链表可以将链表形成一个环.BLINK指针指向前一个元素.FLINK指针指向下一个元素.
typedef struct _LIST_ENTRY
<span style="font-family: Arial, Helvetica, sans-serif;">{</span>
struct _LIST_ENTRY *Flink; struct _LIST_ENTRY *Blink; } LIST_ENTRY, *PLIST_ENTRY;
链表初始化
每个双向链表都是以一个链表头作为链表的第一个元素.初次使用链表头需要进行初始化.主要是将链表头的FLINK和BLINK两个指针指向自己.表示链表头所代表的链是空链.
链表头初始化函数如下:
VOID InitializeListHead( IN PLIST_ENTRY ListHead );
上面定义的链表头只有前后指针而没有数据元素.因此我们需要自己定义链表中每个元素的数据类型.并将LIST_ENTRY结构作为自定义结构的一个子域.LIST_ENTRY的作用就是将自定义数据结构串成一个链表.
例如:
typedef struct _MYDATASTRUCT { LIST_ENTRY ListEntry; ULONG x; ULONG y; }MYDATASTRUCT,*PMYDATASTRUCT;
元素插入链表
从链表头部插入:
VOID InsertHeadList( IN PLIST_ENTRY ListHead, IN PLIST_ENTRY Entry );用法如下:
InsertHeadList(&head, &mydata->ListEntry);其中,head是LIST_ENTRY结构的链表头,mydata是自定义的数据结构
从链表尾部插入:
VOID InsertTailList( IN PLIST_ENTRY ListHead, IN PLIST_ENTRY Entry );从链表中删除:
PLIST_ENTRY RemoveHeadList( IN PLIST_ENTRY ListHead );
PLIST_ENTRY RemoveTailList( IN PLIST_ENTRY ListHead );这两个函数返回的是从链表删除下来的元素中的LIST_ENTRY子域.当我们想获得用户自定义数据结构的指针时,有两种情况:
(1)自定义数据结构的第一个字段就是LIST_ENTRY结构,如下:
typedef struct _MYDATASTRUCT { LIST_ENTRY ListEntry; ULONG x; ULONG y; }MYDATASTRUCT,*PMYDATASTRUCT;
这时,要得到自定义的数据结构可以如下:
PLIST_ENTRY pEntry = RemoveHeadList(&head);
PMYDATASTRUCT pMyData = (PMYDATASTRUCT)pEntry;
(2)自定义数据结构的第一个字段就不是LIST_ENTRY结构,如下:
typedef struct _MYDATASTRUCT { ULONG x; ULONG y; LIST_ENTRY ListEntry; }MYDATASTRUCT,*PMYDATASTRUCT;此时,前面的方法就是错误的,我们可以使用DDK为我们提供的一个宏
PCHAR CONTAINING_RECORD( IN PCHAR Address, IN TYPE Type, IN PCHAR Field );
要得到自定义的数据结构可以如下:
PLIST_ENTRY pEntry = RemoveHeadList(&head); PMYDATASTRUCT pMyData =CONTAINING_RECORD(pEntry, MYDATASTRUCT, ListEntry);DDK建议无论自定义数据结构的第一个字段是否为LIST_ENTRY结构,最好都使用CONTAINING_RECORD宏得到自定义数据结构的指针。
关于这个宏
当知道了某个struct内的某个field 的address, 利用其在结构体中的偏移, 用address减去偏移值,即可反推出这个大的struct 的instance address (结构地址)
这里主要是为了获取PLDR_DATA_TABLE_ENTRY的地址,对这个宏还有疑问的同学
(推荐http://www.cnblogs.com/nbsofer/archive/2013/01/07/2849913.html,这篇写的很详细)
测试代码:
typedef struct _MYDATASTRUCT { ULONG number; LIST_ENTRY ListEntry; }MYDATASTRUCT, *PMYDATASTRUCT;
VOID LinkListTest() { DbgPrint("entry list\n"); LIST_ENTRY ListHead; InitializeListHead(&ListHead); PMYDATASTRUCT pData; ULONG i; for (i=1; i<=10; i++) { pData = (PMYDATASTRUCT)ExAllocatePoolWithTag(PagedPool, sizeof(MYDATASTRUCT),'TSIL'); pData->number = i; InsertHeadList(&ListHead, &pData->ListEntry); } DbgPrint("Insert Ok"); DbgPrint("Delete Ing!"); while(!IsListEmpty(&ListHead)) { PLIST_ENTRY pListEntry = RemoveHeadList(&ListHead); pData = CONTAINING_RECORD(pListEntry, MYDATASTRUCT, ListEntry); DbgPrint("Remove %d element.\n", pData->number); ExFreePoolWithTag(pData,'TSIL'); } }
相关文章推荐
- Windows内存管理(1)--分配内核内存 和 使用链表
- Windows内存管理(1)--分配内核内存 和 使用链表
- 虚拟机VMare Workstation的使用,以及如何在虚拟Linux中访问Windows宿主文件,分区.(转载)
- 浅谈在Delphi中Dll调试方法以及怎样在Dll中使用数据模块DataModule(或表单Form)
- ubuntu, windows下安装opencv, 以及在qt,vs2010,dev,codeblock中的使用
- 改写:C#里根据Windows本地用户组查询组成员,以及修改密码(System.DirectoryServices.Protocols使用"WinNT://"方式)
- objective-c中对象所有权的内存管理(关于set,get方法),以及如何使用@property来进行简易操作(九)
- MS Windows Timer的限制以及如何使用更高分辨率的计时手段。
- windows下makefile 使用配置以及GCC安装方法
- windows下使用curl获取响应头信息,以及服务器隐藏响应头信息的办法
- windows环境下安装mongodb以及nosql使用
- vs2008和vs10以及Windows Phone自带的1000多个 Windows 系统使用的各种图标、光标和动画文件
- 浅谈Windows下VMware虚拟机的使用
- [视频]Silverlight for Windows Phone 7基本开发过程以及Push Button控件的使用
- 浅谈这一个多月MDT windows 7升级以及sophos加密和bitlocker加密项目的感受
- linux下使用samba上传文件夹和映射samba目录为windows下的目录以及使用source insight查看Linux工程的问题
- 在windows安装epics base,以及JCA的使用
- Windows操作系统上各种服务使用的端口号, 以及它们使用的协议的列表
- 使用Windows 7时常见的故障以及解决方法
- Windows以及Linux下双网卡内外网同时使用、设置域名解析优先级的方法