测试方法准备

  • 由于需要在内核中进行代码测试验证,完整编译安装内核比较耗时耗力。准备采用module形式来验证。

Makefile

obj-m:=linked-list.oKERNELBUILD:=/lib/modules/$(shell uname -r)/builddefault:make -C ${KERNELBUILD} M=$(shell pwd) modules
clean:rm -rf *.o *.cmd *.ko *.mod.c .tmp_versions

linked-list.c

#include <linux/module.h>
#include <linux/init.h>
#include <linux/list.h>int linked_list_init(void)
{printk("%s\n", __func__);return 0;
}void linked_list_exit(void)
{printk("%s\n", __func__);
}module_init(linked_list_init);
module_exit(linked_list_exit);
MODULE_AUTHOR("Arnold Lu");
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Linked list test");

安装module

sudo insmod linked-list.ko

查找安装情况

lsmod | grep linked-list

执行log

<4>[621267.946711] linked_list_init
<4>[621397.154534] linked_list_exit

删除module

sudo rmmod linked-list

链表、双向链表、无锁链表

Linked list, doubly linked list, lock-free linked list.
  • 链表是一种常用的组织有序数据的数据结构,它通过指针将一系列数据节点连接成一条数据链,是线性表的一种重要实现方式。相对于数组,链表具有更好的动态性,建立链表时无需预先知道数据总量,可以随机分配空间,可以高效地在链表中的任意位置实时插入或删除数据。链表的开销主要是访问的顺序性和组织链的空间损失。

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

  • 通过设计前驱和后继两个指针域,双链表可以从两个方向遍历,这是它区别于单链表的地方。如果打乱前驱、后继的依赖关系,就可以构成"二叉树";如果再让首节点的前驱指向链表尾节点、尾节点的后继指向首节点(如图2中虚线部分),就构成了循环链表;如果设计更多的指针域,就可以构成各种复杂的树状数据结构。

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

资料直通车:Linux内核源码技术学习路线+视频教程内核源码

学习直通车:Linux内核源码内存调优文件系统进程管理设备驱动/网络协议栈

Simple doubly linked list

数据结构:

struct list_head {struct list_head *next, *prev;
};

声明和初始化:

  • static inline void INIT_LIST_HEAD(struct list_head *list)

在表头插入和在表尾插入:

  1. static inline void list_add(struct list_head *new, struct list_head *head)

  2. static inline void list_add_tail(struct list_head *entry, struct list_head *head)

  • 删除,被删除的节点prev、next分别被设为LIST_POISON2、LIST_POISON1,当访问此节点时会引起叶故障。保证不在链表中的节点项不可访问。

  1. static inline void list_del(struct list_head *entry)

  2. static inline void list_del_init(struct list_head *entry) 将entry从链表解下来,重新初始化,就可以访问节点。

  • 将节点从一个链表搬移到另一个链表,根据插入表头和表位分两种:

  1. static inline void list_move(struct list_head *list, struct list_head *head)

  2. static inline void list_move_tail(struct list_head *list, struct list_head *head)

用新节点替换纠结点:

  • static inline void list_replace(struct list_head *old, struct list_head *new)

将list插入到head:

  1. static inline void list_splice(const struct list_head *list, struct list_head *head)

  2. static inline void list_splice_tail(struct list_head *list, struct list_head *head)

  3. static inline void list_splice_init(struct list_head *list, struct list_head *head) 将list设为空链表

  4. static inline void list_splice_tail_init(struct list_head *list, struct list_head *head) 将list设为空链表

  • static inline void list_cut_position(struct list_head *list, struct list_head *head, struct list_head *entry)

遍历宏:

  1. list_entry(ptr, type, member)

  2. list_first_entry(ptr, type, member)

  3. list_last_entry(ptr, type, member)

  4. list_next_entry(pos, member)

  5. list_prev_entry(pos, member)

  6. list_for_each(pos, head)

  7. list_for_each_prev(pos, head) 反向操作

  8. list_for_each_safe(pos, n, head) 安全操作

  9. list_for_each_entry(pos, head, member) 遍历链表是获取链表节点

  10. list_for_each_entry_safe(pos, n, head, member) 安全操作

  11. list_for_each_entry_reverse(pos, head, member) 反向操作

