04-驱动中的链表操作

链表分为单向链表和双向链表,但是在内核中一般都是使用的有头双向链表。有头双向链接指的是链表的第一项是没有数据的,只保存链表下一节点和上一节点的指向指针。

WDK对链表的定义为:

typedef struct _LIST_ENTRY {
struct _LIST_ENTRY *Flink;
struct _LIST_ENTRY *Blink;
} LIST_ENTRY, *PLIST_ENTRY, *RESTRICTED_POINTER PRLIST_ENTRY;
LIST_ENTRY表示一个链表的节点,其中Flink成员指向当前节点的后一个节点,Blink成员指向当前节点的前一个节点。
WDK定义的链表结构是没有存放数据的变量的,所以在实际开发使用中需要自己定义一个结构体,将WDK的链表结构体放入自己定义的结构中作为自定义结构体的一个成员。如下面例子:
typedef struct _My_List_Entry
{
ULONG dataA;
ULONG dataB;
LIST_ENTRY ListEntry;
ULONG dataC;
ULONG dataD;
}My_List_Entry,*PMy_List_Entry;
链表头节点不携带任何内容,只表示链表的头部,对链表的所有操作都是从头部开始的,当链表只有一个头节点而没有其他节点时,该链表就是一个空的链表,头节点的Flink与Blink指向头节点自身。
下面的代码定义了一个头节点,并对头节点进行初始化:
LIST_ENTRY ListEntry;
InitializeListHead(&ListEntry);
InitializeListHead函数初始化一个头节点,这里的初始化,实际上就是修改头节点的Flink以及Blink成员,使其指向自身。
InitializeListHead函数原型如下:
VOID InitializeListHead(Out PLIST_ENTRY ListHead)//这是一个输出参数,LIST_ENTRY的变量指针所以上面例子使用了&(取地址)符号

节点插入:
常见的节点插入操作有两种,一种是把节点插入到链表的第一个位置,另外一种是把节点插入到链表的最后一个位置。
InsertHeadList函数的作用是把一个节点插入到链表的第一个位置,相应地,InsertTailList函数的作用是把一个节点插入到链表的最后一个位置,两个函数的参数相同:
VOID InsertHeadList(Inout PLIST_ENTRY ListHead,Out PLIST_ENTRY Entry)
VOID InsertTailList(Inout PLIST_ENTRY ListHead,Out PLIST_ENTRY Entry)
其中ListHead表示链表的头节点,即为需要被插入节点的链表头节点,Entry表示需要插入的链表节点。下面用一段代码来演示链表节点的插入:
VOID TestListEntry()
{
LIST_ENTRY ListEntry;
InitializeListHead(&ListEntry);
My_List_Entry mlsA = { 0 };
My_List_Entry mlsB = { 0 };
My_List_Entry mlsC = { 0 };
My_List_Entry mlsD = { 0 };
mlsA.dataA = 9;
mlsB.dataA = 8;
mlsC.dataA = 7;
mlsD.dataA = 6;
InsertHeadList(&ListEntry, &mlsA.ListEntry);
InsertHeadList(&ListEntry, &mlsB.ListEntry);
InsertHeadList(&ListEntry, &mlsC.ListEntry);
InsertTailList(&ListEntry, &mlsD.ListEntry);
}
插入完的链表:链表头->节点C->节点B->节点A->节点D

链表遍历:
链表遍历可以通过Flink指针从前往后遍历,也可以通过Blink从后往前遍历。
下面给出一个遍历的实际例子:

typedef struct _My_List_Entry
{
ULONG dataA;
ULONG dataB;
LIST_ENTRY ListEntry;
ULONG dataC;
ULONG dataD;
}My_List_Entry,*PMy_List_Entry;

VOID TestListEntry()
{
LIST_ENTRY ListEntry;
InitializeListHead(&ListEntry);
My_List_Entry mlsA = { 0 };
My_List_Entry mlsB = { 0 };
My_List_Entry mlsC = { 0 };
My_List_Entry mlsD = { 0 };
mlsA.dataA = 9;
mlsB.dataA = 8;
mlsC.dataA = 7;
mlsD.dataA = 6;
InsertHeadList(&ListEntry, &mlsA.ListEntry);
InsertHeadList(&ListEntry, &mlsB.ListEntry);
InsertHeadList(&ListEntry, &mlsC.ListEntry);
InsertTailList(&ListEntry, &mlsD.ListEntry);
PLIST_ENTRY plistentry = NULL;
plistentry = ListEntry.Flink;
while (plistentry != &ListEntry)
{
PMy_List_Entry pmylist = CONTAINING_RECORD(plistentry, My_List_Entry, ListEntry);
KdPrint((“listptr = %p,entry = %p,data = %d\n”, plistentry, pmylist, pmylist->dataA));
plistentry = plistentry->Flink;
}
}

上面的代码定义了plistentry指针变量用于遍历,把ListEntry.Flink的值赋给plistentry,此时plistentry指向链表中的第一个节点。在while循环中,首先访问节点的具体信息,然后plistentry指向下一个节点,循环结束的条件是plistentry == &ListEntry
还需要提及的一点是pmylist 指针的获取,在while循环块中,是通过plistentry指针进行遍历的,而plistentry指向的地址是My_List_Entry结构体中的ListEntry地址,而ListEntry成员的地址并不是这个结构体的首地址,所以这里需要通过一个宏CONTAINING_RECORD把ListEntry的地址转换成结构体My_List_Entry的首地址,CONTAINING_RECORD宏的用法如下:
#define CONTAINING_RECORD(address, type, field)
其中address表示LIST_ENTRY的地址,type表示类型(自定义列表名或微软提供的结构体名),最后的Field表示结构体中LIST_ENTRY成员的名字。
最终上面运行后的结果为:
在这里插入图片描述

节点移除:
移除节点有三种方式:移除链表中的第一个节点、移除链表中的最后一个节点、移除链表中特定的节点。
移除链表中第一个节点与最后一个节点分别使用RemoveHeadList以及RemoveTailList函数,这两个函数的用法基本一致:
PLIST_ENTRY RemoveHeadList(Inout PLIST_ENTRY ListHead)
PLIST_ENTRY RemoveTailList(Inout PLIST_ENTRY ListHead)
函数仅有一个参数,表示所需要移除的链表头节点指针,这两个函数的返回值均为PLIST_ENTRY,成功移除则返回从链表移除的节点指针,如果无节点可以移除(如链表为空)则返回NULL。
在某些情况下,开发者需要移除某个特定节点,即可使用RemoveEntryList函数,函数原型如下:
BOOLEAN RemoveEntryList(In PLIST_ENTRY Entry)
Entry表示需要移除的链表节点指针,RemoveEntryList函数的返回值类型为BOOLEAN,节点被移除后,若链表变为空链表,RemoveEntryList返回TRUE,否则返回FALSE。
最后介绍一下链表状态的判断方法,当链表中只有头节点,没有其他节点时,这个链表就是一个空链表,用IsListEmpty可以判断一个链表是否为空:
BOOLEAN IsListEmpty(In const LIST_ENTRY * ListHead)
ListHead表示链表的头节点指针,IsListEmpty返回TRUE表示链表为空链表,返回FALSE表示链表非空。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值