在驱动程序开发中,经常使用链表这种数据结构。DDK为用户提供了两种链表的数据结构,简化了对链表的操作。
链表中可以记录使用整形、浮点、字符型或者程序员自定义的数据结构。链表通过指针将这些数据结构组成一条“链”,链中每个元素对应着记录的数据。对于单向链表,每个元素中有一个Next指针指向下一个元素。对于双向链表,每个元素有两个指针:BLINK指针指向前一个元素,FLINK指向下一个元素。
这里我们主要使用双向链表为主,单向链表的操作类似
1. 在驱动中使用链表 - 链表结构
DDK提供了标准的双向链表。双向链表可以将链表形成一个环。BLINK指针指向前一个元素,FLINK指针指向下一个元素,如图1所示。
以下是DDK提供的双向链表的数据结构,这个数据看起来很是奇怪,只有指向前后的指针,而没有数据。这个问题我们继续往下看。
typedef struct _LIST_ENTRY {
struct _LIST_ENTRY *Flink;
struct _LIST_ENTRY *Blink;
} LIST_ENTRY, *PLIST_ENTRY;
2. 在驱动中使用链表 - 链表初始化
每个双向链表都是以一个链表头作为链表的第一个元素。初始使用链表头需要进行初始化,主要将链表头的Flink和Blink两个指针都指向自己。这意味着链表头所代表的链是空链,如图2所示。初始化链表头用InitializeListHead宏实现。
如何判断链表是否为空,可以判断链表头的Flink和Blink是否指向自己。DDK为程序员提供了一个宏简化这种检测,这就是IsListEmpty。
IsListEmpty(&head);
程序员需要自己定义链表中每个元素的数据类型,并将LIST_ENTRY结构作为自定义结构的一个子域。LIST_ENTRY的作用是将自定义的数据结构串成一个链表。
typedef struct _MYDATASTRUT {
// List Entry 需要作为_MYDATASTRUT结构体的一部分
LIST_ENTRY listEntry;
// 下面是自定义的数据
ULONG x;
ULONG y;
}MYDATASTRUT, *PMYDATASTRUT;
3. 在驱动中使用链表 - 从首部插入链表
对链表的插入方式,一种是在链表头部插入,一种是在链表的尾部插入。
在头部插入链表,使用说明语句InsertHeadList,用法如下:
InsertHeadList(&head, &mydata->ListEntry);
其中,head是LIST_ENTRY结构的链表头,mydata是用户定义的数据结构,而它的子域ListEntry是包含其中的LIST_ENTRY数据结构。
假设链表中已经有一个元素Entry1,如图3所示,现在将另外一个元素Entry2插入链表,用InsertHeadList插入。链表插入Entry2的结构如图4所示。
4. 在驱动中使用链表 - 从尾部插入链表
另外一种插入方式是在链表的尾部进行插入。在尾部插入链表使用语句InsertTailList,用法如下:
InsertTailList (&head, &mydata->ListEntry);
其中,head是LIST_ENTRY结构的链表头,mydata是用户定义的数据结构,而它的子域ListEntry是包含其中的LIST_ENTRY数据结构。
假设链表中已经有一个元素Entry1和Entry2,如图5所示。现在将另外一个元素Entry3插入链表,用InsertTailList插入。链表插入Entry3的结构如图5所示。
5. 在驱动中使用链表 - 从链表删除
和插入链表一样,删除也有两种赌赢的方法,一种是从链表头部删除,一种是从链表尾部删除。分别对应RemoveHeadList和RemoveTailList函数。其使用方法如下:
PLIST_ENTRY pEntry = RemoveHeadList(&head);
及
PLIST_ENTRY pEntry = RemoveTailList(&head);
其中,head是链表头,pEntry是从链表删除下来的元素中的ListEntry.这里有一个问题,就是如何从pEntry得到用户自定义数据结构的指针。这里有以下两种情况。
5.1 当自定义的数据结构的第一个字段是LIST_ENTRY时,如:
typedef struct _MYDATASTRUT {
LIST_ENTRY ListEntry;
ULONG x;
ULONG y;
}MYDATASTRUCT, *PMYDATASTRUT;
此时,RemoveHeadList返回的指针可以当做用户自定义的指针,即:
PLIST_ENTRY pEntry = RemoveHeadList(&head);
PMYDATASTRUT pMyData = (PMYDATASTRUT)pEntry;
5.2 当自定义的数据结构的第一个字段不是LIST_ENTRY时,如:
typedef struct _MYDATASTRUT {
ULONG x;
ULONG y;
LIST_ENTRY ListEntry;
}MYDATASTRUCT, *PMYDATASTRUT;
此时,RemoveHeadList返回的指针不可以当做用户自定义的指针,即:
PLIST_ENTRY pEntry = RemoveHeadList(&head);
PMYDATASTRUT pMyData = (PMYDATASTRUT)pEntry; // 是错误的
此时需要通过pEntry的地址你想算出自定义数据的指针。一般通过pEntry在自定义数据中的偏移量,用pEntry减去这个偏移量,就会得到用户自定义结构的指针的地址。如果图7所示,指针A是自定义数据结构的地址,指针B是自定义数据结构中的pEntry.
为了简化这个操作,DDK为程序员提供了宏CONTAINING_RECORD,其用法如下:
PLIST_ENTRY pEntry = RemoveHeadList(&head);
PIRP pIrp = CONTAINING_RECORD(pEntry,
MYDATASTRUCT,
ListEntry);
CONTAINING_RECORD的第一个参数是相当于是图7中指针B,第二个参数是数据结构的名字,第三个参数是数据结构中的字段,返回的相当于图7中的指针A。
DDK建议无论自定义数据结构的第一个字段是否为ListEntry,最好都使用CONTAINING_RECORD宏得到自定义数据结构的指针。