淺談Windows內存管理以及鏈表使用

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

	}
}


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章