欢迎光临散文网 会员登陆 & 注册

内核中的链表及相应的API

2023-02-21 16:12 作者:秋名山西  | 我要投稿

1、内核中的链表节点表示

在内核中,一个驱动管理程序负责管理多个设备,如果需要在驱动程序中跟踪每个设备,这就需要链表。链表有两种类型:

● 单链表

● 双链表

然而,内核中因为种种原因,只实现了双链表,这种结构可以实现FIFO(先进先出)和LIFO(后进先出).如需要使用内核的链表支持,只需引用头文件<linux/list.h>,核心部分的数据结构如下:

在内核中,这个结构体用在链表头结点和链表普通节点中。也就是说,在内核中使用结构体表示链表时,必须在该结构中添加struct list_head 的实例。举个例子,比如说学生成绩的结构体为:

要为其创建链表,就需要在该结构体中添加一个struct list_head的实例。变成这个样子:

如此这般,当我们知道某个链表节点中struct list_head的指针时,便可以使用list.c中提供的宏list_entry方法来获取该节点struct student的地址。如果有时间看代码的话,其实list_entry是通过调用宏container_of来实现通过结构体成员变量的指针获取结构体的地址。这里顺手给出container_of的使用方法:

2、内核中的链表操作

上面的论述中,我们已经清楚了内核中的链表节点的表现形式,接下来我们再学习内核中如何对链表进行创建、销毁以及增删查改等操作。

2.1链表头节点的创建(链表并不一定有头结点)

    通过阅读代码,我发现内核中所创建的链表中,其创建方式分为静态创建和动态创建两种。

有struct list_head可知,头结点都是没有数据域的,仅仅只有指针域,因此初始化时头结点的指针域都指向其自身。那使用INIT_LIST_HEAD和LIST_HEAD的场景有什么区别呢?

我的理解是:当需要初始化的struct list_head实例是独立的且不是嵌入在其他结构体中时,使用这两种方法初始化都是可以的,这是因为此时的struct list_head并没有进行实例化,也就没有开辟对应的内存空间。但是若struct list_head已经嵌入到其他结构体中,它的内存空间就会随着父结构体的开辟而开辟,这时再继续使用LIST_HEAD就不合适了。

简单来说,INIT_LIST_HEAD与LIST_HEAD的区别就是内存空间的开辟,前者不具备开辟内部才能空间的功能,但后者有。

2.2创建普通节点

创建普通节点,只需要创建结构体实例,并初始化嵌入在其中的struct list_head字段即可。代码如下:

这里创建的新节点,也是将struct list_head字段的指针初始化为指向自己本身,是为了避免出现内存泄漏。

2.3添加链表节点

内核提供了list_add用于向链表中添加新节点,它包装了内部函数__list_add。原型如下:

除了list_add外,内核还提供了list_add_tail,实现在链表尾端加入新节点,原型如下:

2.4删除链表节点

内核中删除链表节点的代码:

该函数实现了断开指定节点的prev和next指针,节点移除后,其占据的内存需要手动kfree释放。

2.5链表遍历

使用的是list_for_each_entry宏进行链表遍历。

示例代码如下:

这段代码可以实现统计链表中liming出现的次数。这个循环终止的条件是astu指向的节点的struct list_head字段的地址与传入节点的struct list_head字段的地址相等,表示链表循环结束。

在这个循环中,链表节点的移动的都是struct list_head的指针来实现。

内核中的链表及相应的API的评论 (共 条)

分享到微博请遵守国家法律