LIST_ENTRY
一个常见的 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的更多相关文章
- Linux内核之旅 List_entry()
#include "iostream" #define List_entry(type,member)\ (type *)(()->data)) ) using namesp ...
- 节点地址的函数list_entry()原理详解
本节中,我们继续讲解,在linux2.4内核下,如果通过一些列函数从路径名找到目标节点. 3.3.1)接下来查看chached_lookup()的代码(namei.c) [path_walk()> ...
- [内核驱动] 链表LIST_ENTRY的操作(转)
转载:https://www.cnblogs.com/forlina/archive/2011/08/11/2134610.html 转载:http://www.xuebuyuan.com/15443 ...
- 驱动链表(LIST_ENTRY)
DDK提供了两种链表的数据结构,双向链表和单向链表,其定义如下: typedef struct _LIST_ENTRY { struct _LIST_ENTRY *Flink; struct _LIS ...
- 由结构体成员地址计算结构体地址——list_entry()原理详解
#define list_entry(ptr, type, member) container_of(ptr, type, member) 在进行编程的时候,我们经常在知道结构体地址的情况下,寻找其中 ...
- list_entry(ptr, type, member)——知道结构体内某一成员变量地址,求结构体地址
#define list_entry(ptr, type, member) \ ((type *)(() -> member))) 解释: 1 在0这个地址看做有一个虚拟的type类型的变量,那 ...
- 接触到的一些数据结构: LIST_ENTRY, TAILQ
双链表: LIST_ENTRY: typedef struct _LIST_ENTRY { struct _LIST_ENTRY *Flink; follow: next entry, header ...
- 对list_entry(ptr, type, member)的理解
如何根据一个结构体成员的地址.结构体类型以及该结构体成员名获得该结构体的首地址? #define list_entry(ptr, type, member) \ ((type *)((char *)( ...
- nandflash驱动程序编写
NAND FLASH是一个存储芯片 那么: 这样的操作很合理"读地址A的数据,把数据B写到地址A" 问1. 原理图上NAND FLASH和S3C2440之间只有数据线, 怎么传输地 ...
随机推荐
- Swift Tour 随笔总结 (3)
关于Optional的Control Flow if let constantName = someOptional { statements } 如果该Optional为nil,则不进入if,否则执 ...
- 程序员必读:Linux内存管理剖析
现在的服务器大部分都是运行在Linux上面的,所以作为一个程序员有必要简单地了解一下系统是如何运行的. 对于内存部分需要知道: 地址映射 内存管理的方式 缺页异常 先来看一些基本的知识,在进程看来,内 ...
- UIView 注意问题
1. UIView.userInteractionEnabled UIView.userInteractionEnabled默认值是YES http://blog.csdn.net/studyreco ...
- 重写HashMap
package gov.gfmis.fap.util; import java.util.ArrayList; import java.util.HashMap; import java.util.L ...
- C#关键字base
例子: public CustomStroke(SharpType type) :base() { this.type = type; } 这里的CustomStroke继承与基类Stroke类,用关 ...
- Java正则表达式的最简单应用
String emailRegex = "^\\w+([-+.]\\w+)*@\\w+([-.]\\w+)*\\.\\w+([-.]\\w+)*$"; Pattern pat = ...
- 在 FREEBUF 投放广告
在 FREEBUF 投放广告 FreebuF黑客与极客—高质量的全球互联网安全媒体,同时也是爱好者们交流.分享安全技术的最佳平台.本站读者群以IT.政企信息安全人员.互联网安全爱好者和学生为主,对互联 ...
- process vs thread
process vs thread http://blog.csdn.net/mishifangxiangdefeng/article/details/7588727 6.进程与线程的区别:系统调度是 ...
- jstl数字转日期
<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt" %> <jsp: ...
- web页面版权部分的显示问题
网站开发中绝大部分页面底部都需要版权信息,一般都是Copyright ©域名 2014 - 2015. All Rights Reserved.这种格式,当然也有其他的,有时候不太注意会发现做出的这个 ...