一个常见的 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. Java中的构造函数和重载

    一.Java中的构造函数 构造函数是对象被创建时初始化对象的成员方法,它具有和它所在的类完全一样的名字.构造函数只能有入口参数,没有返回类型,因为一个类的构造方法的返回类就是类本身.构造函数定义后,创 ...

  2. HDU 4857 Couple doubi(找循环节)

    题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=4861 解题报告:桌子上有k个球 ,第i个球的价值wi = 1^i+2^i+...+(p-1)^i (m ...

  3. Firefox渗透测试黑客插件集

    前天看S哥用Firefox的hackbar进行手动注入进行渗透,觉得直接运用浏览器的插件进行渗透测试有很多优点,既可以直接在前端进行注入等操作,也可以省却了寻找各种工具的麻烦.前端还是最直接的!于是这 ...

  4. SNMP协议

           SNMP(Simple Network Management Protocol,SNMP)简单网络管理协议,其定义了传送管理信息的协议消息格式及管理站和设备代理相互之间进行消息传送的规程 ...

  5. 重新编译安装gcc-4.1.2(gcc版本降级)之TFS安装

    wget http://gcc.parentingamerica.com/releases/gcc-4.1.2/gcc-4.1.2.tar.gz tar -zxfv gcc-4.1.2.tar.gz ...

  6. object-c面向对象1

    ---恢复内容开始--- 类,对象,方法,属性. 类是object-c一种重要的数据类型,是组成object-c程序的基本要素.object-c的类声明和实现包括两个部分:接口和实现部分. @inte ...

  7. Solr DIH导入出现 Data Config problem: 前言中不允许有内容 异常

    Solr配置DIH导入时出现 “Data Config problem: 前言中不允许有内容.” 异常. <response> <lst name="responseHea ...

  8. windows下bat批处理实现守护进程

    本文转自网络,由于找不到原作者,因而无法知道出处.如果有幸让原作者看到,请联系我加上.先转载至此. 最近几天加班加疯掉了,天天晚上没法睡.开发部的一个核心程序总是会自己宕机,然后需要手工去起,而这个服 ...

  9. 【JAVA、C++】LeetCode 004 Median of Two Sorted Arrays

    There are two sorted arrays nums1 and nums2 of size m and n respectively. Find the median of the two ...

  10. codeforces 474D.Flowers 解题报告

    题目链接:http://codeforces.com/problemset/problem/474/D 题目意思:Marmot 吃两种类型的花(实在难以置信呀--):red 或者 white,如果要吃 ...