news 2026/4/15 7:26:40

跟我学C++中级篇—Linux内核中链表分析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
跟我学C++中级篇—Linux内核中链表分析

一、链表

链表是开发者学习数据结构时最初的一种数据结构,它的应用是非常广泛的。而且在实际的开发,特别是面试中,对链表的各种应用都是无法绕开的。一些常见的算法也是以链表为基础的。链表的应用形式有很多,一般在教材中学习到的也就是开发中常见到的链表其基本的应用方式如下:

//双向链表structlist{intiValue;int*pData;structlist*pNext;// 下一个节点structlist*pPev;// 前一个节点};

可以把链表当成一个一个索链的结点,它们通过指针象“铁环一样”紧紧的链接在一起,形成一条索链一样的表。

二、内核中的链表与传统链表对比

看过内核源码的开发者可能在内核中也看到过链表的定义形势,会发现与教材中的传统的链表有些不同。下面先看一下内核中的代码的链表定义:

//双向链表structlist_head{structlist_head*next,*prev;};//linux-6.9.12/virt/kvm/eventfd.cstruct_ioeventfd{structlist_headlist;u64 addr;intlength;structeventfd_ctx*eventfd;u64 datamatch;structkvm_io_devicedev;u8 bus_idx;bool wildcard;};

把上面的链表代码和教材中的链表代码比较,大家可以发现有什么不同么?最典型的就是教材中的链表包含着数据本身,而在内核中则是数据本身包含链表(这样说可能不太准确,但容易理解)。所以内核中这种链表也可以称为侵入式链表。另外,这种链表可以嵌入到任何的数据结构中形成链表,其通用性非常强,应用非常灵活。对于这种链表,其具体的操作时间复杂度为O(1),相对来说要稳定,同时,这种链表不再参与数据本身的内存分配和回收,在设计上将数据结构与数据进行了解耦。最后,这种数据链表内存中的数据存储连续,对缓存友好,访问速度快。这也是内核中使用这种链表的一个重要原因。

三、应用分析

理解内核中这种双向循环链表,需要明白几个主要的宏:

//自己指向自己,前向和后向都如此,形成一个空的循环链表#defineLIST_HEAD_INIT(name){&(name),&(name)}#defineLIST_HEAD(name)\structlist_headname=LIST_HEAD_INIT(name)/** * INIT_LIST_HEAD - Initialize a list_head structure * @list: list_head structure to be initialized. * * Initializes the list_head to point to itself. If it is a list header, * the result is an empty list. */staticinlinevoidINIT_LIST_HEAD(structlist_head*list){WRITE_ONCE(list->next,list);WRITE_ONCE(list->prev,list);}//写入数据#define__WRITE_ONCE(x,val)\do{\*(volatiletypeof(x)*)&(x)=(val);\}while(0)#defineWRITE_ONCE(x,val)\do{\compiletime_assert_rwonce_type(x);\__WRITE_ONCE(x,val);\}while(0)#ifdefined(__x86_64__)#definesmp_store_release(p,v)\do{\barrier();\WRITE_ONCE(*p,v);\}while(0)//遍历,内核中还有数个相类似的实现/** * list_for_each - iterate over a list * @pos: the &struct list_head to use as a loop cursor. * @head: the head for your list. */#definelist_for_each(pos,head)\for(pos=(head)->next;!list_is_head(pos,(head));pos=pos->next)/** * list_for_each_reverse - iterate backwards over a list * @pos: the &struct list_head to use as a loop cursor. * @head: the head for your list. */#definelist_for_each_reverse(pos,head)\for(pos=(head)->prev;pos!=(head);pos=pos->prev)

这也算内核的特点,底层大量使用了宏来处理一些基础的数据结构和相关的算法定义。只要掌握了这些底层的宏处理,那么在阅读相关的数据结构和算法处理时,就基本没有什么问题了。
实际的应用上,这种链表与普通链表的整体用法没有本质不同,但在一些细节上却有自己的特点,主要有:

  1. 使用container_of宏来处理数据结构与链表节点指针关系
    看下内核中的源码:
