Linux内核设计与实现 第六章 内核数据结构

2023-10-27

目录

1)单向链表和双向链表

​编辑

​编辑

2)环形链表

3)沿链表移动

4)Linux内核中的实现

5)操作链表

6)遍历链表

6.2队列

1)kfifo

2)创建队列

3)推入队列数据

4)摘取队列数据

5)获取队列数据

6)重置和撤销队列

6.3映射

1)初始化一个idr

​编辑2)分配一个新的UID

3)查找UID

4)删除UID

5)撤销idr

6.4二叉树

1)二叉搜索树

2)自平衡二叉搜索树

6.5数据结构以及选择

6.6算法复杂度

1)算法

2)大O符号

3)大θ符号

4)时间复杂度


6.1链表

链表和静态数组的不同之处在于,它所包含的元素都是动态创建并插入链表的,在编译时不必知道具体需要创建多少元素。


1)单向链表和双向链表

2)环形链表

 


3)沿链表移动

沿链表移动是先访问某个元素,然后沿该元素的向前或向后指针访问下一个元素。
链表存放数据的理想情况是:需要遍历所有数据或需要动态加入和删除数据时。


4)Linux内核中的实现

在Linux2.1时官方明确了内核链表的实现,从此在Linux内核只能用官方实现的针对链表的一组函数。

 //list_head与一个具体的数据结构fox:
struct list_head{
       struct list_head *next;
       struct list_head *prev;
}
struct fox{
       unsigned long tail_length;//尾巴长度
       unsigned long weight;//重量
       bool is_fantastic;//是否是狐狸
       struct list_head list;
}
//list_head嵌入一个具体的数据结构fox:
struct fox{
       unsigned long tail_length;//尾巴长度
       unsigned long weight;//重量
       bool is_fantastic;//是否是狐狸
       struct list_head list;
}
链表头:指链表的头指针


5)操作链表

内核提供了一组函数来操作链表。
list_add(要插入的节点的指针,链表头)//向链表中加入一个节点。
list_del(要删除的节点的指针)//从链表中删除一个节点。(节点结构体有链表头数据)
list_move(源链表的链表头,目的链表的链表头)//把节点从一个链表的链首移动到另一个链表的链首。
list_move_tail(源链表的链表头,目的链表的链表头)//把节点从一个链表的链尾移动到另一个链表的链尾。
list_empty(链表头)//检查链表是否为空
list(源链表的链表头,目的链表的链表头)//把源链表插入到目的链表的链表头后面


6)遍历链表

list_for_each(临时指针变量,遍历的链表的链表头)//运行list_for_each()临时指针变量会从链表头,向下移动到链表尾。临时指针变量就像光标
list_for_each_entry(临时指针变量,遍历的起始位置,遍历的链表的链表头)//从的链表的起始位置正向的遍历
list_for_each_entry_reverse(临时指针变量,遍历的起始位置,遍历的链表的链表头)//从的链表的起始位置反向的遍历
list_for_each_entry(临时指针变量,要删除的节点的指针,遍历的起始位置,遍历的链表的链表头)//从的链表的起始位置正向的遍历,同时删除指定节点
如果出现并发进行删除链表或者并发进行链表操作,就需要锁定链表。第9章和第10章会讨论同步和锁
更多Linux提供的链表操作去看看头文件<linux/list.h>


6.2队列

任何操作系统内核都少不了一种编程模型:生产者和消费者。
生产者将数据推入队列,消费者从队列中摘取出数据。原则是先进先出first-in first-out(FIFO)
Linux内核通用队列实现称为kfifo(kernel first-in first-out)。


1)kfifo

Linux内核通用队列kfifo,维护了两个偏移量:a)入口偏移:指下一次入队列的位置 b)出口偏移:指下一次出队列时的位置。
enqueue操作拷贝数据到队列中的入口偏移位置。
dequeue操作从队列中出口偏移出拷贝数据。
出口偏移总是小于等于入口偏移,否则无意义,因为那样说明要出队列的元素根本还没有入队列。

2)创建队列

