在驱动中使用链表

文章作者: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);

    此时在向链表中插入或移除节点时会自动调用关联的锁进行加锁操作,有效地保证了多线程安全性。

你可能感兴趣的:(多线程,数据结构,编程)