//typeof:用于类型检查,确保传入的指针为成员类型指针#definetypeof_member(T,m)typeof(((T*)0)->m)/** * container_of - cast a member of a structure out to the containing structure * @ptr: the pointer to the member. * @type: the type of the container struct this is embedded in. * @member: the name of the member within the struct. * * WARNING: any const qualifier of @ptr is lost. */#definecontainer_of(ptr,type,member)({\void*__mptr=(void*)(ptr);\static_assert(__same_type(*(ptr),((type*)0)->member)||\__same_type(*(ptr),void),\"pointer type mismatch in container_of()");\((type*)(__mptr-offsetof(type,member)));})/** * container_of_const - cast a member of a structure out to the containing * structure and preserve the const-ness of the pointer * @ptr: the pointer to the member * @type: the type of the container struct this is embedded in. * @member: the name of the member within the struct. */#definecontainer_of_const(ptr,type,member)\_Generic(ptr,\consttypeof(*(ptr))*:((consttype*)container_of(ptr,type,member)),\default:((type*)container_of(ptr,type,member))\)//__builtin_offsetof GCC中内置的一个函数,用来计算struct中特定成员相对于起始地址的字节偏移量#undefoffsetof#defineoffsetof(TYPE,MEMBER)__builtin_offsetof(TYPE,MEMBER)

container_of首先将链表的指针类型转化为void的__mptr,然后用offsetof来获取成员到对象起始地址的偏移量(所以是((T)0),而((T*)0)->m就是与0的偏移量,不用再作减法),此时再用__mptr减去偏移量的绝对值,则可以得到包含指针成员变量的对象的地址。
2. 支持相同数据在多个链表的处理
这是一种比较特殊的应用,就是说一个数据对象可以在多个链表中进行访问处理,如下面的代码:

//arch/x86/kvm/svm/svm.hstructkvm_sev_info{bool active;/* SEV enabled guest */bool es_active;/* SEV-ES enabled guest */unsignedintasid;/* ASID used for this guest */unsignedinthandle;/* SEV firmware handle */intfd;/* SEV device fd */unsignedlongpages_locked;/* Number of pages locked */structlist_headregions_list;/* List of registered regions */u64 ap_jump_table;/* SEV-ES AP Jump Table address */structkvm*enc_context_owner;/* Owner of copied encryption context */structlist_headmirror_vms;/* List of VMs mirroring */structlist_headmirror_entry;/* Use as a list entry of mirrors */structmisc_cg*misc_cg;/* For misc cgroup accounting */atomic_tmigration_in_progress;};//arch/x86/kvm/svm/svm.cstaticintsev_guest_init(structkvm*kvm,structkvm_sev_cmd*argp){structkvm_sev_info*sev=&to_kvm_svm(kvm)->sev_info;......INIT_LIST_HEAD(&sev->regions_list);INIT_LIST_HEAD(&sev->mirror_vms);......returnret;}
  1. 广泛使用宏来进行链表的操作
    这个除了前面的一些初始化等操作,最典型的就是遍历:
//以下为几个遍历的方法,还有很多,可参看相关文件#definelist_for_each(pos,head)\for(pos=(head)->next;!list_is_head(pos,(head));pos=pos->next)#definelist_for_each_reverse(pos,head)\for(pos=(head)->prev;pos!=(head);pos=pos->prev)#definelist_for_each_rcu(pos,head)\for(pos=rcu_dereference((head)->next);\!list_is_head(pos,(head));\pos=rcu_dereference(pos->next))#definelist_for_each_continue(pos,head)\for(pos=pos->next;!list_is_head(pos,(head));pos=pos->next)#definelist_for_each_safe(pos,n,head)\for(pos=(head)->next,n=pos->next;\!list_is_head(pos,(head));\pos=n,n=pos->next)#definelist_for_each_prev_safe(pos,n,head)\for(pos=(head)->prev,n=pos->prev;\!list_is_head(pos,(head));\pos=n,n=pos->prev)
  1. 通用的链表相关接口
    这此只简单的说明一下,在“include/linux/list.h”中有插入(头插、尾插和中间插入)、删除、交换、切片、替代等等。

这种链表当然也有其不足之处,主要有:

  1. 需要修改数据结构引入链表相关的数据定义
  2. 实现较为复杂(包括container_of等一系列宏的使用)
  3. 维护调试相对复杂

四、应用场景