//动态创建并初始化
int kfifo_alloc(struct kfifo,unsigned int size,gfp_t gfp_mask)//创建并初始化一个大小为size的kfifo,内核使用gfp_mask标识分配队列
//静态创建并初始化
DECLARE_KFIFO(name,size)//创建一个名称为name,大小为size的Linux内核通用队列kfifo。
INIT_KFIFO(name)                //初始化名称为name的Linux内核通用队列kfifo。


3)推入队列数据

unsigned int kfifo_in(struct kfifo *fifo,const *from,unsigned int len)//把form指针所指的len个字节的数据拷贝到fifo所指的队列中


4)摘取队列数据

//kfifo对象维护了两个offset:in offset和out offset。in offset指示了下次入队的位置,而out offset指示了下次出队的位置。
unsigned int kfifo_out(struct kfifo *fifo,void *to,unsigned int len)//从fifo所指向的队列中拷贝出长度为len字节的数据到to所指的缓冲中。
unsigned int kfifo_out_peek(struct kfifo *fifo,void *to,unsigned int len,unsigned offset)//从fifo所指向的队列中拷贝出长度为len字节的数据到to所指的缓冲中,out offset没有被更改。


5)获取队列数据

static inline unsigned int kfifo_size(struct kfifo *fifo)//该函数返回队列的空间总大小.
static inline unsigned int kfifo_len(struct kfifo *fifo)//该函数返回队列中已入队字节数.
static inline unsigned int kfifo_avail(struct kfifo *fifo)//该函数返回队列的可用空间的大小.
static inline int kfifo_is_empty(struct kfifo *fifo)//该函数测试kfifo是否为空.
static inline int kfifo_is_full(struct kfifo *fifo)//该函数测试kfifo是否已满.


6)重置和撤销队列

static inline void kfifo_reset(struct kfifo *fifo)//该函数重置队列.
void kfifo_free(struct kfifo *fifo)//该函数销毁用kfifo_alloc()创建的队列.


6.3映射

IDR机制原理:

IDR机制适用在那些需要把某个整数和特定指针关联在一起的地方。例如,在IIC总线中,每个设备都有自己的地址,要想在总线上找到特定的设备,就必须要先发送设备的地址。当适配器要访问总线上的IIC设备时,首先要知道它们的ID号,同时要在内核中建立一个用于描述该设备的结构体,和驱动程序

将ID号和设备结构体结合起来,如果使用数组进行索引,一旦ID 号很大,则用数组索引会占据大量内存空间。这显然不可能。或者用链表,但是,如果总线中实际存在的设备很多,则链表的查询效率会很低。

此时,IDR机制应运而生。该机制内部采用红黑树实现,可以很方便的将整数和指针关联起来,并且具有很高的搜索效率

一个映射,也常称为关联数组,其实是一个由唯一键组成的集合,而每个键必然关联一个特定的值。即键到值的关联关系称为映射。值=f(键)


三个标准的映射操作:
Add(key,value)
Remove(key)
value=Lookup(key)//look up v.查阅;查找
映射可以通过散列表和平衡二叉树搜索树实现。

Linux在add基础上实现了allocate(内存分配)操作,allocate(内存分配)操作向map中加入键值对,产生标识数UID。linux映射标识数UID到一个指针。

idr数据结构,用于映射用户空间的标识数UID。

1)初始化一个idr

struct idr {
    struct idr_layer __rcu *top;    /*根节点*/
    struct idr_layer *id_free;      /*空闲节点*/
    int          layers;            /*树的高度*/
    int          id_free_cnt;       /*空闲节点数*/
    spinlock_t      lock;
};

struct idr_layer {
    unsigned long         bitmap;                   /*位图*/
    struct idr_layer __rcu    *ary[1<<IDR_BITS];    /*孩子节点或者地址数据*/
    int             count;                          /*ary已存放数*/
    int             layer;                          /* 相对叶子节点的高度 */
    struct rcu_head         rcu_head;
};


2)分配一个新的UID

