前言
重点介绍linux使用的链表,其他几种看图简单理解,linux 链表采用数据和链表分开形式的环形双向链表。
本次实验采用linux下模块注册的形式进行,注册后会把链表数据打印到终端。
常用的链表有:
- 单向链表
- 双向链表
- 环形链表
创建基本结构数据
这里使用和内核一样的方法,将链表和数据分开
以下所有的实验 数据都是num ,在测试过程中会不断改变num 的值,通过观察num判断每个节点:
struct list_num
{
struct list_head list; // 内核链表类型
int num;
};
linux 链表类型定义:
struct list_head {
struct list_head *next, *prev;
};
linux链表初始化
内核链表初始化会把头节点地址传到 prev 和 next 成员,
以下有两种方法初始化:
- 第一种是自定义链表并初始化
- 第二种是内核主动定义链表并初始化
方法1
list1.num = 1;
INIT_LIST_HEAD(&list1.list);
运行结果:
初始化后 prev 和 next 地址相等,都是当前节点。
方法2
直接调用宏定义:
static LIST_HEAD(list);
linux链表添加节点
添加节点2 和 3, 新增节点采用入栈形式,先进后出,后进新出, 也就是新加的节点作为链表头,
可通过序号 1 和 2 、3 的地址来判断:
list1.serial_num = 1;
INIT_LIST_HEAD(&list1.list); // 节点1
list2.serial_num = 2;
list_add(&list2.list, &list1.list); // 节点2
list3.serial_num = 3;
list_add(&list3.list, &list1.list); // 节点3
运行结果:
linux链表删除节点
注意删除节点只是从链表上取出来,并不代表清空所占内存
- 直接清空:
将删除上述添加的 链表2节点
list_del(&list2.list);
list2.serial_num = 0;
运行结果:
链表2的prev、next 地址被清空,链表1和3链接
- 清空并且初始化:
清空并初始化是方便后续可以再使用起来,而且初始化是将自己的节点地址给prev、next指针。
运行结果:
获取链表所在数据结构的基地址 list_entry
如下图,内核大部分链表声明不会是第一个成员,这就导致从其他节点访问时不能直接. 或者 -> 访问, 必须获取基地址才行,这时 list_entry 就有作用了。
如下程序片段,第一个例子是错误示范,第二个是正确使用。
printk("通过list1 访问list2 , 测试 list entry : \r\n");
printk("错误使用 , 原因是链表的位置不在结构体起始位置 : \r\n");
//p1 = list_entry( list1.list.prev, struct list_num , list);
p1 = &list1.list.prev;
printk("serial_num: %d, addr: %p, prev: %p, next: %p \r\n",p1->serial_num , &p1->list, p1->list.prev , p1->list.next);
printk("正确使用 : \r\n");
p1 = list_entry( list1.list.prev, struct list_num , list);
printk("serial_num: %d, addr: %p, prev: %p, next: %p \r\n",p1->serial_num , &p1->list, p1->list.prev , p1->list.next);
运行结果:
结束
源码若有需要私信!!!