一個常見的 Windows 2000 數據類型是 LIST_ENTRY 結構。內核使用該結構將全部對象維護在一個雙向鏈表中。一個對象分屬多個鏈表是很常見的, Flink 成員是一個向前連接,指向下一個 LIST_ENTRY 結構, Blink 成員則是一個向後連接,指向前一個 LIST_ENTRY 結構。一般狀況下,這些鏈表都成環形,也就是說,最後一個 Flink 指向鏈表中的第一個 LIST_ENTRY 結構,而第一個 Blink 指向最後一個。這樣就很容易雙向遍歷該鏈表。若是一個程序要遍歷整個鏈表,它須要保存第一個 LIST_ENTRY 結構的地址,以判斷是否已遍歷了整個鏈表。若是鏈表僅包含一個 LIST_ENTRY 結構,那麼該 LIST_ENTRY 結構必須引用其自身,也就是說, Flink 和 Blink 都指向其本身。編程
typedef struct _LIST_ENTRY
{
struct _LIST_ENTRY *Flink;
struct _LIST_ENTRY *Blink;
} LIST_ENTRY, *PLIST_ENTRY;windows
--------------------------------------------------------------------------------
LIST_ENTRY使用:
VOID LinkListTest()
{
LIST_ENTRY linkListHead;
//初始化鏈表
InitializeListHead(&linkListHead);安全
PMYDATASTRUCT pData;
ULONG i = 0;
//在鏈表中插入10個元素
KdPrint(("Begin insert to link list"));
for (i=0 ; i<10 ; i++)
{
pData = (PMYDATASTRUCT)
ExAllocatePool(PagedPool,sizeof(MYDATASTRUCT));
pData->number = i;
InsertHeadList(&linkListHead,&pData->ListEntry);
}多線程
//從鏈表中取出,並顯示
KdPrint(("Begin remove from link list\n"));
while(!IsListEmpty(&linkListHead))
{
PLIST_ENTRY pEntry = RemoveTailList(&linkListHead);
pData = CONTAINING_RECORD(pEntry,
MYDATASTRUCT,
ListEntry);
KdPrint(("%d\n",pData->number));
ExFreePool(pData);
}
}函數
遍歷:
PLIST_ENTRY pLink=NULL;
for(pLink = glinkListRule.Flink; pLink !=(PLIST_ENTRY) &glinkListRule.Flink; pLink = pLink->Flink)
{
pRegPrtRule pData= CONTAINING_RECORD(pLink,RegPrtRule,ListEntry);
}ui
--------------------------------------------------------------------------------線程
在驅動中使用鏈表:
在驅動程序的開發中常常須要用到鏈表,常見的鏈表有單向鏈表和雙向鏈表,咱們只介紹雙向鏈表的使用方法,DDK爲咱們提供了標準的雙向鏈表LIST_ENTRY,但這個鏈表裏面沒有數據,不能直接使用,咱們須要本身定義一個結構體類型,而後將LIST_ENTRY做爲結構體的一個子域,以下所示:
typedef struct _MYDATASTRUCT{
ULONG number;
LIST_ENTRY ListEntry;
} MYDATASTRUCT, *PMYDATASTRUCT;
實際上把LIST_ENTRY放在結構體的第一個子域纔是較好的作法,此處咱們不過多地關心,反正用法都是大同小異。下面咱們就在驅動程序中建立一個鏈表,使用剛剛定義的結構體做爲節點類型。代碼以下所示:指針
VOID LinkListTest()
{
LIST_ENTRY linkListHead; // 鏈表
PMYDATASTRUCT pData; // 節點數據
ULONG i = 0; // 計數
//初始化
InitializeListHead(&linkListHead);
//向鏈表中插入10個元素
KdPrint(("[ProcessList] Begin insert to link list"));
for (i=0 ; i<10 ; i++)
{ // pData是咱們定義的指針,必須被初始化後才能使用
pData = (PMYDATASTRUCT)ExAllocatePool(PagedPool,sizeof(MYDATASTRUCT));
pData->number = i;
// 將其做爲一個節點插入鏈表
InsertHeadList(&linkListHead,&pData->ListEntry);
}
對象
// 從鏈表中取出全部數據並顯示
KdPrint(("[ProcessList] Begin remove from link list\n"));
while(!IsListEmpty(&linkListHead))
{
// 取出一個節點
PLIST_ENTRY pEntry = RemoveTailList(&linkListHead);
// 獲取節點內容
pData = CONTAINING_RECORD(pEntry, MYDATASTRUCT, ListEntry);
KdPrint(("%d\n",pData->number));
// 釋放節點,ExAllocatePool必須與ExFreePool成對使用
ExFreePool(pData);
}
}
複製代碼開發
上述代碼能夠正常地經過編譯並運行,但其中存在着一個很大的隱患:它不是多線程安全的。若是有多個線程同時操做同一個鏈表的話,可能會引起不可預料的後果,咱們能夠經過使用自旋鎖來避免,修改後的代碼以下所示:
VOID LinkListTest()
{
LIST_ENTRY linkListHead; // 鏈表
PMYDATASTRUCT pData; // 節點數據
ULONG i = 0; // 計數
KSPIN_LOCK spin_lock; // 自旋鎖
KIRQL irql; // 中斷級別
// 初始化
InitializeListHead(&linkListHead);
KeInitializeSpinLock(&spin_lock);
//向鏈表中插入10個元素
KdPrint(("[ProcessList] Begin insert to link list"));
// 鎖定,注意這裏的irql是個指針
KeAcquireSpinLock(&spin_lock, &irql);
for (i=0 ; i<10 ; i++)
{
pData = (PMYDATASTRUCT)ExAllocatePool(PagedPool,sizeof(MYDATASTRUCT));
pData->number = i;
InsertHeadList(&linkListHead,&pData->ListEntry);
}
// 解鎖,注意這裏的irql不是指針
KeReleaseSpinLock(&spin_lock, irql);
//從鏈表中取出全部數據並顯示
KdPrint(("[ProcessList] Begin remove from link list\n"));
// 鎖定
KeAcquireSpinLock(&spin_lock, &irql);
while(!IsListEmpty(&linkListHead))
{
PLIST_ENTRY pEntry = RemoveTailList(&linkListHead);
pData = CONTAINING_RECORD(pEntry, MYDATASTRUCT, ListEntry);
KdPrint(("%d\n",pData->number));
ExFreePool(pData);
}
// 解鎖
KeReleaseSpinLock(&spin_lock, irql);
}
上述代碼介紹了自旋鎖的使用方法,但須要注意的是:上面這段代碼在實際應用中是沒有任何價值的。由於在上述代碼咱們定義的鎖是一個局部變量,被分配在棧中,這樣每一個線程在調用該函數的時候,都會從新初始化一個鎖,所以這個鎖就失去了原本的做用。在實際的編程中,咱們應該把鎖定義成一個全局變量,或者靜態(static)變量,或者將其建立在堆空間中。 另外,咱們還能夠爲每一個鏈表都定義並初始化一個鎖,在須要向該鏈表插入或移除節點時不使用前面介紹的普通函數,而是使用以下方法:ExInterlockedInsertHeadList(&linkListHead, &pData->ListEntry, &spin_lock);pData = (PMYDATASTRUCT)ExInterlockedRemoveHeadList(&linkListHead, &spin_lock); 此時在向鏈表中插入或移除節點時會自動調用關聯的鎖進行加鎖操做,有效地保證了多線程安全性。以上大部分論述來自《windows驅動開發技術詳解》