概述
链表,队列,映射,二叉树等数据结构是程序设计中常用的数据结构。为了统一这些数据结构的操作接口,Linux内核开发者实现了一些标准的操作接口及实现(使用了大量的GNU扩展特性),以达到代码重用,开发者应该尽量使用这些标准接口,避免实现自己的再创造,虽然那样看起来很酷,很有劲。
有关链表
传统的双向链表实现方法是在链表元素中加入两个指针,然后用这些指针来构造双向链表。如下所示
其示意图如下:NULL为空指针。
如果将双向链表中的首尾两个元素进行链接,则会形成循环双向链表。示意图如下:
由上可以看出,如果想得到链表中某个指定节点,必须要遍历链表。所以,对于那些需要随机存取的数据,尽量使用数组,而不是链表,当然也可以配合一个哈希表来使用链表,有兴趣的同志可以看看。
上面的实现方法没有问题,但是对于内核来说,如果每个内核对象都采用这种方法,那么要为每个结构体添加相应代码,还要实现其链表操作函数。这样很麻烦,而且也不能达到代码复用以及提供统一的接口。所以内核开发者采用了另外一种巧妙的方法:声明list_head这么一个结构,然后只要嵌入这么一个数据结构就可以实现双向链表了。
假设你想以链表形式存储自己的课程与成绩,则可以采用下面的形式
这样,就利用成员变量list将所有的链表节点连接起来,当然,一般还要设置一个头结点。
除此之外,开发者还提供了一些函数和宏用于链表操作。如使用container_of()可以通过list成员获得course_score结构体的地址。
宏container_of使用了GNU扩展。分析一下这个宏定义,首先注意ptr为指向容器结构体的指针,type为容器结构体的类型,而member则是其内嵌的list_head成员变量的名称,如上例,type为course_score,而member则为list。
(type *)0将地址0转换为结构type的地址
(type *)0->member获取type机构体中member成员变量的偏移地址。由于容器结构体的地址为0,这就是member成员的偏移地址,所以宏offsetof也就是这个作用。
typeof( ((type *)0->member)返回member成员的类型,然后将指针_mptr声明为该类型的指针,并赋值为ptr。
(type *)((char *)_mptr - offsetof(type, member));})则根据member成员的实际_mptr以及偏移量offsetof()则可以得到容器结构体的地址。
当有两种方法可以初始化链表,静态初始化和动态初始化。
如果在编码时初始化链表,则可以使用宏LIST_HEAD_INIT,如上例中,则可以
如果是运行时初始化链表,则可以使用宏INIT_LIST_HEAD或者内联函数INIT_LIST_HEAD来初始化,两者功能一样,只是内联函数提供了类型检测。如下所示
链表的其他操作包括添加、删除、合并、遍历等。
插入节点
插入操作有两种:表头插入和表尾插入。实际上,两种插入的方法是一样的,只是内部函数调用时,参数不同而已。
删除节点
对 LIST_POISON1,LIST_POISON2 的解释:
These are non-NULL pointers that will result in page faults under normal circumstances, used to verify that nobody uses non-initialized list entries.
移动节点
链表合并
将一个非空链表插入到另外一个链表中。先做链表是否为空的检查,因为每个链表只有一个头节点,将空链表插入到另外一个链表中是没有意义的。但被插入的链表可以是空的。两个链表有两个头结点,在函数中要去掉一个头结点。
链表遍历
prefetch为预取函数,提前预取下一指令,能提高程序执行速度。
最后
以上就是精明香菇为你收集整理的Linux内核数据结构(一)的全部内容,希望文章能够帮你解决Linux内核数据结构(一)所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复