//看看如何使用idr:
int idr_pre_get(struct idr,gfp_t gfp_mask)//分配存放ID号的内存:每次通过IDR获得UID号之前 ,需要为UID号先分配内存。分配内存的函数是idr_pre_get().成功返回1,失败放回0。第一个参数是指向IDR的指针,第二个参数是内存分配标志。
int idr_get_new(struct idr *idp, void *ptr, int *id)//使用idp所指向的idr去分配一个新的UID,并且将其关联到指针ptr上。成功返回0,并将新的UID存于id。失败返回错误码-EAGAIN。


3)查找UID

void *idr_find(struct idr *idp, int id)//在红黑树中找到指定的id


4)删除UID

void idr_remove(struct idr *idp, int id)//在红黑树中删除指定的id,当然id关联的指针也会删除


5)撤销idr

void idr_destroy(struct idr *idp)
//如果成功,则只释放idr中未使用的内存。它并不释放当前分配给UID使用的任何内存。通常,内核代码不会撤销idr,除非关闭或者卸载,而且只用在没有其他用户(也就没有更多的UID)时才能删除。
//当然可以调用void idr_remove_all(struct idr *idp)强制删除所有的UID


6.4二叉树


1)二叉搜索树

略,看算法追逐之路https://blog.csdn.net/weixin_55255438/article/details/125030571?spm=1001.2014.3001.5502


2)自平衡二叉搜索树

略,看算法追逐之路https://blog.csdn.net/weixin_55255438/article/details/125030571?spm=1001.2014.3001.5502


6.5数据结构以及选择

如果对数据集合的主要操作是遍历数据,就使用链表。
如果你的代码符合生产者/消费者模式,则使用队列。
如果你需要映射一个UID到一个对象,就使用映射。
如果你需要存储大量数据,并且检索迅速,那么红黑树最好。


6.6算法复杂度


1)算法

算法就是一系列指令

2)大O符号

一种很有用的渐近表示法,它是一个函数,此函数增长等于或快于我们研究的函数,大O符号用来描述这种增长率。

3)大θ符号

Knuth教授把上述大O符号,称为大θ符号


4)时间复杂度

常见的复杂度:

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Linux内核设计与实现 第六章 内核数据结构 的相关文章

