凤来凰科技网

否柳暗花明如何移植并使用Linux内核的通用链表(附完整代码实现)预见曾

凤来凰科技网 1

否柳暗花明如何移植并使用Linux内核的通用链表(附完整代码实现)预见曾

在实际的工作中,Space Molly400%的原价是1099元,我们可能会经常使用链表结构来存储数据,二手溢价分别达22.5倍和16.37倍。报道指出,特别是嵌入式,在闲鱼等二手平台上,经常会使用linux内核最经典的双向链表 list_head。

本篇文章详细介绍了Linux内核的通用链表是如何实现的,泡泡玛特MEGA珍藏系列1000%的娃已经叫出了89999元的高价,对于经常使用的函数都给出了详细的说明和测试用例,400%小的,并且移植了Linux内核的链表结构,也卖到了1.8万元一个。泡泡玛特董事长王宁在不久前的半年报业绩会上表示,在任意平台都可以方便的调用内核已经写好的函数。建议收藏,“受欢迎程度超出预期。MEGA系列将是后续泡泡玛特的重点IP。”MEGA珍藏系列在8月中旬发售后的火热似乎也让这只沉寂了许久的“盲盒第一股”在二级市场有所回暖,以备不时之需!

1. 链表简介

1.1 单链表

1.2 双链表

1.3 循环链表

2. Linux内核中的链表

2.1 链表的定义

2.2 链表的初始化

2.3 链表增加节点

2.4 链表删除节点

2.5 链表替换节点

2.6 链表删除并插入节点

2.7 链表的合并

2.8 链表的遍历

3. 疑惑解答

4. list.h移植源码

5. 总结

1. 链表简介

  链表是一种常用的组织有序数据的数据结构,公司股价自近期低点8月23日至9月07日反弹约20%。但该股自今年2月17日点以来,它通过指针将一系列数据节点连接成一条数据链,市值近乎腰斩,是线性表的一种重要实现方式。

  相对于数组,股价跌幅超40%。这家曾经股票市值近千亿,链表具有更好的动态性,建立链表时无需预先知道数据总量,可以随机分配空间,可以高效地在链表中的任意位置实时插入或删除数据。

  通常链表数据结构至少应包含两个域:数据域和指针域,数据域用于存储数据,指针域用于建立与下一个节点的联系。按照指针域的组织以及各个节点之间的联系形式,链表又可以分为单链表、双链表、循环链表等多种类型。

  下面分别给出这几类常见链表类型的示意图:

1.1 单链表

  单链表是最简单的一类链表,它的特点是仅有一个指针域指向后继节点(next)。因此,对单链表的遍历只能从头至尾(通常是NULL空指针)顺序进行

1.2 双链表

  通过设计前驱和后继两个指针域,双链表可以从两个方向遍历,这是它区别于单链表的地方。

  如果打乱前驱、后继的依赖关系,就可以构成"二叉树";如果再让首节点的前驱指向链表尾节点、尾节点的后继指向首节点,就构成了循环链表;如果设计更多的指针域,就可以构成各种复杂的树状数据结构。

双链表1.3 循环链表

  循环链表的特点是尾节点的后继指向首节点。前面已经给出了双链表的示意图,它的特点是从任意一个节点出发,沿两个方向的任何一个,都能找到链表中的任意一个数据。如果去掉前驱指针,就是单循环链表。

循环链表

2. Linux内核中的链表

  上面介绍了普通链表的实现方式,可以看到数据域都是包裹在节点指针中的,通过节点指针访问下一组数据。

  但是 Linux内核的链表实现可以说比较特殊,只有前驱和后继指针,而没有数据域。链表的头文件是在include/list.h(Linux2.6内核)下。在实际工作中,也可以将内核中的链表拷贝出来供我们使用,就需不要造轮子了。

2.1 链表的定义

  内核链表只有前驱和后继指针,并不包含数据域,这个链表具备通用性,使用非常方便。因此可以很容易的将内核链表结构体包含在任意数据的结构体中,非常容易扩展。我们只需要将链表结构体包括在数据结构体中就可以。下面看具体的代码。

内核中的链表

  内核链表的结构

  当需要用内核的链表结构时,只需要在数据结构体中定义一个类型的结构体成员对象就可以。这样,我们就可以很方便地使用内核提供给我们的一组标准接口来对链表进行各种操作。我们定义一个学生结构体,里面包含学号和数学成绩。结构体如下:

2.2 链表的初始化

2.2.1 内核实现

2.2.2 说明

   和都可以初始化链表,二者的区别如下:   初始化链表时会顺便创建链表对象

   初始化链表时需要我们已经有了一个链表对象

  我们可以看到链表的初始化其实非常简单,就是让链表的前驱和后继都指向了自己

2.2.3 举例

2.3 链表增加节点

2.3.1 内核实现

2.3.2 说明

  为头插法,即在链表头(head节点)前插入节点。最后打印的时候,先插入的先打印,后插入的后打印。

  例如原链表为1->2->3,使用插入4后变为,4->1->2->3。因为链表时循环的,而且通常没有首尾节点的概念,所以可以把任何一个节点当成head

  同理,为尾插法,即在链表尾(head节点)插入节点。最后打印的时候,先插入的后打印,后插入的先打印。

  例如原链表为1->2->3,使用插入4后变为,1->2->3->4。

