一个常见的 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;

--------------------------------------------------------------------------------
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);
 
}

--------------------------------------------------------------------------------

在驱动中使用链表:
在驱动程序的开发中经常需要用到链表,常见的链表有单向链表和双向链表,我们只介绍双向链表的使用方法,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驱动开发技术详解》

LIST_ENTRY的更多相关文章

  1. Linux内核之旅 List_entry()

    #include "iostream" #define List_entry(type,member)\ (type *)(()->data)) ) using namesp ...

  2. 节点地址的函数list_entry()原理详解

    本节中,我们继续讲解,在linux2.4内核下,如果通过一些列函数从路径名找到目标节点. 3.3.1)接下来查看chached_lookup()的代码(namei.c) [path_walk()> ...

  3. [内核驱动] 链表LIST_ENTRY的操作(转)

    转载:https://www.cnblogs.com/forlina/archive/2011/08/11/2134610.html 转载:http://www.xuebuyuan.com/15443 ...

  4. 驱动链表(LIST_ENTRY)

    DDK提供了两种链表的数据结构,双向链表和单向链表,其定义如下: typedef struct _LIST_ENTRY { struct _LIST_ENTRY *Flink; struct _LIS ...

  5. 由结构体成员地址计算结构体地址——list_entry()原理详解

    #define list_entry(ptr, type, member) container_of(ptr, type, member) 在进行编程的时候,我们经常在知道结构体地址的情况下,寻找其中 ...

  6. list_entry(ptr, type, member)——知道结构体内某一成员变量地址,求结构体地址

    #define list_entry(ptr, type, member) \ ((type *)(() -> member))) 解释: 1 在0这个地址看做有一个虚拟的type类型的变量,那 ...

  7. 接触到的一些数据结构: LIST_ENTRY, TAILQ

    双链表: LIST_ENTRY: typedef struct _LIST_ENTRY { struct _LIST_ENTRY  *Flink; follow: next entry, header ...

  8. 对list_entry(ptr, type, member)的理解

    如何根据一个结构体成员的地址.结构体类型以及该结构体成员名获得该结构体的首地址? #define list_entry(ptr, type, member) \ ((type *)((char *)( ...

  9. nandflash驱动程序编写

    NAND FLASH是一个存储芯片 那么: 这样的操作很合理"读地址A的数据,把数据B写到地址A" 问1. 原理图上NAND FLASH和S3C2440之间只有数据线, 怎么传输地 ...

随机推荐

  1. JVM是如何分配和回收内存?有实例!

    上一篇博客我简单介绍了下如何手动计算一个Java对象到底占用多少内存?今天就想聊下这个内存JVM到底是是如何分配和回收的. Java整体来说还是一个GC比较友好的语言,无论是分代的垃圾收集,还是基于G ...

  2. 那些不是秘密的微信earning方法

    微信这个新兴的移动平台着实培养了一些自媒体大号,让个人也能成为媒体中心,当然微信也成为了集富利器.他们是怎么做到的呢?让我们探究一下微信earn的方法吧,一起发散思维. ★微信公众平台推广功能公测,大 ...

  3. sql注入学习小结

    /* 转载请注明出处,By:珍惜少年时 小知识,只是放在博客吃饭时无聊看看,大牛勿喷. */ 珍惜少年时博客,专注网络安全 web渗透测试 00x1爆所有库: mysql> select sch ...

  4. Centos下安装mysql 总结

    一.MySQL安装 Centos下安装mysql 请点开:http://www.centoscn.com/CentosServer/sql/2013/0817/1285.html 二.MySQL的几个 ...

  5. 我常用的delphi 第三方控件

    转载:http://www.cnblogs.com/xalion/archive/2012/01/09/2317246.html 有网友问我常用的控件及功能.我先大概整理一下,以后会在文章里面碰到时再 ...

  6. Calico在Kubernetes中的搭建

    一,需求 Kubernetes官方推荐的是Flannel,但是Flannel是一个overlay的网络,对性能会有一定的影响.Calico恰好能解决一下overlay网络的不足. Calico在Kub ...

  7. iOS UITableView 的beginUpdates和endUpdates

    在官方文档中是这样介绍beginUpdates的 Call this method if you want subsequent insertions, deletion, and selection ...

  8. (转)SQL Server 的事务和锁(二)-Range S-S锁

    在这篇随笔中,我们的主要关注点在 Key-Range Lock.Key-Range Lock有 S-S.S-U.I-N.X-X几种情况.我们一个一个来说,力求明白.遗憾的是,这里可能会比较冗长,那么死 ...

  9. CodeForces - 404A(模拟题)

    Valera and X Time Limit: 1000MS   Memory Limit: 262144KB   64bit IO Format: %I64d & %I64u Submit ...

  10. iOS7隐藏顶部状态栏

    找到工程中的Supporting Files/工程名-info.plist 添加设置 1.status bar is initially hidden=YES 2.View Controller-ba ...