首页 > 系统相关 >Linux内核-内核链表

Linux内核-内核链表

时间:2024-08-03 23:27:38浏览次数:15  
标签:head list next 链表 内核 Linux entry prev 节点

1 内核链表

内核链表本质就是一个双向循环链表:

image

链表的实现仅用一个include/linux/list.h实现。

内核链表有别于传统链表就在节点本身不包含数据域,只包含指针域。故而可以很灵活的拓展数据结构。使用时包含在用户数据结构内部。

1.1 内核链表结构体

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

image

这里把内核经典的container_ofoffsetof实现也贴进来了。实际上一般使用container_of都用include\linux\kernel.h

1.2 list初始化

1.2.1 用宏初始化-LIST_HEAD

#define LIST_HEAD_INIT(name) { &(name), &(name) }
#define LIST_HEAD(name) \
   struct list_head name = LIST_HEAD_INIT(name)

image

LIST_HEAD 定义一个list_head变量, 让next,和prev,也就是【前驱】和【后继】指针都指向自己,作为链表头指针。

例如:

LIST_HEAD(list); // struct list_head list = {.next = list, .prev = list};

1.2.2 用接口初始化-INIT_LIST_HEAD

image

INIT_LIST_HEAD函数用来对一个list_head指针初始化。WRITE_ONCE是一种内存屏障机制,只写入一次, 防止并发产生竞态,参考Linux内核-并发与同步 | Hexo (fuzidage.github.io)

linux内核下并发时同步机制 - fuzidage - 博客园 (cnblogs.com)

因此INIT_LIST_HEAD等效于:

static inline void INIT_LIST_HEAD(struct list_head *list) {
   list->next = list;
   list->prev = list;
}

例如:

 struct list_head list;
 INIT_LIST_HEAD(&list);

1.2.3 初始化完后头部节点图例

image

1.3 内核链表操作

1.3.1 插入节点

list_add总是在链表的头部插入, list_add_tail插在链表尾部。

static inline void __list_add(struct list_head *new,
			      struct list_head *prev,
			      struct list_head *next)
{
	next->prev = new;
	new->next = next;
	new->prev = prev;
	WRITE_ONCE(prev->next, new);
}

static inline void list_add(struct list_head *new, struct list_head *head)
{
	__list_add(new, head, head->next);
}

static inline void list_add_tail(struct list_head *new, struct list_head *head)
{
	__list_add(new, head->prev, head);
}

image

1.3.1.1 创建一个节点

struct my_data_list {
    int data ;
    struct list_head node;
};

struct my_data_list first_data = { 
    .val = 1,
    .list = LIST_HEAD_INIT(first_data.node),//作为节点,其实可以不用初始化next和prev域
};

image

1.3.1.2 头插节点-list_add

list_add(&frist_data.node, &listHead);

list_add总是在链表的头部插入,先看插入第一个节点:
image

插入第一个节点,就是让list_headnextprev都指向第一个节点,第一个节点的nextprev也都指向了list_head,构成一个单元素的环。

再插入第二个节点
image

结合代码讲解:

static inline void __list_add(struct list_head *new,
			      struct list_head *prev,
			      struct list_head *next)
{
	next->prev = new;
	new->next = next;
	new->prev = prev;
	WRITE_ONCE(prev->next, new);
}

//我们把__list_add(new, head, head->next);带入进去得:
{
    head->next->prev  = new; //①
    new->next = head->next; //②
    new->prev = head;  //③
    head->next = new;  //④
}

①让节点1的prev指向节点2。

②让节点2的next指向节点1.

③让节点2的prev指向头节点。

④让头节点的next指向节点2。

1.3.1.2.1 头插入的要点总结

总结1: head的next是指向链表中最新的节点,head的prev指向链表中最旧的节点。

总结2list_add函数作为头插本质:

​ ①把链表头的next剪掉,next去指向新节点;但是得提前将旧节点的prev剪掉,旧节点prev也去指向新节点。注意为什么要先操作旧节点?因为旧节点就是head->next啊。

​ ②让新节点前驱prev指向head, 后继next指向旧节点。

总结3:头插遍历总是先访问到最新的元素,类似于”栈stack“, ”先进后出“

1.3.1.3 尾插节点-list_add_tail