2.3.3 举例

测试结果2.4 链表删除节点2.4.1 内核实现

2.4.2 说明

  链表删除之后,entry的前驱和后继会分别指向和,这个是内核设置的一个区域,但是在本例中将其置为了。

2.4.3 举例

测试结果2.5 链表替换节点2.5.1 内核实现

2.5.2 说明

  使用新的节点替换旧的节点。

  与不同之处在于,会将旧的节点重新初始化,让前驱和后继指向自己。

2.5.3 举例

测试结果2.6 链表删除并插入节点2.6.1 内核实现

2.6.2 说明

  函数实现的功能是删除list指向的节点,同时将其以头插法插入到head中。和功能类似,只不过是将list节点插入到了head的尾

2.6.3 举例

测试结果2.7 链表的合并2.7.1 内核实现

2.7.2 说明

  完成的功能是合并两个链表。

  假设当前有两个链表,表头分别是和(都是变量),当调用时,只要非空,链表的内容将被挂接在链表上,位于和(原表的第一个节点)之间。新链表将以原表的第一个节点为首节点,而尾节点不变。

合并两个链表

  和类似,只不过在合并完之后,调用将list设置为空链。

2.7.3 用例

测试结果2.8 链表的遍历2.8.1 内核实现

2.8.2 说明

  可以得到节点结构体的地址,得到地址后就可以对结构体中的元素进行操作了。依靠函数,内核链表的增删查改都不需要知道结构体所嵌入式的对象,就可以完成各种操作。

  为什么这里使用来定义结构体呢,下面会详细解释

  得到的是结构体中第一个元素的地址

  是用来正向遍历链表的,pos相当于一个临时的节点,用来不断指向下一个节点。

  和是用来倒着遍历链表的。

  和,这两个函数是为了避免在遍历链表的过程中因pos节点被释放而造成的断链。这个时候就要求我们另外提供一个与pos同类型的指针n,在for循环中暂存pos下一个节点的地址。(内核的设计者考虑的真是全面!)

  用于准备一个结构体的首地址,用在中。

  从当前pos的下一个节点开始继续遍历剩余的链表,不包括pos.如果我们将pos、head、member传入,此宏将会从链表的头节点开始遍历。

  从当前的pos的前一个节点开始继续反向遍历剩余的链表,不包括pos。

  从pos开始遍历剩余的链表。

  从pos节点的下一个节点开始遍历剩余的链表,并防止因删除链表节点而导致的遍历出错。

  从pos节点开始继续遍历剩余的链表,并防止因删除链表节点而导致的遍历出错。其与的不同在于在第一次遍历时,pos没有指向它的下一个节点,而是从pos开始遍历。

  从pos的前一个节点开始反向遍历一个链表,并防止因删除链表节点而导致的遍历出错。

  返回当前pos节点的下一个节点的type结构体首地址。

2.8.3 举例

测试结果

  例子就不都写出来了,感兴趣的可以自己试试。

3. 疑惑解答

  之前我们定义结构体的时候是把 放在首位的,当使用遍历的时候,pos获取的位置就是结构体的位置,也就是链表的位置。如下所示。

  但是当我们把放在最后时,pos获取的显然就已经不是链表的位置了,那么当我们再次调用时就会出错。

  这个函数表示在遍历的时候获取entry,该宏中的pos类型为容器结构类型的指针,这与前面中的使用的类型不再相同(这也就是为什么我们上面会分别定义pos1和pos2的原因了)。

  不过这也是情理之中的事,毕竟现在的pos,我要使用该指针去访问数据域的成员age了;head是你使用初始化的那个对象,即头指针,注意,不是头结点;member就是容器结构中的链表元素对象。使用该宏替代前面的方法。这个时候就要用到这个宏了。(再一次感叹内核设计者的)。

  关于宏将在下一篇文章详细介绍,这里先知道如何使用就可以。

4. list.h移植源码

  这里需要注意一点,如果是在GNU中使用GCC进行程序,可以不做更改,直接使用上面的函数即可;但如果你想把其移植到Windows环境中进行使用,可以直接将prefetch语句删除即可,因为prefetch函数它通过对数据手工预取的方法,减少了读取延迟,从而提高了性能,也就是prefetch是GCC用来提高效率的函数,如果要移植到非GNU环境,可以换成相应环境的预取函数或者直接删除也可,它并不影响链表的功能。

5. 总结

  今天的文章就分享到这里,下一篇文章详细介绍内核中的 container_of宏的实现原理。

  微信公众号近期改变了推送规则,如果您想经常看到我的文章,可以选择【置顶】【星标】公众号。在每次阅读后,在页面下方点一个【】或【在看】,这样,每次推送的文章才会第一时间出现在您的订阅列表里。

end

ios anr是什么意思

linux中cd 是什么权限

华为内置路由器怎么用

属牛女属兔男的婚姻怎么样

做梦差点被人抓住什么意思

抖音上给男孩子起名字叫什么

古董局中局1哪年拍的

微信视频号如何看时间

广东网络seo优化资费

免责声明:文中图片均来源于网络,如有版权问题请联系我们进行删除!

标签:linux内核 代码 初始化