Linux内核中常用的数据结构和算法

Linux内核中常用的数据结构和算法

Linux内核代码中广泛使用了数据结构和算法,其中最常用的两个是链表和红黑树。

链表

Linux内核代码大量使用了链表这种数据结构。链表是在解决数组不能动态扩展这个缺陷而产生的一种数据结构。链表所包含的元素可以动态创建并插入和删除。链表的每个元素都是离散存放的,因此不需要占用连续的内存。链表通常由若干节点组成,每个节点的结构都是一样的,由有效数据区和指针区两部分组成。有效数据区用来存储有效数据信息,而指针区用来指向链表的前继节点或者后继节点。因此,链表就是利用指针将各个节点串联起来的一种存储结构。

(1)单向链表

单向链表的指针区只包含一个指向下一个节点的指针,因此会形成一个单一方向的链表,如下代码所示。

struct list {    int data;   /*有效数据*/    struct list *next; /*指向下一个元素的指针*/};

如图所示,单向链表具有单向移动性,也就是只能访问当前的节点的后继节点,而无法访问当前节点的前继节点,因此在实际项目中运用得比较少。

Linux内核中常用的数据结构和算法

单向链表示意图

(2)双向链表

如图所示,双向链表和单向链表的区别是指针区包含了两个指针,一个指向前继节点,另一个指向后继节点,如下代码所示。

struct list {    int data;   /*有效数据*/    struct list *next; /*指向下一个元素的指针*/    struct list *prev; /*指向上一个元素的指针*/};
Linux内核中常用的数据结构和算法

双向链表示意图

(3)Linux内核链表实现

单向链表和双向链表在实际使用中有一些局限性,如数据区必须是固定数据,而实际需求是多种多样的。这种方法无法构建一套通用的链表,因为每个不同的数据区需要一套链表。为此,Linux内核把所有链表操作方法的共同部分提取出来,把不同的部分留给代码编程者自己去处理。Linux内核实现了一套纯链表的封装,链表节点数据结构只有指针区而没有数据区,另外还封装了各种操作函数,如创建节点函数、插入节点函数、删除节点函数、遍历节点函数等。

Linux内核链表使用struct list_head数据结构来描述。

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

struct list_head数据结构不包含链表节点的数据区,通常是嵌入其他数据结构,如struct page数据结构中嵌入了一个lru链表节点,通常是把page数据结构挂入LRU链表。

struct page {    ...    struct list_head lru;    ...}

链表头的初始化有两种方法,一种是静态初始化,另一种动态初始化。

把next和prev指针都初始化并指向自己,这样便初始化了一个带头节点的空链表。

/*静态初始化*/#define LIST_HEAD_INIT(name) { &(name), &(name) }#define LIST_HEAD(name)     struct list_head name = LIST_HEAD_INIT(name)/*动态初始化*/static inline void INIT_LIST_HEAD(struct list_head *list){    list->next = list;    list->prev = list;}

添加节点到一个链表中,内核提供了几个接口函数,如list_add()是把一个节点添加到表头,list_add_tail()是插入表尾。

void list_add(struct list_head *new, struct list_head *head)list_add_tail(struct list_head *new, struct list_head *head)

遍历节点的接口函数。

#define list_for_each(pos, head) for (pos = (head)->next; pos != (head); pos = pos->next)

这个宏只是遍历一个一个节点的当前位置,那么如何获取节点本身的数据结构呢?这里还需要使用list_entry()宏。

#define list_entry(ptr, type, member)     container_of(ptr, type, member)container_of()宏的定义在kernel.h头文件中。#define container_of(ptr, type, member) ({                const typeof( ((type *)0)->member ) *__mptr = (ptr);        (type *)( (char *)__mptr - offsetof(type,member) );})#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)

其中offsetof()宏是通过把0地址转换为type类型的指针,然后去获取该结构体中member成员的指针,也就是获取了membertype结构体中的偏移量。最后用指针ptr减去offset,就得到type结构体的真实地址了。

下面是遍历链表的一个例子。

