Windows内存管理(1)--分配内核内存 和 使用链表
2014-02-28 10:23
183 查看
1. 分配内核内存
Windows驱动程序使用的内存资源非常珍贵,分配内存时要尽量节约。和应用程序一样,局部变量是存放在栈空间中的。但栈空间不会像应用程序那么大,所以驱动程序不适合递归调用或者局部变量是大型数据结构。如果需要大型数据结构,我们可以在堆中申请。
堆中申请的函数有以下几个:
(1)PVOID
ExAllocatePool(
IN POOL_TYPE PoolType,
IN SIZE_T NumberOfBytes
);
(2)PVOID
ExAllocatePoolWithTag(
IN POOL_TYPE PoolType,
IN SIZE_T NumberOfBytes,
IN ULONG Tag
);
(3) PVOID
ExAllocatePoolWithQuota(
IN POOL_TYPE PoolType,
IN SIZE_T NumberOfBytes
);
(4)PVOID
ExAllocatePoolWithQuotaTag(
IN POOL_TYPE PoolType,
IN SIZE_T NumberOfBytes,
IN ULONG Tag
);
PoolType:是个枚举变量,如果此值为NonPagedPool,则分配非分页内存。如果此值为PagedPool,则分配的内存为分页内存。
NumberOfBytes:是分配的内存大小,最好是4的整数倍。
返回值是分配的内存地址,一定是内核模式下的地址。如果返回0,则代表分配失败。
将分配的内存进行回收的函数如下:
(1)VOID
ExFreePool(
IN PVOID P
);
(2)NTKERNELAPI
VOID
ExFreePoolWithTag(
IN PVOID P,
IN ULONG Tag
);
2. 在驱动中使用链表
在驱动程序开发中,经常要使用链表这种数据结构。DDK为用户提供两种链表的数据结构,简化了对链表的操作。
下面主要讲的是双向链表。
(1)链表结构
DDK提供了标准的双向链表。双向链表可以将链表形成一个环。BLINK指针指向前一个元素。FLINK指针指向下一个元素。
DDK提供的双向链表的数据结构如下:
typedef struct _LIST_ENTRY {
struct _LIST_ENTRY *Flink;
struct _LIST_ENTRY *Blink;
} LIST_ENTRY, *PLIST_ENTRY;
(2)链表初始化
每个双向链表都是以一个链表头作为链表的第一个元素。初次使用链表头需要进行初始化。主要是将链表头的FLINK和BLINK两个指针指向自己。表示链表头所代表的链是空链。
链表头初始化函数如下:
VOID
InitializeListHead(
IN PLIST_ENTRY ListHead
);
如何判断链表是否为空,可以判断链表头的BLINK和FLINK指针是否指向自己。DDK为我们提供了一个宏简化了这种检查:
BOOLEAN
IsListEmpty(
IN PLIST_ENTRY ListHead
);
上面定义的链表头只有前后指针而没有数据元素。因此我们需要自己定义链表中每个元素的数据类型。并将LIST_ENTRY结构作为自定义结构的一个子域。LIST_ENTRY的作用就是将自定义数据结构串成一个链表。
例如:
typedef struct _MYDATASTRUCT{
LIST_ENTRY ListEntry;
ULONG x;
ULONG y;
}MYDATASTRUCT,*PMYDATASTRUCT;
(3)插入删除链表元素
从链表头部插入:
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宏得到自定义数据结构的指针。
测试代码:
typedef struct _MYDATASTRUCT{
ULONG number;
LIST_ENTRY ListEntry;
}MYDATASTRUCT, *PMYDATASTRUCT;
#pragma INITCODE
VOID LinkListTest()
{
KdPrint(("进入双向链表测试函数!\n"));
LIST_ENTRY ListHead;
InitializeListHead(&ListHead);
PMYDATASTRUCT pData;
ULONG i;
KdPrint(("开始往链表中插入数据!\n"));
for (i=1; i<=10; i++)
{
pData = (PMYDATASTRUCT)ExAllocatePool(PagedPool, sizeof(MYDATASTRUCT));
pData->number = i;
InsertHeadList(&ListHead, &pData->ListEntry);
}
KdPrint(("插入数据完毕!\n"));
KdPrint(("-----------------------------------------------------------------\n"));
KdPrint(("开始删除链表中的数据!\n"));
while(!IsListEmpty(&ListHead))
{
PLIST_ENTRY pListEntry = RemoveHeadList(&ListHead);
pData = CONTAINING_RECORD(pListEntry, MYDATASTRUCT, ListEntry);
KdPrint(("Remove %d element.\n", pData->number));
ExFreePool(pData);
}
KdPrint(("删除链表中的数据完毕!\n"));
KdPrint(("-----------------------------------------------------------------\n"));
}
Windows驱动程序使用的内存资源非常珍贵,分配内存时要尽量节约。和应用程序一样,局部变量是存放在栈空间中的。但栈空间不会像应用程序那么大,所以驱动程序不适合递归调用或者局部变量是大型数据结构。如果需要大型数据结构,我们可以在堆中申请。
堆中申请的函数有以下几个:
(1)PVOID
ExAllocatePool(
IN POOL_TYPE PoolType,
IN SIZE_T NumberOfBytes
);
(2)PVOID
ExAllocatePoolWithTag(
IN POOL_TYPE PoolType,
IN SIZE_T NumberOfBytes,
IN ULONG Tag
);
(3) PVOID
ExAllocatePoolWithQuota(
IN POOL_TYPE PoolType,
IN SIZE_T NumberOfBytes
);
(4)PVOID
ExAllocatePoolWithQuotaTag(
IN POOL_TYPE PoolType,
IN SIZE_T NumberOfBytes,
IN ULONG Tag
);
PoolType:是个枚举变量,如果此值为NonPagedPool,则分配非分页内存。如果此值为PagedPool,则分配的内存为分页内存。
NumberOfBytes:是分配的内存大小,最好是4的整数倍。
返回值是分配的内存地址,一定是内核模式下的地址。如果返回0,则代表分配失败。
将分配的内存进行回收的函数如下:
(1)VOID
ExFreePool(
IN PVOID P
);
(2)NTKERNELAPI
VOID
ExFreePoolWithTag(
IN PVOID P,
IN ULONG Tag
);
2. 在驱动中使用链表
在驱动程序开发中,经常要使用链表这种数据结构。DDK为用户提供两种链表的数据结构,简化了对链表的操作。
下面主要讲的是双向链表。
(1)链表结构
DDK提供了标准的双向链表。双向链表可以将链表形成一个环。BLINK指针指向前一个元素。FLINK指针指向下一个元素。
DDK提供的双向链表的数据结构如下:
typedef struct _LIST_ENTRY {
struct _LIST_ENTRY *Flink;
struct _LIST_ENTRY *Blink;
} LIST_ENTRY, *PLIST_ENTRY;
(2)链表初始化
每个双向链表都是以一个链表头作为链表的第一个元素。初次使用链表头需要进行初始化。主要是将链表头的FLINK和BLINK两个指针指向自己。表示链表头所代表的链是空链。
链表头初始化函数如下:
VOID
InitializeListHead(
IN PLIST_ENTRY ListHead
);
如何判断链表是否为空,可以判断链表头的BLINK和FLINK指针是否指向自己。DDK为我们提供了一个宏简化了这种检查:
BOOLEAN
IsListEmpty(
IN PLIST_ENTRY ListHead
);
上面定义的链表头只有前后指针而没有数据元素。因此我们需要自己定义链表中每个元素的数据类型。并将LIST_ENTRY结构作为自定义结构的一个子域。LIST_ENTRY的作用就是将自定义数据结构串成一个链表。
例如:
typedef struct _MYDATASTRUCT{
LIST_ENTRY ListEntry;
ULONG x;
ULONG y;
}MYDATASTRUCT,*PMYDATASTRUCT;
(3)插入删除链表元素
从链表头部插入:
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宏得到自定义数据结构的指针。
测试代码:
typedef struct _MYDATASTRUCT{
ULONG number;
LIST_ENTRY ListEntry;
}MYDATASTRUCT, *PMYDATASTRUCT;
#pragma INITCODE
VOID LinkListTest()
{
KdPrint(("进入双向链表测试函数!\n"));
LIST_ENTRY ListHead;
InitializeListHead(&ListHead);
PMYDATASTRUCT pData;
ULONG i;
KdPrint(("开始往链表中插入数据!\n"));
for (i=1; i<=10; i++)
{
pData = (PMYDATASTRUCT)ExAllocatePool(PagedPool, sizeof(MYDATASTRUCT));
pData->number = i;
InsertHeadList(&ListHead, &pData->ListEntry);
}
KdPrint(("插入数据完毕!\n"));
KdPrint(("-----------------------------------------------------------------\n"));
KdPrint(("开始删除链表中的数据!\n"));
while(!IsListEmpty(&ListHead))
{
PLIST_ENTRY pListEntry = RemoveHeadList(&ListHead);
pData = CONTAINING_RECORD(pListEntry, MYDATASTRUCT, ListEntry);
KdPrint(("Remove %d element.\n", pData->number));
ExFreePool(pData);
}
KdPrint(("删除链表中的数据完毕!\n"));
KdPrint(("-----------------------------------------------------------------\n"));
}
相关文章推荐
- Windows内存管理(1)--分配内核内存 和 使用链表
- Windows内存管理(1)--分配内核内存 和 使用链表
- 链表创建为什么需要使用内存分配?
- HUSTOJ的Windows版评判内核(限制内存使用)
- windows内核驱动内存管理之Lookaside使用
- Windows内存管理机制及C++内存分配实例(五):堆
- 使用kprobe追踪内核内存分配失败现象
- 浅谈Windows内存管理以及链表使用
- Linux的内存管理主要分为两部分:物理地址到虚拟地址的映射,内核内存分配管理(主要基于slab)。
- Windows驱动开发小练习-内存分配与链表操作
- Windows驱动开发小练习-内存分配与链表操作
- Linux内存管理 —— 内核态和用户态的内存分配方式
- linux 3.4.10 内核内存管理源代码分析10:slab通用长度内存分配
- Windows内核中使用List链表来模拟FIFO操作
- 嵌入式操作系统内核原理和开发(改进的链表内存分配算法)
- 全面介绍Windows内存管理机制及C++内存分配实例(六):堆栈
- Linux 内核链表的使用及深入分析【转】
- C++内存管理(内存分配、内存泄漏、内存回收)
- C/C++获取Windows系统CPU和内存及硬盘使用情况
- Windows三种内存分配机制