一个常见的 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. linux awk 内置函数详细介绍(实例)

    这节详细介绍awk内置函数,主要分以下3种类似:算数函数.字符串函数.其它一般函数.时间函数 一.算术函数: 以下算术函数执行与 C 语言中名称相同的子例程相同的操作: 函数名 说明 atan2( y ...

  2. 【云计算】Docker Nginx示例

    使用数据卷容器,配置Nginx Docker作为静态文件服务器 . 该方法是直接使用命令行,当然也可使用Dockerfile文件进行创建. 其实,使用docker创建nginx容器是很简单的,但要和数 ...

  3. less,sass,stylus配置和应用教程及三者比较

    less,sass,stylus配置和应用教程及三者比较  Less 1. 定义: Less是CSS预处理语言,在css基础之上增加了诸如变量,混合(mix),继承,运算,函数等功能,LESS既可以运 ...

  4. eclipse字体的设置

    eclipse的默认字体太小,所以设置的大一些比较清楚,方法很简单,单击菜单栏的"Window"选择"Preferences",如下图: 然后左侧依次选择Gen ...

  5. Java for LeetCode 187 Repeated DNA Sequences

    All DNA is composed of a series of nucleotides abbreviated as A, C, G, and T, for example: "ACG ...

  6. Java for LeetCode 072 Edit Distance【HARD】

    Given two words word1 and word2, find the minimum number of steps required to convert word1 to word2 ...

  7. java类初始化优先级

    父类静态变量.父类静态代码块.子类静态变量.子类静态代码块.父类非静态变量.父类非静态代码块.父类构造函数.子类非静态变量.子类非静态代码块.子类构造函数

  8. 以普通用户登录 su root 用vncviewer:xxxxx 会报错!!exit 回到最初环境变的用户 问题解决!!!!

    [root@ok IT-DOC]# vncviewer : TigerVNC Viewer - built May :: Copyright (C) - TigerVNC Team and many ...

  9. 第六步:Lucene查询索引

    package cn.harmel.lucene; import java.io.IOException; import java.nio.file.Paths; import org.apache. ...

  10. Linux运维操作

    http://www.it165.net/os/html/201204/1909.html https://i.cnblogs.com/EditPosts.aspx?opt=1 http://www. ...