判断链表是否为空:

  • static inline int list_empty(const struct list_head *head)

Doubly linked list with a single pointer list head

  • linux内核里边除了著名的list双向循环链表以外,还有一个重要的数据结构,就是哈希链表。哈希链表也在很多重要的地方有所使用,比如linux内核的dentry,进程查询,文件系统等,可以说,弄明白hlist对于理解linux内核具有重要的意义。

struct hlist_head {struct hlist_node *first;
};struct hlist_node {struct hlist_node *next, **pprev;
};
  • linux内核的hash链表有两个数据结构组成,一个是hlist_head是hash表的表头,一个是hlist_node是hash标的后续节点。

  • 在使用的时候,一般定义一个struct hlist_head xxx[100]数组(100只是一个代表的数字,视具体情况而定),采取哈希函数来将键值与数组的对应的地址联系起来,如果出现冲突的话,就在hlist_head的后边继续添加。 hlist_head的成员first指针指向后续的第一个节点,如果哈希链表是空的话,就为NULL。

  • 为什么hlist_head不弄成双向链表呢,因为为了节约空间,如果一个指针的话,一个哈希数组的空间消耗就会减半。

  • hlist_node的成员next指向后续的节点的地址,如果为空就是NULL,另一个成员pprev是二级指针,指向前一个节点的next成员的地址,如果前一个成员是hlist_head的话,pprev的值就是前一个的first指针的地址。

#define HLIST_HEAD(name) struct hlist_head name = { .first = NULL } 定义并且初始化。

#define INIT_HLIST_HEAD(ptr) ((ptr)->first = NULL) 在定义之后,需要初始化,不然使用会导致错误。

static inline void INIT_HLIST_NODE(struct hlist_node *h) 初始化node节点

static inline int hlist_empty(const struct hlist_head *h) 判断hash链表是否为空

static inline void hlist_del(struct hlist_node *n) 删除节点,并且将节点next、pprev指针修改为LIST_POSITION1和LIST_POSITION2。

static inline void hlist_del_init(struct hlist_node *n) 此种方法更安全,删除然后再初始化节点。

static inline void hlist_add_head(struct hlist_node *n, struct hlist_head *h) 将节点插入到hash链表的头结点后边。

static inline void hlist_add_before(struct hlist_node *n, struct hlist_node *next) 将一个节点插入到next前面。

static inline void hlist_add_behind(struct hlist_node *n, struct hlist_node *prev) 将一个节点插入到prev后面。

遍历访问节点: hlist_for_each(pos, head) hlist_for_each_safe(pos, n, head) #define hlist_entry(ptr, type, member) container_of(ptr,type,member) hlist_entry_safe(ptr, type, member) hlist_for_each_entry(pos, head, member) hlist_for_each_entry_safe(pos, n, head, member)

Lock-less NULL terminated single linked list

  • 数据结构如下:

struct llist_head {struct llist_node *first;
};struct llist_node {struct llist_node *next;
};

#define LLIST_HEAD(name) struct llist_head name = LLIST_HEAD_INIT(name)

static inline void init_llist_head(struct llist_head *list) llist_entry(ptr, type, member) llist_for_each(pos, node)

static inline bool llist_empty(const struct llist_head *head)

static inline struct llist_node *llist_next(struct llist_node *node)

static inline bool llist_add(struct llist_node *new, struct llist_head *head) bool llist_add_batch(struct llist_node *new_first, struct llist_node *new_last, struct

llist_head *head) static inline struct llist_node *llist_del_all(struct llist_head *head)

