一个常见的 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. 小白科普之JavaScript的BOM模型

    一.什么是BOM 1. BOM是browser object model的缩写,简称浏览器对象模型: 2. BOM提供了独立于内容而与浏览器窗口进行交互的对象,描述了与浏览器进行交互的方法和接口: 3 ...

  2. MyEclipse安装JS代码提示(Spket插件)

    近期需要大量使用JS来开发,但是MyEclipse2014自带的JS编辑器没有代码提示的功能,开发效率有点低,所以安装了一个Spket的插件,过程非常简单,SVN插件的安装比这个更简单. Spket插 ...

  3. HTML快速入门3

    四.表格 (Table) 1. 表格的基本形式 表由 <table> 开始, </table> 结束,表的内容由 <tr>,<th> 和 <td& ...

  4. .NET Reflector 7.6.1.824 Edition .NET程序反编译神器(附插件安装教程2012-10-13更新) 完全破解+使用教程

    原文来自VAllen cnblogs 一.使用教程1.解压后,双击Reflector.exe,如果有选择默认版本的.Net Framework,根据需要选择即可.你选择的版本不同则出现的默认程序集也不 ...

  5. 面向侧面的程序设计AOP-------《三》.Net平台AOP技术概览

    本文转载自张逸:晴窗笔记 .Net平台与Java平台相比,由于它至今在服务端仍不具备与unix系统的兼容性,也不具备类似于Java平台下J2EE这样的企业级容器,使得.Net平台在大型的企业级应用上, ...

  6. Binary Tree Right Side View

    Given a binary tree, imagine yourself standing on the right side of it, return the values of the nod ...

  7. Object-c 控制语句

    控制语句: 分支语句   if-else  有控制机制    switch 循环语句   while    do-while  for 跳转语句   break,continue,goto

  8. Python操作Mysql实例代码教程在线版(查询手册)

    本文介绍了Python操作MYSQL.执行SQL语句.获取结果集.遍历结果集.取得某个字段.获取表字段名.将图片插入数据库.执行事务等各种代码实例和详细介绍,代码居多,是一桌丰盛唯美的代码大餐   实 ...

  9. 【USACO】pprime

    开始看第一眼题就觉得问题会在超时上,果然写了个小代码运行到test 9时超时了 #include <stdio.h> #include <math.h> int isprime ...

  10. 编译qt

    进入开始菜单Microsoft Visual Studio 2010,Visual Studio Tools,Visual Studio Command Prompt (2010),需要注意的是,这里 ...