list_add_tail(&frist_data.node, &listHead);
image
image

static inline void __list_add(struct list_head *new,
			      struct list_head *prev,
			      struct list_head *next)
{
	next->prev = new;
	new->next = next;
	new->prev = prev;
	WRITE_ONCE(prev->next, new);
}
//我们把__list_add(new, head->prev, head);带入进去得:
{
    head->prev  = new;       //①
    new->next = head;        //②
    new->prev = head->prev;  //③
    head->prev->next = new;  //④
}

①头节点的prev指向节点2。

②让节点2的next指向头节点.

③让节点2的prev指向头节点的prev。

④让节点1的next指向节点2。

1.3.1.3.1 尾插入的要点总结

总结1: head的next是指向链表中最旧的节点,head的prev指向链表中最新的节点。

总结2list_add_tail函数作为尾插本质:

​ ①先把链表头的prev剪掉,prev去指向新节点;最后把旧节点(尾节点)的next指向新节点。这样插入的就变成了新的尾节点。

​ ②同时让新节点的next指向head, prev指向旧节点(尾节点)。

总结3:尾插遍历总是先访问到旧的元素,类似于”队列FIFO“, ”先进先出“

1.3.2 删除节点-list_del

image

static inline void __list_del(struct list_head * prev, struct list_head * next)
{
	next->prev = prev;
	WRITE_ONCE(prev->next, next);
}
static inline void __list_del_entry(struct list_head *entry)
{
	__list_del(entry->prev, entry->next);
}
static inline void list_del(struct list_head *entry)
{
	__list_del(entry->prev, entry->next);
	entry->next = LIST_POISON1;
	entry->prev = LIST_POISON2;
}

可以看到list_del__list_del_entry没有本质区别,核心都是__list_del。举个例子比如:

list_del(&frist_data.node);

非常好理解就不画图了,就是将下一个节点的prev指向前一个节点,同时反过来也要将前一个节点的next指向下一个节点。这个节点不就删除掉了。

注意:摘掉的节点prev、next 指针分别被设为 LIST_POSITION2LIST_POSITION1两个特殊值,这样设置是为了保证不在链表中的节点项不可访问–对LIST_POSITION1LIST_POSITION2的访问都将引起页故障。一访问就会立马出错,这样保证了数据安全性。来看下LIST_POSITION1LIST_POSITION2, 在include\linux\poison.h:
image

1.3.3 链表删除与反初始化-list_del_init

image

可以看到调用了__list_del_entry摘除自己这个节点,同时INIT_LIST_HEAD用接口初始化自己这个节点。

1.3.4 链表遍历

1.3.4.0 list_entry

image

遍历的关键就是这个list_entry 宏。它等效于container_of, 实现原理参考前面的讲解:

union和bit_field巧妙进行寄存器位操作 | Hexo (fuzidage.github.io)

union和bit field巧妙进行寄存器位操作 - fuzidage - 博客园 (cnblogs.com)

1.3.4.1 list_for_each(正向遍历)

作用:传入头节点,去遍历里面的node
image

head->next开始,也就是从第一个节点开始往后遍历,直到最后一个节点,这时pos就等于head了,循环结束。

1.3.4.1 list_for_each_safe

下面这个list_for_each_safe, 多了一个tmp变量而已,提前用n去试探下一节点,安全情况下才赋给pos.
image

1.3.4.2 list_for_each_entry

作用:传入头节点,去遍历里面的node所对应的外层宿主结构体。举个例子比较好理解:

struct xxx_dev {
    struct list_head job_list;
    char name[64];
    int irq;
    ...
};
struct xxx_job {
	atomic_t job_state;
	struct list_head node;//add to xxx_dev job_list
	atomic_t job_id;
    ...
};
struct xxx_dev dev;
struct xxx_job job;

list_add_tail(&job->node, &dev->job_list);

struct xxx_job *job_tmp;
list_for_each_entry(job_tmp, &dev->job_list, node) {
    //从链表中取出job_tmp,do someting
}

首先定义一个链表job_list藏在xxx_dev里面,然后链表的节点宿主是xxx_job

list_add_tailxxx_jobnode即可加入xxx_devjob_list

list_for_each_entry即可根据xxx_jobnode成员取出宿主结构。

