/*
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');
}
}