在驅動中使用鏈表

文章作者:grayfox
作者主頁:http://nokyo.blogbus.com
原始出處:http://nokyo.blogbus.com/logs/33271026.html  

    在驅動程序的開發中經常需要用到鏈表,常見的鏈表有單向鏈表和雙向鏈表,我們只介紹雙向鏈表的使用方法,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);

    此時在向鏈表中插入或移除節點時會自動調用關聯的鎖進行加鎖操作,有效地保證了多線程安全性。

發佈了53 篇原創文章 · 獲贊 0 · 訪問量 1萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章