来分析一下list_for_each_entry函数:
image

list_for_each_entry(job_tmp, &dev->job_list, node)那么首先进入for循环:

①代入list_first_entry(&dev->job_list, typeof(*job_tmp), node)

继续代入:看到了熟悉的list_entry(&dev->job_list->next, typeof(*job_tmp), node),是不是就是对应第一个节点的宿主结构xxx_job地址。

那么pos(也就是job_tmp)就指向了第一个节点的宿主结构。

②判断node是否达到head(也就是看有没有遍历到最后一个节点)

③此时pos已经是第一个节点的宿主结构,继续代入list_next_entry(第一个节点的宿主结构, node),看到了熟悉的list_entry(第一个节点的宿主结构->member->next, typeof(*job_tmp), node),这不就是下一个节点的宿主结构嘛,赋值给pos。

1.3.4.2 list_for_each_entry_safe

image

和前面的list_for_each_entry作用完全一样,可以看到实现也是基本一致。多了一个tmp变量而已,提前用n去试探下一节点,安全情况下才赋给pos.

1.3.4.3 list_for_each_prev(反向遍历)

作用:传入头节点,反向去遍历里面的node
image

没什么好说的,和list_for_each相反方向遍历。

1.3.4.3 list_for_each_prev_safe

image

1.3.4.4 list_for_each_entry_reverse

作用:传入头节点,反向去遍历里面的node所对应的外层宿主结构体。和list_for_each_entry方向相反。
image

1.3.4.4 list_for_each_entry_safe_reverse

list_for_each_entry_reverse作用完全一样。
image

1.3.4.5 list_for_each_entry_continue/list_for_each_entry_from(从中间某个节点开始遍历)

image

image

1.3.4.5 list_for_each_entry_continue_reverse(从中间某个节点开始反向遍历)

image

1.3.5 判段链表是否为空

1.3.5.1 list_empty

image

只要头节点的next还是自己,那就代表链表为空。

1.3.6 判段节点是否为最后一个节点

1.3.6.1 list_is_last

image

只要传入的节点的next为头节点,那就是最后一个节点。

1.3.7 获取第一个节点的宿主结构

1.3.7.1 list_first_entry/list_first_entry_or_null

image

struct xxx_job *job_tmp;
job_tmp = list_first_entry(&dev->job_list, typeof(*job_tmp), node)

前面1.3.4.2 list_for_each_entry小节其实已经分析过了,取出第一个节点的宿主结构指针。

下面这个list_first_entry_or_null多了一个判空,如果空链表,则会返回null。
image

1.3.8 获取最后一个节点的宿主结构

1.3.8.1 list_last_entry/list_first_entry_or_null

image

头节点的prev不就对应对应最后一个节点嘛。然后list_entry找到宿主结果。

1.3.9 获取上一个节点的宿主结构

1.3.9.1 list_prev_entry

image

传入某个节点取prev不就是上一个节点嘛,然后list_entry找到宿主结果。

1.3.10 获取下一个节点的宿主结构

1.3.10.1 list_next_entry

image

传入某个节点取next不就是下一个节点嘛,然后list_entry找到宿主结果。

1.4 内核链表进阶操作

1.4.1 节点从A链表转移到B链表

1.4.1.1 搬移到新链表头-list_move

image

可以看到就是从旧链表摘除节点,再头插到新链表。

1.4.1.2 搬移到新链表尾-list_move_tail

image

可以看到就是从旧链表摘除节点,再尾插到新链表。

1.4.2 链表A和链表B合并-list_splice

image

image

分析:list_splice(list1, list2);

带入:__list_splice(list1, list2, list2->next),那么:

first = list1->next;
last = list1->prev;

list1->next = list2;
list2->next = list1->next;

list1->prev->next = list2->next;
list2->next->prev = list1->prev;

最后最好是将还要list1进行反初始化,这样list1才彻底和各个节点断链,比如list_splice_init函数:
image

1.4.3 节点的替换-list_replace

list_replace:将新的节点替换到旧的节点上。

list_replace_init:将新的节点替换到旧的节点上。同时将旧的节点的prev和next指向自己,反初始化。
image

