设为首页收藏本站
网站公告 | 这是第一条公告
     

 找回密码
 立即注册
缓存时间23 现在时间23 缓存数据 荣耀也罢,屈辱也罢,都要以平和的心态去面对,少一些无奈与感慨,多一份从容和淡然。晚安!

荣耀也罢,屈辱也罢,都要以平和的心态去面对,少一些无奈与感慨,多一份从容和淡然。晚安!

查看: 338|回复: 0

linux内核双向链表详解

[复制链接]

  离线 

TA的专栏

  • 打卡等级:常驻代表
  • 打卡总天数:37
  • 打卡月天数:0
  • 打卡总奖励:513
  • 最近打卡:2025-11-10 03:04:18
等级头衔

等級:晓枫资讯-上等兵

在线时间
0 小时

积分成就
威望
0
贡献
400
主题
372
精华
0
金钱
1718
积分
854
注册时间
2023-2-10
最后登录
2025-11-10

发表于 2025-8-29 23:16:46 | 显示全部楼层 |阅读模式
介绍下linux内核的双向链表的使用,接口定义在include/linux/list.h

结构体
  1. struct list_head {
  2.     struct list_head *next;  // 指向下一个节点
  3.     struct list_head *prev;  // 指向前一个节点
  4. };
复制代码
使用的时候,会把这个结构体放在需要使用链表的结构体里,放在结构体里的任意位置都可以,读取数据的时候,是从链表里拿到node后,通过container_of拿到node所在结构体的地址,根据这个地址再来找结构体里的其他成员。
所以,同一个链表里,是根据node把存放的内容关联串起来,再根据node拿到内容的,而不关心这些node所在的内容的结构体是不是同一个结构体。
  1. struct zslnode {
  2.     int seq;
  3.     struct list_head node;
  4. };

  5. struct zslnode1 {
  6.     int seq;  
  7.     struct list_head node1;
  8.     int seq1;
  9. };
复制代码
初始化

初始化有多种方式,常见的有LIST_HEAD_INIT和INIT_LIST_HEAD
作用是常见一个struct list_head变量,把里面的next和prev都指向自己。

增加

常用的增加分为两种接口,往前增加list_add,往后增加list_add_tail。这俩接口都是调用的__list_add实现的,往前增加是__list_add(new, head, head->next),往后是__list_add(new, head->prev, head)。
__list_add主要就是如下操作:
  1.        next->prev = new;
  2.        new->next = next;
  3.        new->prev = prev;
  4.        WRITE_ONCE(prev->next, new);
复制代码
所以,增加节点其实就是在原来链表的的head->prev、head和head->next三个之间去增加。
head本身不存在链表上(即在head上去存数据遍历的时候是取不到的),像是把双向链表的首尾连接起来的节点,head->next永远指向链表第一个节点,head->prev指向最后一个节点。
这样就容易理解__list_add里的赋值操作,去掉prev和next之间的联系,然后把new增加到prev和next中间。
往前增加参数里prev是head,即首尾相连的点,next是head->next即链表第一位,new增加到中间就变成了链表第一位。
往后增加prev是链表最后一位,next是首尾相连的点,new增加到中间就变成了链表最后一位。
1.jpeg


删除

删除常用的是list_del,最终调用的是__list_del,主要操作如下:
  1.        next->prev = prev;
  2.        WRITE_ONCE(prev->next, next);
复制代码
即,后面一个节点的prev指向前面一个节点,前面一个节点的next指向后面。

遍历

遍历常用的是list_for_each_entry正向遍历,list_for_each_entry_reverse反向遍历,也还有不少别的变种,基本差不多。
  1.        list_for_each_entry定义如下
  2.        #define list_for_each_entry(pos, head, member)                           \
  3.        for (pos = list_first_entry(head, typeof(*pos), member);   \
  4.             !list_entry_is_head(pos, head, member);                 \
  5.             pos = list_next_entry(pos, member))
复制代码
找到第一个节点,然后一路next查找。第一个节点,前面有提到就是head->next,再通过list_entry拿结构体地址,list_entry就是使用的container_of。
反向遍历就是反过来,查找最后一个节点,即head->prev,然后一路prev往前查找node。
直到遍历到list_entry_is_head停止,即发现自己就是head,&pos->member == (head),这里&pos->member就是存储的结构体里指向node的地址。