struct llist_node *llist_del_first(struct llist_head *head)

  • llist_add、llist_add_batch、llist_del_first都是基于cmpxchg原子操作来实现,整个操作是原子的;llist_del_all是基于xchg来实现的。

  • cmpxchg(void* ptr, int old, int new),如果ptr和old的值一样,则把new写到ptr内存,否则返回ptr的值,整个操作是原子的。在Intel平台下,会用lock cmpxchg来实现,这里的lock个人理解是锁住内存总线,这样如果有另一个线程想访问ptr的内存,就会被block住。

B+树

A relatively simple B+Tree implementation. I have written it as a learning exercise to understand how

B+Trees work. Turned out to be useful as well. ... A tricks was used that is not commonly found in textbooks. The lowest values are to the right, not to the left.

All used slots within a node are on the left, all unused slots contain NUL values. Most operations

simply loop once over all slots and terminate on the first NUL.

B树诞生的背景:

在大规模数据存储中,实现索引查询这样一个实际背景下,树节点存储的元素数量是有限的,这样就会导致二叉树结构由于树的深度过大而造成磁盘I/O读写过于频繁,进而导致查询效率低下。 那么如何减少树的深度,一个基本的想法是采用多叉树结构。 因为磁盘的操作费时费资源,那么如何提高效率,即如何避免频繁的读取呢?根据磁盘查找存取的次数往往由树的高度决定,所以只要通过较好的结构降低树的高度。根据平衡二叉树的启发,自然就想到平衡多叉树结构。

几个算法时间复杂度度量:

O(n) 表示某函数值(未列出)是 n 的常数倍;亦即他们增长的速度相当.称 大O,big O (发音 "欧" 英文字母 O ) 同理:O(logN):是 logN 的常数倍;O(nlogn):是 nlogn 的常数倍

优先排序列表

  • plist有两个重要结构体struct plist_head和struct plist_node,分别用来表示plist表头和plist节点。

struct plist_head {struct list_head node_list;
};struct plist_node {int prio;struct list_head prio_list;struct list_head node_list;
};

相关函数:

  1. PLIST_HEAD(head) 初始化plist表头

  2. PLIST_NODE_INIT(node, __prio) 初始化plist节点

  3. static inline void plist_head_init(struct plist_head *head) 初始化plist表头

  4. static inline void plist_node_init(struct plist_node *node, int prio) 初始化plist节点

添加节点、删除节点:

  1. extern void plist_add(struct plist_node *node, struct plist_head *head); 通过plist_add添加到head的node是按照prio优先级由高到低顺序在node_list上排列。

  2. extern void plist_del(struct plist_node *node, struct plist_head *head);

  3. extern void plist_requeue(struct plist_node *node, struct plist_head *head); 是plist_del的优化版本

遍历plist:

  • plist_for_each(pos, head)

判断head是否为空:

  • static inline int plist_head_empty(const struct plist_head *head)

判断当前node是否在node_list上:

  • static inline int plist_node_empty(const struct plist_node *node)

获取前一、后一节点:

  1. plist_next(pos)

  2. plist_prev(pos)

获取首节点、尾节点:

  1. static inline struct plist_node *plist_first(const struct plist_head *head)

  2. static inline struct plist_node *plist_last(const struct plist_head *head)

下面是对plist进行的一些验证:

static dump_list(void)
{struct plist_node *node_pos, *first_node, *last_node;int i;printk(KERN_DEBUG "%s start\n", __func__);printk("node_list: ");list_for_each_entry(node_pos, &test_head.node_list, node_list) {printk("%d ", node_pos->prio);}printk("\n");first_node = plist_first(&test_head);last_node = plist_last(&test_head);printk("prio_list: %d ", first_node->prio);list_for_each_entry(node_pos, &first_node->prio_list, prio_list) {printk("%d ", node_pos->prio);}printk("\n");#if 0for (i = 0; i < ARRAY_SIZE(test_node); i++) {if(!plist_node_empty(test_node+i))printk(KERN_DEBUG "(test_node+%d)->prio=%d\n", i, (test_node+i)->prio);}
#endifprintk(KERN_DEBUG "MIN(prio)=%d MAX(prio)=%d\n", first_node->prio, last_node->prio);printk(KERN_DEBUG "%s end\n", __func__);
}static int  __init plist_test(void)
{int nr_expect = 0, i, loop;unsigned int r = local_clock();printk(KERN_DEBUG "start plist test\n");plist_head_init(&test_head);for (i = 0; i < ARRAY_SIZE(test_node); i++)plist_node_init(test_node + i, 0);for (loop = 0; loop < 10; loop++) {r = r * 193939 % 47629;i = r % ARRAY_SIZE(test_node);if (plist_node_empty(test_node + i)) {r = r * 193939 % 47629;test_node[i].prio = r % 10;plist_add(test_node + i, &test_head);nr_expect++;} else {plist_del(test_node + i, &test_head);nr_expect--;}plist_test_check(nr_expect);if (!plist_node_empty(test_node + i)) {plist_test_requeue(test_node + i);plist_test_check(nr_expect);}}dump_list();for (i = 0; i < ARRAY_SIZE(test_node); i++) {if (plist_node_empty(test_node + i))continue;plist_del(test_node + i, &test_head);nr_expect--;plist_test_check(nr_expect);}printk(KERN_DEBUG "end plist test\n");return 0;
}
  • 通过初始化不超过10个node节点,优先级为0-9。然后查看node_list和prio_list两链表的节点情况:

[22050.404475] start plist test

[22050.404481] dump_list start

[22050.404482] node_list: 0 0 1 1 2 6 8 8 9 9

[22050.404486] prio_list: 0 1 2 6 8 9

[22050.404488] MIN(prio)=0 MAX(prio)=9

[22050.404489] dump_list end

[22050.404491] end plist test

[22050.947810] start plist test

[22050.947816] dump_list start

[22050.947817] node_list: 0 1 1 2 2 3 3 3 8 8

[22050.947820] prio_list: 0 1 2 3 8

[22050.947822] MIN(prio)=0 MAX(prio)=8

[22050.947823] dump_list end

[22050.947825] end plist test

[22051.491245] start plist test

[22051.491254] dump_list start

[22051.491256] node_list: 0 1 2 3 3 3 6 9 9 9

[22051.491262] prio_list: 0 1 2 3 6 9

[22051.491266] MIN(prio)=0 MAX(prio)=9

[22051.491267] dump_list end

[22051.491271] end plist test

  • 可以看出node_list上的节点按照优先级由高到低排序,优先级可能会重复;在prio_list上是不同优先级的节点。如下所示:

* pl:prio_list (only for plist_node)

* nl:node_list *HEAD| NODE(S)

* | * ||------------------------------------|

* ||->|pl|<->|pl|<--------------->|pl|<-|

* | |10| |21| |21| |21| |40| (prio)

* | | | | | | | | | | |

* | | | | | | | | | | |

* | ->|nl|<->|nl|<->|nl|<->|nl|<->|nl|<->|nl|<-|

* |-------------------------------------------------|

红黑树

Red-Black treesareusedfor scheduling, virtual memory management, to track file descriptors and directory entries,etc.