static inline void list_replace(struct list_head *old,
				struct list_head *new) {
    new->next = old->next;//①
    new->next->prev = new;//②
    new->prev = old->prev;//③
    new->prev->next = new;//④
}

image

可以看到虽然替换成功了,但是old还是有指向关系,我们再对old进行INIT_LIST_HEAD(old);断掉old的指向关系,也就是对应list_replace_init函数:

image

old断链后图像那么最后就会变成:
image

2 内核链表总结

image

标签:head,list,next,链表,内核,Linux,entry,prev,节点
From: https://www.cnblogs.com/fuzidage/p/18341284

相关文章

  • Linux中的线程3
    死锁在Linux操作系统中,死锁(Deadlock)是指两个或多个进程(或线程)在执行过程中,因互相持有对方所需的资源而又都在等待对方释放资源,导致它们都无法继续执行下去的一种状态。这种僵局会浪费系统资源,甚至可能导致系统崩溃。案例://线程A和B,以及资源X和Y的初始状态资源X:空闲资......
  • 熟练使用linux常用基本命令梳理汇总
    目录Linux基本命令简单认识shell认识命令的基本格式:内建命令与外部命令查看命令的类型-type查看命令的使用方法-helpmkdirpwdtouchecho认识路径lscd认识热键/linux热键treenanocatgccstatrmrmdir基本认识--创建目录权限linux有多少条指令mansudocpmvwc>和>><morelessheadtail管......
  • leetcode 021:删除链表的倒数第 N 个结点
    LCR021.删除链表的倒数第N个结点给定一个链表,删除链表的倒数第 n 个结点,并且返回链表的头结点。示例1:输入:head=[1,2,3,4,5],n=2输出:[1,2,3,5]示例2:输入:head=[1],n=1输出:[]示例3:输入:head=[1,2],n=1输出:[1]structListNode*removeNthF......
  • 【香橙派系列教程】(四)基于ARM-Linux架构的语音控制刷抖音项目
    【四】基于ARM-Linux架构的语音控制刷抖音项目文章目录【四】基于ARM-Linux架构的语音控制刷抖音项目1.语音模块配置1.创建产品2.引脚配置3.词条定义4.添加控制5.发布版本6.烧录固件2.编程实现语音和开发板通信3.手机接入Linux热拔插1.dmesg命令2.adb调试踩坑问题3.总......
  • C++实现静态链表
    #include<iostream>usingnamespacestd;//定义静态链表的最大容量constintMAX_SIZE=100;//节点类classNode{public:intdata;//节点存储的数据intnext;//节点指向下一个节点的索引(在数组中的位置)//默认构造函数Node():data(0......
  • 链表part01
    今天是8月2日,学习了链表的基础知识。题目主要是链表的基础操作和反转链表,注意虚拟头节点的使用、next的顺序和tmp的灵活使用。1.移除元素题目:给一个链表的头节点head和整数val,请删除链表中所有满足Node.val==val的节点,并返回新的头节点。删除的方法,cur指针挨个遍......
  • Linux 系统资源管理
     1.系统配置查看我们日常经常会提及系统资源的使用状况,那么系统资源具体是指什么呢?其实系统资源主要分为两种,运行资源和存储资源运行资源:又称计算资源,主要是cpu、内存资源。存储资源:即文件系统资源。之前我们讲过,磁盘大小、分区大小、LV大小并不代表系统可用空间的大小,只......
  • Linux网络:网络层IP协议(一)
    目录一、网络层与IP协议基本概念1.1IP协议构成1.2网段划分1.3DHCP一、网络层与IP协议基本概念 在详细了解网络层之前我们需要先引入一些基本概念:主机:配置有IP地址,但不进行路由控制的设备,这里的主机就是我们的一台台电脑;路由器:即配置有IP地址,又能进行......
  • Linux 防火墙系统
    iptables和nftablesiptables是Linux中最常用的防火墙工具,它通过Linux内核中的netfilter模块提供的Hook来管理网络数据包的处理和转发。nftables是iptables的代替品,在Debian10、Ubuntu22、CentOS8中已经由iptables切换到了nftables。iptables的操作命令......
  • 结构体与共用体,链表的学习
    结构体定义        C语言允许用户自己定义一种数据结构,称为结构体。        声明一个结构体类型一般形式为:                strcut 结构体名                {                    成员列表......