一般来说,这种链表会应用于以下几种场景:

  1. 内核相关的系统编程
  2. 多链表数据结构的应用
  3. 内存操作对大小及性能操作要求较高的场合
  4. 其它一些特定的应用

一般来说,这种侵入式链表应该不算主流应用的方式,但在某些方面很有应用价值。

五、内核中的例子

具体的用法如下:

//drivers/base/core.c//设备固件节点(两个fwnode_handle)的依赖关系创建即consumer(消费者)supplier(供应者)staticint__fwnode_link_add(structfwnode_handle*con,structfwnode_handle*sup,u8 flags){structfwnode_link*link;list_for_each_entry(link,&sup->consumers,s_hook)if(link->consumer==con){link->flags|=flags;return0;}link=kzalloc(sizeof(*link),GFP_KERNEL);if(!link)return-ENOMEM;link->supplier=sup;INIT_LIST_HEAD(&link->s_hook);link->consumer=con;INIT_LIST_HEAD(&link->c_hook);link->flags=flags;list_add(&link->s_hook,&sup->consumers);list_add(&link->c_hook,&con->suppliers);pr_debug("%pfwf Linked as a fwnode consumer to %pfwf\n",con,sup);return0;}

如果抛开链表所处理的功能外,就只考虑链表实现本身,只要理解了前面的相关实现代码,还是没有什么大问题的。

六、总结

学习内核中的链表的目的,除了为深入掌握内核相关的技术作准备外,另外一个重要的目的是明白实现链表处理数据的其它方式。正所谓“它山之石,可以攻玉”。与诸君共勉!

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/14 10:49:42

科力辰平台:作为一个科技查新平台,其核心能力边界在哪里?

在科技情报服务领域,各类平台不断涌现,其中不乏宣称能提供一站式查新服务的工具。科力辰-全国科技业务大数据平台(以下简称科力辰)便是其中之一,它定位为整合官方数据的科技查新平台。本文基于一段时间的实际体验与功能…

作者头像 李华
网站建设 2026/4/11 12:59:13

基于SpringBoot的校园活动中心线上管理系统(程序+文档+讲解)

课题介绍在校园活动集约化管理、场地资源高效利用需求升级的背景下,传统校园活动中心管理存在 “场地预约混乱、审批流程冗长、资源调度低效” 的痛点,基于 SpringBoot 构建的校园活动中心线上管理系统,适配学生社团、活动负责人、管理员等角…

作者头像 李华
网站建设 2026/4/11 11:23:57

22、应用盈利与上架Windows应用商店全攻略

应用盈利与上架Windows应用商店全攻略 应用盈利要点 在应用开发中,实现应用盈利是一个重要的环节,以下是一些关键要点: 1. 微软Windows应用商店的试用机制 :微软Windows应用商店允许将付费应用以试用版的形式发布。开发者可以为单个应用创建并维护试用(免费)版和全功…

作者头像 李华
网站建设 2026/4/11 23:53:23

为什么 Amazon 账号越来越难起权重?冷启动 14 天才是关键分水岭

注册只是合格,冷启动才是分水岭 大量账号的问题,都集中在注册后的 7–14 天内: 浏览行为过于集中,登录时间、操作路径高度一致,点击目标明确,几乎没有无效行为,IP、设备环境变化异常或过于“干净…

作者头像 李华
网站建设 2026/4/15 1:21:45

Java毕设选题推荐:基于springboot的小游戏在线活动网站的设计与实现基于Web的小游戏集成网站的设计与实现【附源码、mysql、文档、调试+代码讲解+全bao等】

博主介绍:✌️码农一枚 ,专注于大学生项目实战开发、讲解和毕业🚢文撰写修改等。全栈领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java、小程序技术领域和毕业项目实战 ✌️技术范围:&am…

作者头像 李华
网站建设 2026/4/10 22:48:09

伯克利团队揭示直接处理语音的AI模型是否真的更好

在数字化时代,语音翻译技术正变得越来越重要。当你在异国他乡旅行时,或者需要处理多语言会议记录时,是否想过机器是如何理解并翻译你的话语的?最近,来自意大利布鲁诺凯斯勒基金会的Sara Papi博士领导的一支国际研究团队…

作者头像 李华