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之间只有数据线, 怎么传输地 ...
随机推荐
- 微信稳居Android App排行榜4月份国内榜首
根据某机构通过对Android样本访问行为的持续监测数据进行样本属性加权并根据iOS/Android用户调研数据建模推总得出中国移动互联网用户规模以及相应的用户结构数据显示,2015年4月份国内And ...
- cocos基础教程(11)事件分发机制
cocos3.0的事件分发机制: 创建一个事件监听器-用来实现各种触发后的逻辑. 事件监听器添加到事件分发器_eventDispatcher,所有事件监听器有这个分发器统一管理. 事件监听器有以下几种 ...
- [Effective JavaScript 笔记]第16条:避免使用eval创建局部变量
js中的eval函数是一个强大.灵活的工具.强大的工具容易被滥用,所以了解是值得的.(本人只用过它来处理json数据).错误使用eval函数的方式一:允许它干扰作用域.调用eval函数会将其参数作为j ...
- [Effective JavaScript 笔记]第42条:避免使用轻率的猴子补丁
41条对违反抽象原则行为的讨论之后,下面聊一聊终极违例.由于对象共享原型,因此每一个对象都可以增加.删除或修改原型的属性.这个有争议的实践通常称为猴子补丁. 猴子补丁示例 猴子补丁的吸引力在于其强大. ...
- 最长公共子串 NYOJ 36
http://acm.nyist.net/JudgeOnline/problem.php?pid=36 最长公共子序列 时间限制:3000 ms | 内存限制:65535 KB 难度:3 描述 ...
- uc_client是如何与UCenter进行通信的
以用户登录为例介绍,其它注销,改密码,消息,头像,好友均类同. 从用户xxx在某一应用程序的login.php,输入用户名,密码讲起.先用uc_user_login函数到uc_server验证此用户和 ...
- Android 下载文件及写入SD卡
Android 下载文件及写入SD卡,实例代码 <?xml version="1.0" encoding="utf-8"?> <LinearL ...
- 【SpringMVC】SpringMVC系列3之@PathVariable映射URL占位符参数
3.@PathVariable映射URL占位符参数 3.1.概述 带占位符的 URL 是 Spring3.0 新增的功能,该功能在SpringMVC 向 REST 目标挺进发展过程中具有里程碑的意义. ...
- 集群ssh服务和免密码登录的配置
安装Hadoop之前,由于集群中大量主机进行分布式计算需要相互进行数据通信,服务器之间的连接需要通过ssh来进行,所以要安装ssh服务,默认情况下通过ssh登录服务器需要输入用户名和密码进行连接,如果 ...
- Android涉及到的设计模式
转载地址:http://blog.csdn.net/dengshengjin2234/article/details/8502097 1.适配器模式:ListView或GridView的Adapter ...