随机推荐

  • 使用Nginx解决跨域问题

    目录 使用Nginx解决跨域问题 1 修改浏览器 客户端访问地址 2 在nginx conf配置文件需配置server 3 在Nginx中配置客户端访问的接口 按照规则或通配 并设置被代理的服务器 4 在Nginx中统一配置客户端访问的头部
  • java实时获取汇率

    1 分享三个觉得挺不错的汇率api 1 每小时免费50次查询配额 NOWapi 2 0 1元2000次 年 阿里云 汇率api 3 每天免费100次查询配额 需要实名认证 聚合科技 如果只是针对很少外币获取汇率的话 个人推荐去阿里云购买 毕
  • Java中使用Jar包时读取当前jar文件所在的目录工具

    在实际使用中 jar包所放的位置是不一定的所以要动态获取当前目录 package com gj5u publics util import java io File 获取打包后jar的路径信息 author Rex public class
  • angularJs摸态框实例加详细注解

  • mos 多路模拟电子开关_同步四开关 BuckBoost 180W 模块电源

    点击上方 21Dianyuan 关注我们 本文是 21Dianyuan 社区 原创 技术文章 作者 xueyiranpiao 感谢作者的辛苦付出 本电源主要应用于电池充电 电池为8串磷酸铁锂 10000mAH 0 6C 充电 也可以用于其它
  • Mybatis底层源码分析(最详细的版本)

    Mybatis底层源码分析 最详细的版本 1 概要介绍 MyBatis 是一款优秀的持久层框架 也是当前最流行的java持久层框架之一 它内部封装了jdbc 使开发 者只需要关注sql语句本身 而不需要花费精力去处理加载驱动 创建连接 创建
  • 网络编程知识预备(2) ——TCP三次握手与四次挥手、流量控制(滑动窗口)、拥塞控制、半连接状态、2MSL

    参考 浅显易懂的三次握手与四次挥手 作者 丶PURSUING 发布时间 2021 03 19 09 33 20 网址 https blog csdn net weixin 44742824 article details 114990198
  • python3 面向对象_Python3快速入门(六)——Python3面向对象

    Python3快速入门 六 Python3面向对象 一 面向对象技术简介 1 面向对象简介 面向对象编程 Object Oriented Programing OOP 是一种编程思想 OOP把对象当成程序的一个基本单元 一个对象包含数据和操
  • antd date-picker 默认时间设置问题

    一 官网给出的例子
  • Python编程:通讯录(文件读取)

    描述 读取附件中的csv文件 通讯录信息 放入字典中 后两项以列表形式做为字典的值 并依次输出其中的信息 文件内数据不需要修改 输出时数据之间以空格间隔 编码格式使用GBK 输入 A 时 按行输出文件信息 输入 D 时 直接输出字典内容 输
  • vue3 props属性基本使用梳理

    前言 vue2中props属性的使用是比较统一的基本就一种方式 但是vue3中其实方式是比较多的 因此就打算梳理一下 会按照选项式和组合式进行梳理 包括属性的定义 取值以及属性的监听 应该是叫单文件组件和组合式API 不知道vue官方是根据
  • 递归->栈->队列面试题

    本文所有程序均已测试通过 测试结果图就不一个一个再截图了 读者可以自己copy验证一下 后期我会把思路图补出来 1 行走机器人问题 货架N个 机器人初始位置在pos 经过minutes分钟后到达T有多少种方案 行走机器人问题 货架N个 机器
  • 使用Hexo从0到1搭建个人博客详细教程(超详细,超简单)

    看完这篇 轻轻松松搭建个人博客 校花 班花 额 额 看了就会的博客搭建教程 一 搭建前的软件准备 git node 二 安装hexo 完成简单本地页面展示 三 将Hexo部署到Github 1 Github创建个人仓库 2 生成ssh添加到
  • WebStorm功能特点以及使用指南

    WebStorm功能特点以及使用指南 首先看看WebStorm合其他的IDE有什么特别之处 1 自动保存 不需要你一次又一次地ctrl s啦 所有的操作都直接存储 当然 万一键盘误操作也会被立即存储 不过我们可以通过本地版本控制解决这个问题
  • 创建型模式-建造者模式理解

    1 前言 首先建造者模式适合下面的场景 进行使用 假设不同的对象有着基本的共同特点 或者配合前端进行页面布局 进行构建一个复杂的对象 那么可以参考工厂方法模式进行抽取对象 并进行解耦 达到一个设计符合要求的对象的过程 eg 1 保险产品 前
  • python自适应图片大小_python – 如何在Pygame中将图像缩放到屏幕尺寸

    您可以使用pygame transform scale缩放图像 import pygame picture pygame image load filename picture pygame transform scale picture
  • 小世界网络和复杂网络+python代码实现

    文章目录 小世界网络 复杂网络的特性 平均路径长度L 聚集系数C 度及度分布 小世界效应 规则网络 随机网络 小世界网络 无标度网络 python 代码 生成小世界网络 规则网络 喜欢的话请关注我们的微信公众号 你好世界炼丹师 公众号主要讲
  • 头文件重复定义问题解决“C1014错误“

    比如现在有三个文件 两个头文件 一个 cpp文件 header1 h include header2 h int fun2 header2 h include header1 h int fun main cpp include heade
  • Git学习笔记----基础运用

    安装Git Windows 进入官网下载或百度网盘下载 Git V2 23 x64 提取码 uf2x Ubuntu sudo apt get install git 安装完成之后打开git命令行 Ubuntu命令行即可操作 输入以下代码 查
  • Linux内核设计与实现 第六章 内核数据结构

    目录 1 单向链表和双向链表 编辑 编辑 2 环形链表 3 沿链表移动 4 Linux内核中的实现 5 操作链表 6 遍历链表 6 2队列 1 kfifo 2 创建队列 3 推入队列数据 4 摘取队列数据 5 获取队列数据 6 重置和撤销队