Linux内核常用数据结构要点
简单总结一下Linux Kernel常用数据结构和选取原则
·
Linux中最重要最常用如下四种:
LIST:链表 <linux/list.h>
- Linux内核的标准链表就是采用“环形、双向”链表形式实现
- 沿着链表移动智能是线性移动
- 需要随机访问的数据,一般不使用链表
- 链表存放数据的理想情况是:需要遍历所有数据、或者需要动态加入/删除数据
- 有时首元素会用一个特殊的指针表示,称为“头指针”,可以方便的找到链表的“起始端”
- Linux内核实现特殊性:不是将数据结构塞入链表,而是将链表结点塞入数据结构
- Linux内核链表操作函数的复杂度都是O(1),而不管链表中元素数目的多少
- list_add\__list_add
- list_add_tail\__list_add_tail
- list_del\__list_del
- list_del_init\__list_del_init
- list_move\__list_move
- list_move_tail\__list_move_tail
- list_empty\__list_empty
- list_entry\__list_entry
- list_splice\__list_splice
- list_splice_init\__list_splice_init
- Linux内核遍历链表的复杂度是O(n),n是链表元素数目
- list_for_each
- list_for_each_entry
- list_for_each_entry_reverse 反向遍历链表,有二原因:①当反向遍历性能好时;②当遍历顺序很重要时;
- list_for_each_entry_safe 该函数允许在遍历链表循环体中删除元素,普通遍历函数不允许这样做
- list_for_each_entry_safe_reverse
- 链表操作函数分为外部函数、内部函数,函数同名,只差双下划线__,内部函数用prev、next参数
FIFO:队列 <linux/kfifo.h>
- Linux内核通用队列实现称为kfifo,实现在文件kernel/kfifo.c中
- 维护两个偏移量:
- 入口偏移(下一次入队时位置)>=出口偏移(下一次出队时位置);
- 入口偏移==出口偏移,说明队列空了;
- 入口偏移==队列长度,说明队列重置前不能再有入队操作;
- 提供两个主要操作:
- 入队enqueue(copy数据到队列入口偏移位置)
- 出队dequeue(从队列出口偏移copy数据)
- 队列操作:
- kfifo_alloc 动态创建、初始化 (size需是2的n次幂)
- kfifo_init 动态创建、初始化,但使用已分配内存 (size同上)
- DECLARE_KFIFO/INIT_KFIFO 静态声明,不常用(size同上)
- kfifo_in 入队
- kfifo_out 出队
- kfifo_out_peek 不出队,只返回数据
- kfifo_size 队列总体空间(长度)
- kfifo_len 队列已用空间
- kfifo_avail 队列剩余空间
- kfifo_is_empty 判定队列空
- kfifo_is_full 判定队列满
- kfifo_reset 重置队列(抛弃所有元素)
- kfifo_free 释放kfifo_alloc创建的队列
映射:
- 称为关联数组,由“键<==>值”对儿组成的集合;
- “键”是唯一的,“值”关联到键;
- 映射操作:
- Add (key, value) 增加
- Remove (key) 删除
- Value = Lookup (key) 查找
- Allocate 插入键值对,产生UID
- Linux内核提供了简单、有效的映射数据结构。但是它并非一个通用的映射;
- Linux内核提供它的目标是:映射一个唯一的标识数(UID)到一个指针; (因此并不适合其它场景)
- idr数据结构:用于映射用户空间的UID
- idr_init 初始化静态/动态分配的idr
- ...
二叉树:
- 树,是一个能提供分层的“树”型数据结构的特定数据结构;
- 树,是一个无环、连接的有向图;
- 树,任何一个节点具有0~n个出边,1个入边;
- 节点深度,是从根节点起到达该节点止一共需要经过的父节点数目;
- 树的高度,是叶子节点的深度;
- 二叉树,任何一个节点最多有2个出边的树,即只能有0、1、2个出边;
- BST:二叉搜索树
- 规则1:根左分支节点值 < 根节点值
- 规则2:根右分支节点值 > 根节点值
- 规则3:所有子树都是二叉搜索树
- 前序遍历:根节点->左子树->右子树
- 中序遍历:左子树->根节点->右子树
- 后序遍历:左子树->右子树->根节点
- 在树中搜索给定值、按序遍历都相当快捷;中序遍历二叉搜索树时会按照节点值从小到大的顺序排序;
- 自平衡二叉搜索树
- 平衡二叉搜索树:所有叶子节点深度差值都不超过1的二叉搜索树
- 自平衡二叉搜索树:操作都试图维持平衡(半平衡)的二叉搜索树
- 红黑树
- 是一种“自平衡二叉搜索树”
- 是Linux主要的平衡二叉搜索树
- 具有特殊的着色属性(或红色、或黑色)
- 能维持半平衡结构,其6属性:
- 属性1:所有节点要么着红色、要么着黑色
- 属性2:叶子节点都是黑色
- 属性3:叶子节点不包含数据
- 属性4:非叶子节点都有2个子节点
- 属性5:如果节点是红色,则它子节点都是黑色
- 属性6:一个节点到该节点的叶子节点的所有路径中,总是包含相同数目的黑色节点;
- 上述属性确保:
- 一个红色节点不能是其它红色节点的子节点(或者父节点),即两个红色节点不能相连;
- 从树的任何一个节点到其叶子节点的路径都具有相同数目的黑色节点;即最长路径是红黑交替路径,最短路径是全黑路径;
- 所以,最深叶子节点深度,不会大于2倍最浅叶子节点深度; 红黑树总是半平衡的;
- rbtree: <linux/rbtree.h>
- Linux实现的红黑树
- 实现在lib/rbtree.c中
- 插入效率和树中节点数目呈现对数关系;(即:时间复杂度与节点数目是对数关系)
- 根节点由数据结构rb_root描述,创建新红黑树,需要分配新的rb_root结构并初始化为RB_ROOT
- 其它节点由数据结构rb_node描述
- 未提供搜索和插入操作函数,需要由用户自己实现(可以使用rbtree提供的辅助函数,但要实现比较操作算子)
其它较少用的:
- radixtree:基数树
数据结构的选择:
- 原则一:遍历操作为主时,优先考虑链表;(没有数据结构能提供比线性算法复杂度更好的算法去遍历元素)
- 原则二:排除性能因素,当需要相对较少数据项时,优先考虑链表;
- 原则三:当需要与其它选择链表的代码交互时,优先考虑链表;
- 原则四:需要大小不明的数据集合,优先选择链表;
- 原则五:代码架构复合"生产者/消费者"模式,优先选择队列;
- 原则六:当需要一个定长的缓冲,选择队列;
- 原则七:如果需要映射一个UID到一个对象,选择映射;
- 原则八:如果需要存储大量数据,并且快速检索,选择红黑树;
更多推荐
已为社区贡献1条内容
所有评论(0)