一文带你了解。你不知道的Linux内核中的算法和数据结构。相关推荐

  1. Linux内核中的算法和数据结构

    算法和数据结构纷繁复杂,但是对于Linux Kernel开发人员来说重点了解Linux内核中使用到的算法和数据结构很有必要. 在一个国外问答平台stackexchange.com的Theoretica ...

  2. 一文带你深入了解Linux IIO 子系统

    [推荐阅读] 一文剖析Linux内核中内存管理 分析linux启动内核源码 关于如何快速学好,学懂Linux内核.内含学习路线 工业场合里面也有大量的模拟量和数字量之间的转换,也就是我们常说的 ADC ...

  3. 【技术分享篇】Linux内核——手把手带你实现一个Linux内核文件系统丨Linux内核源码分析

    手把手带你实现一个Linux内核文件系统 1. 内核文件系统架构分析 2. 行行珠玑,代码实现 [技术分享篇]Linux内核--手把手带你实现一个Linux内核文件系统丨Linux内核源码分析 更多L ...

  4. linux内核内存屏障,从硬件引申出内存屏障,带你深入了解Linux内核RCU

    本文简介 本文从硬件的角度引申出内存屏障,这不是内存屏障的详尽手册,但是相关知识对于理解RCU有所帮助.这不是一篇单独的文章,这是<谢宝友:深入理解Linux RCU>系列的第2篇,前序文 ...

  5. Linux 内核中的 GCC 特性(zz)

    from:http://www.ibm.com/developerworks/cn/linux/l-gcc-hacks/ GCC 和 Linux 是出色的组合.尽管它们是独立的软件,但是 Linux ...

  6. Linux 内核中的宏定义

    Linux 内核中的宏定义 rtoax 日期 内核版本:linux-5.10.13 注释版代码:https://github.com/Rtoax/linux-5.10.13 __attribute__ ...

  7. 调皮的程序员:Linux之父雕刻在Linux内核中的故事

    本文内容由公众号"格友"原创分享. 1.引言 (不羁的大神,连竖中指都这么帅) 因为LINUX操作系统的流行,Linus 已经成为地球人都知道的名人.虽然大家可能都听过钱钟书先生的 ...

  8. Linux内核中max()宏的奥妙何在?(一)

    Linux内核中max()宏的奥妙何在?(一) 1.max()宏那点事 在Linux内核中,有这样四个比较大小的函数,如下: max(x,y) //两个数求最大值 min(x,y) //两个数求最小值 ...

  9. Linux内核中max()宏的奥妙何在?(二)——大神Linus对这个宏怎么看?

    最新max()宏 上回,我们在<Linux内核中max()宏的奥妙何在?(一)>一文中说到,在3.18.34版Linux内核源码中的max()宏,采用了GCC的扩展特性,可以避免一些错误. ...

最新文章

  1. 解决安卓系统写入SD卡权限问题
  2. 朴素贝叶斯分类器详解及中文文本舆情分析(附代码实践)
  3. c语言 宏 变长参数,科学网—C/C++中处理变长参数函数(Variadic Function)的几个宏 - 彭彬的博文...
  4. 0403互联网新闻 | 哔哩哔哩上线电商类小程序;翼鸥教育和腾讯云联合发布ClassIn Cloud...
  5. 北邮OJ 102. 最远距离 北邮2012网研院复试上机题
  6. C# js调用winform方法,C# JS与winform通信
  7. 一些来自STL的好东西
  8. 听 Fabien Potencier 谈Symfony2 之 《What is Symfony2 ?》
  9. 工资低是浪费生命?俞敏洪:当你工资比同学少一半 生命已经浪费一半
  10. 数据库双机热备(代码实现)
  11. 正则表达式(思维导图速查版)
  12. sql字段合并mysql_sql合并字段
  13. springboot 打 jar 包分离依赖 lib 和 配置文件
  14. 大数据实效_新都区:运用“大数据”提升专项巡察实效
  15. 嵌入式相关开源项目、库、资料
  16. 将CNKI的caj格式下载成为pdf
  17. 往十年不忘初心,新十年不负韶华丨万字长文带你完整回顾2020数据技术嘉年华...
  18. 北大英语系计算机系,北京大学
  19. 无刷直流电机 PWM控制
  20. 智能(语音)对话系统架构研究

热门文章

  1. Windows中rabbitmq的可视化界面,其实很简单!
  2. 【Ini格式文件】Ini格式文件的读写
  3. 现代软件企业竞争环境
  4. 基于冯洛伊曼拓扑的鲸鱼算法用于滚动轴承的故障诊断研究(Matlab代码实现)
  5. 由浅到深玩转Python爬虫(一)初识爬虫
  6. demo 记录 通用方法什么的到这里抄一下
  7. 简话消融研究是什么意思
  8. js 获取当前天的前几天或者后几天日期
  9. Speedoffice(word)文档如何操作上标和下标?
  10. java和js中替换字符串的方法