static ssize_t class_osdblk_list(struct class *c,                struct class_attribute *attr,                char *data){    int n = 0;    struct list_head *tmp;    list_for_each(tmp, &osdblkdev_list) {        struct osdblk_device *osdev;        osdev = list_entry(tmp, struct osdblk_device, node);        n += sprintf(data+n, "%d %d %llu %llu %sn",            osdev->id,            osdev->major,            osdev->obj.partition,            osdev->obj.id,            osdev->osd_path);    }    return n;}

红黑树

红黑树(Red Black Tree)被广泛应用在内核的内存管理和进程调度中,用于将排序的元素组织到树中。红黑树被广泛应用在计算机科学的各个领域中,它在速度和实现复杂度之间提供一个很好的平衡。

红黑树是具有以下特征的二叉树。

每个节点或红或黑。

每个叶节点是黑色的。
如果结点都是红色,那么两个子结点都是黑色。
从一个内部结点到叶结点的简单路径上,对所有叶节点来说,黑色结点的数目都是相同的。

红黑树的一个优点是,所有重要的操作(例如插入、删除、搜索)都可以在O(log n)时间内完成,n为树中元素的数目。经典的算法教科书都会讲解红黑树的实现,这里只是列出一个内核中使用红黑树的例子,供读者在实际的驱动和内核编程中参考。这个例子可以在内核代码的documentation/Rbtree.txt文件中找到。

#include #include #include #include #include #include #include MODULE_AUTHOR("figo.zhang");MODULE_DESCRIPTION(" ");MODULE_LICENSE("GPL");  struct mytype {      struct rb_node node;     int key; };/*红黑树根节点*/ struct rb_root mytree = RB_ROOT;/*根据key来查找节点*/struct mytype *my_search(struct rb_root *root, int new)  {     struct rb_node *node = root->rb_node;     while (node) {          struct mytype *data = container_of(node, struct mytype, node);          if (data->key > new)               node = node->rb_left;          else if (data->key rb_right;          else               return data;     }     return NULL;  }/*插入一个元素到红黑树中*/  int my_insert(struct rb_root *root, struct mytype *data)  {     struct rb_node **new = &(root->rb_node), *parent=NULL;     /* 寻找可以添加新节点的地方 */     while (*new) {          struct mytype *this = container_of(*new, struct mytype, node);          parent = *new;          if (this->key > data->key)               new = &((*new)->rb_left);          else if (this->key key) {               new = &((*new)->rb_right);          } else               return -1;     }     /* 添加一个新节点 */     rb_link_node(&data->node, parent, new);     rb_insert_color(&data->node, root);     return 0;  }static int __init my_init(void){     int i;     struct mytype *data;     struct rb_node *node;     /*插入元素*/     for (i =0; i key = i;          my_insert(&mytree, data);     }     /*遍历红黑树,打印所有节点的key值*/      for (node = rb_first(&mytree); node; node = rb_next(node))           printk("key=%dn", rb_entry(node, struct mytype, node)->key);     return 0;}static void __exit my_exit(void){     struct mytype *data;     struct rb_node *node;     for (node = rb_first(&mytree); node; node = rb_next(node)) {          data = rb_entry(node, struct mytype, node);          if (data) {                rb_erase(&data->node, &mytree);                kfree(data);          }     }}module_init(my_init);module_exit(my_exit);

mytree是红黑树的根节点,my_insert()实现插入一个元素到红黑树中,my_search()根据key来查找节点。内核大量使用红黑树,如虚拟地址空间VMA的管理。

无锁环形缓冲区

生产者和消费者模型是计算机编程中最常见的一种模型。生产者产生数据,而消费者消耗数据,如一个网络设备,硬件设备接收网络包,然后应用程序读取网络包。环形缓冲区是实现生产者和消费者模型的经典算法。环形缓冲区通常有一个读指针和一个写指针。读指针指向环形缓冲区中可读的数据,写指针指向环形缓冲区可写的数据。通过移动读指针和写指针实现缓冲区数据的读取和写入。

