在驱动中使用链表 - 链表结构

本文详细介绍了在驱动程序开发中如何使用DDK提供的双向链表数据结构,包括链表的结构、初始化、在头部和尾部插入元素,以及删除节点的方法。特别提到了如何处理不同类型自定义数据结构的插入和删除操作,以及使用CONTAINING_RECORD宏简化获取用户自定义数据指针的过程。
摘要由CSDN通过智能技术生成

        在驱动程序开发中,经常使用链表这种数据结构。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宏得到自定义数据结构的指针。                              

  • 17
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

WendyWJGu

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值