实例
  1. struct zslnode {
  2.     int seq;
  3.     struct list_head node;
  4. };

  5. struct zslnode1 {
  6.     int seq1;       
  7.     struct list_head node1;
  8.     int seq2;
  9. };

  10. static void testlist(void)
  11. {
  12.         struct list_head list;
  13.         struct zslnode next1;
  14.         struct zslnode next2;       
  15.         struct zslnode1 next3;
  16.         struct zslnode pre1;       
  17.         struct zslnode pre2;

  18.         struct zslnode *tmpnode;
  19.         struct zslnode1 *tmpnode1;

  20.         INIT_LIST_HEAD(&list);

  21.         next1.seq = 101;
  22.         list_add_tail(&next1.node, &list);

  23.         next2.seq = 102;
  24.         list_add_tail(&next2.node, &list);

  25.         next3.seq1 = 1000;
  26.         next3.seq2 = 2000;
  27.         list_add_tail(&next3.node1, &list);

  28.         pre1.seq = 99;
  29.         list_add(&pre1.node, &list);

  30.         pre2.seq = 98;
  31.         list_add(&pre2.node, &list);

  32.         list_for_each_entry(tmpnode, &list, node)
  33.         {
  34.                 printk(KERN_INFO "tlist: seq:%d\n",tmpnode->seq);
  35.         }

  36.         list_del(&next2.node);

  37.         list_for_each_entry(tmpnode1, &list, node1)
  38.         {
  39.                 printk(KERN_INFO "tlist1: seq:%d %d\n",tmpnode1->seq1,tmpnode1->seq2);
  40.         }

  41. }
复制代码
运行结果如下
2.jpeg


注意事项


  • 从扫描的逻辑来看,head的节点是不能被扫描到的,虽然只有head存在的话,能拿到数据(那是因为head->next=head)。
  • 从实例运行结果tlist1的打印可以看到不同的结构体可以放在一起处理,但是会出现内存越界的情况,读到不可控的内容。
  • List的内容存放,都是用指针存放的,所以如果链表是全局变量的话,里面的节点也必须是全局或者静态变量,不能是栈里的内容,不然会有指针踩飞导致崩溃等问题。

总结

以上为个人经验,希望能给大家一个参考,也希望大家多多支持晓枫资讯。

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
晓枫资讯-科技资讯社区-免责声明
免责声明:以上内容为本网站转自其它媒体,相关信息仅为传递更多信息之目的,不代表本网观点,亦不代表本网站赞同其观点或证实其内容的真实性。
      1、注册用户在本社区发表、转载的任何作品仅代表其个人观点,不代表本社区认同其观点。
      2、管理员及版主有权在不事先通知或不经作者准许的情况下删除其在本社区所发表的文章。
      3、本社区的文章部分内容可能来源于网络,仅供大家学习与参考,如有侵权,举报反馈:点击这里给我发消息进行删除处理。
      4、本社区一切资源不代表本站立场,并不代表本站赞同其观点和对其真实性负责。
      5、以上声明内容的最终解释权归《晓枫资讯-科技资讯社区》所有。
http://bbs.yzwlo.com 晓枫资讯--游戏IT新闻资讯~~~
严禁发布广告,淫秽、色情、赌博、暴力、凶杀、恐怖、间谍及其他违反国家法律法规的内容。!晓枫资讯-社区
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

手机版|晓枫资讯--科技资讯社区 本站已运行

CopyRight © 2022-2025 晓枫资讯--科技资讯社区 ( BBS.yzwlo.com ) . All Rights Reserved .

晓枫资讯--科技资讯社区

本站内容由用户自主分享和转载自互联网,转载目的在于传递更多信息,并不代表本网赞同其观点和对其真实性负责。

如有侵权、违反国家法律政策行为,请联系我们,我们会第一时间及时清除和处理! 举报反馈邮箱:点击这里给我发消息

Powered by Discuz! X3.5

快速回复 返回顶部 返回列表