在Linux内核中,KFIFO是采用无锁环形缓冲区的实现。FIFO的全称是“First In First Out”,即先进先出的数据结构,它采用环形缓冲区的方法来实现,并提供一个无边界的字节流服务。采用环形缓冲区的好处是,当一个数据元素被消耗之后,其余数据元素不需要移动其存储位置,从而减少复制,提高效率

(1)创建KFIFO

在使用KFIFO之前需要进行初始化,这里有静态初始化和动态初始化两种方式。

int kfifo_alloc(fifo, size, gfp_mask)

该函数创建并分配一个大小为size的KFIFO环形缓冲区。第一个参数fifo是指向该环形缓冲区的struct kfifo数据结构;第二个参数size是指定缓冲区元素的数量;第三个参数gfp_mask表示分配KFIFO元素使用的分配掩码。

静态分配可以使用如下的宏。

#define DEFINE_KFIFO(fifo, type, size)#define INIT_KFIFO(fifo)

(2)入列

把数据写入KFIFO环形缓冲区可以使用kfifo_in()函数接口。

int kfifo_in(fifo, buf, n)

该函数把buf指针指向的n个数据复制到KFIFO环形缓冲区中。第一个参数fifo指的是KFIFO环形缓冲区;第二个参数buf指向要复制的数据的buffer;第三个数据是要复制数据元素的数量。

(3)出列

从KFIFO环形缓冲区中列出或者摘取数据可以使用kfifo_out()函数接口。

#define    kfifo_out(fifo, buf, n)

该函数是从fifo指向的环形缓冲区中复制n个数据元素到buf指向的缓冲区中。如果KFIFO环形缓冲区的数据元素小于n个,那么复制出去的数据元素小于n个。

(4)获取缓冲区大小

KFIFO提供了几个接口函数来查询环形缓冲区的状态。

#define kfifo_size(fifo)#define kfifo_len(fifo)#define kfifo_is_empty(fifo)#define kfifo_is_full(fifo)

kfifo_size()用来获取环形缓冲区的大小,也就是最大可以容纳多少个数据元素。kfifo_len()用来获取当前环形缓冲区中有多少个有效数据元素。kfifo_is_empty()判断环形缓冲区是否为空。kfifo_is_full()判断环形缓冲区是否为满。

(5)与用户空间数据交互

KFIFO还封装了两个函数与用户空间数据交互。

#define    kfifo_from_user(fifo, from, len, copied)#define    kfifo_to_user(fifo, to, len, copied)

kfifo_from_user()是把from指向的用户空间的len个数据元素复制到KFIFO中,最后一个参数copied表示成功复制了几个数据元素。

kfifo_to_user()则相反,把KFIFO的数据元素复制到用户空间。这两个宏结合了copy_to_user()copy_from_user()以及KFIFO的机制,给驱动开发者提供了方便。

以上就是Linux内核中常用的数据结构和算法的详细内容,更多请关注创想鸟其它相关文章!

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 chuangxiangniao@163.com 举报,一经查实,本站将立刻删除。
发布者:程序猿,转转请注明出处:https://www.chuangxiangniao.com/p/110378.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2025年11月23日 08:03:00
下一篇 2025年11月23日 09:07:17

相关推荐

  • 怎样用免费工具美化PPT_免费美化PPT的实用方法分享

    利用KIMI智能助手可免费将PPT美化为科技感风格,但需核对文字准确性;2. 天工AI擅长优化内容结构,提升逻辑性,适合高质量内容需求;3. SlidesAI支持语音输入与自动排版,操作便捷,利于紧急场景;4. Prezo提供多种模板,自动生成图文并茂幻灯片,适合学生与初创团队。 如果您有一份内容完…

    2025年12月6日 软件教程
    000
  • Pages怎么协作编辑同一文档 Pages多人实时协作的流程

    首先启用Pages共享功能,点击右上角共享按钮并选择“添加协作者”,设置为可编辑并生成链接;接着复制链接通过邮件或社交软件发送给成员,确保其使用Apple ID登录iCloud后即可加入编辑;也可直接在共享菜单中输入邮箱地址定向邀请,设定编辑权限后发送;最后在共享面板中管理协作者权限,查看实时在线状…

    2025年12月6日 软件教程
    100
  • REDMI K90系列正式发布,售价2599元起!

    10月23日,redmi k90系列正式亮相,推出redmi k90与redmi k90 pro max两款新机。其中,redmi k90搭载骁龙8至尊版处理器、7100mah大电池及100w有线快充等多项旗舰配置,起售价为2599元,官方称其为k系列迄今为止最完整的标准版本。 图源:REDMI红米…

    2025年12月6日 行业动态
    200
  • Linux中如何安装Nginx服务_Linux安装Nginx服务的完整指南

    首先更新系统软件包,然后通过对应包管理器安装Nginx,启动并启用服务,开放防火墙端口,最后验证欢迎页显示以确认安装成功。 在Linux系统中安装Nginx服务是搭建Web服务器的第一步。Nginx以高性能、低资源消耗和良好的并发处理能力著称,广泛用于静态内容服务、反向代理和负载均衡。以下是在主流L…

    2025年12月6日 运维
    000
  • Linux journalctl与systemctl status结合分析

    先看 systemctl status 确认服务状态,再用 journalctl 查看详细日志。例如 nginx 启动失败时,systemctl status 显示 Active: failed,journalctl -u nginx 发现端口 80 被占用,结合两者可快速定位问题根源。 在 Lin…

    2025年12月6日 运维
    100
  • 华为新机发布计划曝光:Pura 90系列或明年4月登场

    近日,有数码博主透露了华为2025年至2026年的新品规划,其中pura 90系列预计在2026年4月发布,有望成为华为新一代影像旗舰。根据路线图,华为将在2025年底至2026年陆续推出mate 80系列、折叠屏新机mate x7系列以及nova 15系列,而pura 90系列则将成为2026年上…

    2025年12月6日 行业动态
    100
  • Linux如何防止缓冲区溢出_Linux防止缓冲区溢出的安全措施

    缓冲区溢出可通过栈保护、ASLR、NX bit、安全编译选项和良好编码实践来防范。1. 使用-fstack-protector-strong插入canary检测栈破坏;2. 启用ASLR(kernel.randomize_va_space=2)随机化内存布局;3. 利用NX bit标记不可执行内存页…

    2025年12月6日 运维
    000
  • Linux如何优化系统性能_Linux系统性能优化的实用方法

    优化Linux性能需先监控资源使用,通过top、vmstat等命令分析负载,再调整内核参数如TCP优化与内存交换,结合关闭无用服务、选用合适文件系统与I/O调度器,持续按需调优以提升系统效率。 Linux系统性能优化的核心在于合理配置资源、监控系统状态并及时调整瓶颈环节。通过一系列实用手段,可以显著…

    2025年12月6日 运维
    000
  • Pboot插件数据库连接的配置教程_Pboot插件数据库备份的自动化脚本

    首先配置PbootCMS数据库连接参数,确保插件正常访问;接着创建auto_backup.php脚本实现备份功能;然后通过Windows任务计划程序或Linux Cron定时执行该脚本,完成自动化备份流程。 如果您正在开发或维护一个基于PbootCMS的网站,并希望实现插件对数据库的连接配置以及自动…

    2025年12月6日 软件教程
    000
  • Linux命令行中wc命令的实用技巧

    wc命令可统计文件的行数、单词数、字符数和字节数,常用-l统计行数,如wc -l /etc/passwd查看用户数量;结合grep可分析日志,如grep “error” logfile.txt | wc -l统计错误行数;-w统计单词数,-m统计字符数(含空格换行),-c统计…

    2025年12月6日 运维
    000
  • Linux命令行中fc命令的使用方法

    fc 是 Linux 中用于管理命令历史的工具,可查看、编辑并重新执行历史命令。输入 fc 直接编辑最近一条命令,默认调用 $EDITOR 打开编辑器修改后自动执行;通过 fc 100 110 或 fc -5 -1 可批量编辑指定范围的历史命令,保存后按序重跑;使用 fc -l 列出命令历史,支持起…

    2025年12月6日 运维
    000
  • 曝小米17 Air正在筹备 超薄机身+2亿像素+eSIM技术?

    近日,手机行业再度掀起超薄机型热潮,三星与苹果已相继推出s25 edge与iphone air等轻薄旗舰,引发市场高度关注。在此趋势下,多家国产厂商被曝正积极布局相关技术,加速抢占这一细分赛道。据业内人士消息,小米的超薄旗舰机型小米17 air已进入筹备阶段。 小米17 Pro 爆料显示,小米正在评…

    2025年12月6日 行业动态
    000
  • 「世纪传奇刀片新篇」飞利浦影音双11声宴开启

    百年声学基因碰撞前沿科技,一场有关声音美学与设计美学的影音狂欢已悄然引爆2025“双十一”! 当绝大多数影音数码品牌还在价格战中挣扎时,飞利浦影音已然开启了一场跨越百年的“声”活革命。作为拥有深厚技术底蕴的音频巨头,飞利浦影音及配件此次“双十一”精准聚焦“传承经典”与“设计美学”两大核心,为热爱生活…

    2025年12月6日 行业动态
    000
  • 荣耀手表5Pro 10月23日正式开启首销国补优惠价1359.2元起售

    荣耀手表5pro自9月25日开启全渠道预售以来,市场热度持续攀升,上市初期便迎来抢购热潮,一度出现全线售罄、供不应求的局面。10月23日,荣耀手表5pro正式迎来首销,提供蓝牙版与esim版两种选择。其中,蓝牙版本的攀登者(橙色)、开拓者(黑色)和远航者(灰色)首销期间享受国补优惠价,到手价为135…

    2025年12月6日 行业动态
    000
  • VSCode终端美化:功率线字体配置

    首先需安装Powerline字体如Nerd Fonts,再在VSCode设置中将terminal.integrated.fontFamily设为’FiraCode Nerd Font’等支持字体,最后配合oh-my-zsh的powerlevel10k等Shell主题启用完整美…

    2025年12月6日 开发工具
    000
  • Linux命令行中locate命令的快速查找方法

    locate命令通过查询数据库快速查找文件,使用-i可忽略大小写,-n限制结果数量,-c统计匹配项,-r支持正则表达式精确匹配,刚创建的文件需运行sudo updatedb更新数据库才能查到。 在Linux命令行中,locate 命令是快速查找文件和目录路径的高效工具。它不直接扫描整个文件系统,而是…

    2025年12月6日 运维
    000
  • 环境搭建docker环境下如何快速部署mysql集群

    使用Docker Compose部署MySQL主从集群,通过配置文件设置server-id和binlog,编写docker-compose.yml定义主从服务并组网,启动后创建复制用户并配置主从连接,最后验证数据同步是否正常。 在Docker环境下快速部署MySQL集群,关键在于合理使用Docker…

    2025年12月6日 数据库
    000
  • Linux文件系统rsync命令详解

    rsync通过增量同步高效复制文件,支持本地及远程同步,常用选项包括-a、-v、-z和–delete,结合SSH可安全传输数据,配合cron可实现定时备份。 rsync 是 Linux 系统中一个非常强大且常用的文件同步工具,能够高效地在本地或远程系统之间复制和同步文件与目录。它以“增量…

    2025年12月6日 运维
    000
  • Linux systemctl list-dependencies命令详解

    systemctl list-dependencies 用于查看 systemd 单元的依赖关系,帮助排查启动问题和优化启动流程。1. 基本语法为 systemctl list-dependencies [选项] [单元名称],默认显示 default.target 的依赖。2. 常见单元类型包括 …

    2025年12月6日 运维
    100
  • 微信如何开启翻译功能_微信翻译功能的语言切换

    首先开启微信翻译功能,长按外文消息选择翻译并设置“始终翻译此人消息”;接着在“我-设置-通用-多语言”中切换目标语言以优化翻译方向;若效果不佳,可复制内容至第三方工具如Google翻译进行高精度处理。 如果您在使用微信与不同语言的联系人沟通时,发现聊天内容无法理解,则可能是未开启微信内置的翻译功能或…

    2025年12月6日 软件教程
    000

发表回复

登录后才能评论
关注微信