一、Linux内核链表源码的获取
下载系统源码的方法常见的有两种:
第一种访问网站下载: kernel.org
第二种输入Linux命令下载:sudo apt install linux-source-5.15.0 (一般这种下载的是当前系统所用到的系统源码版本)
下载完之后在/usr/src中可找到系统源码的压缩包,可以解压到共享路径
ps:内核链表形状
内核链表的内置结点类型: struct list_head ,只存放指针域,需要用户自己改造。把只有指针域的结构体存放在用户自定义的大头结构体(自带数据域)里面
二、在Linux系统源码压缩包里面查找内核链表的源码对应的.h文件
疑问:list.h存放的是什么?里面存放了关于链表操作的宏函数,函数里面主要是指针域操作的代码(链表的添加 删除 移动 遍历都是指针域操作),
但是并没有数据域操作的代码(需要我们自己完成),因为Linux系统链表偏向于高移植性和复用性。
解压,进入到相应对路径,把"list.h"复制出来自己需要的位置。
三、内核链表源码出现的第一个编译错误
包含的头文件是系统源码里面的,我们没有必要把他们全部抠出来(开发的时候一般是把整个源码包和自己的代码进入一起编译),直接删掉即可。
解决方案:
四、第二个错误,WRITE_ONCE是属于系统内核接口(在内核空间里面使用的),我们应用层(用户空间)无法使用
write_once 是提高效率,可以快速的把list赋值给list的next域,找到write_once函数,将第二个形参赋值于第一个形参(其中,__hlist开头的函数不需要操作,因为是哈希表,与双向循环链表没有关系)
五、第三个出错 找不到结构体类型struct list_head ,需要我们自己定义类型
解决方法:自己定义类型
六、第四个出错 加入布尔变量的头文件 stdbool.h
七、第五个出错:这里宏定义是在内核里面起到标记作用,被标记的节点的前驱后继等于内核特定的地址那么一般是被删掉的节点
八、第六个出错 在623左右是哈希表的代码,直接干掉,记得最后留一个#endif
九、研究内核链表的每一个结点的形状,所以每一个结点的形状是一样的。
十、READ_ONCE函数的警告
/******************************************************************************************************
* @file name: : Kernel.c
* @brief : Create Kernel List Node
* @author : [email protected]
* @date : 2024/07/26
* @version 1.0 : V1.0
* @property : 暂无a
* @note : None
* CopyRight (c) 2023-2024 [email protected] All Right Reseverd
******************************************************************************************************/
/*******************************************************************************************************
* @funname : Create_Kernel_Node
* @brief : 创建内核链表的头节点
* @param : None
* @retval : USER_BIG_POI
* @date : 2024/07/26
* @version : V1.0
* @note : None
*******************************************************************************************************/
USER_BIG_POI Create_Kernel_Node()
{
USER_BIG_POI new_big_node = (USER_BIG_POI)malloc(sizeof(USER_BIG_NODE));
if(new_big_node == (USER_BIG_POI)NULL)
{
printf("malloc error!\n");
return (USER_BIG_POI)-1;
}
memset(new_big_node,0,sizeof(USER_BIG_NODE));
INIT_LIST_HEAD(&new_big_node->little_head);
return new_big_node;
}
/*******************************************************************************************************
* @funname : Head_Add_Node
* @brief : 内核链表的头插法
* @param :
@a: big_head
* @retval : int
* @date : 2024/07/26
* @version : V1.0
* @note : None
*******************************************************************************************************/
/*
ps:
第一步:判断大头头节点异常不异常
第二步:创建新的大头结点,输入新数据
第三步:调用内核链表接口 list_add(要添加的新节点的小头地址,默认可填任意小头节点的地址)
list_add:功能很灵活,其实可以把新结点添加到指定结点右边
*/
int Head_Add_Node(BIG_LINK big_head)
{
if(big_head == (BIG_LINK)NULL)
{
printf("大头 头节点异常!\n");
return -1;
}
BIG_LINK new_big_node = Create_Linux_Kernel_List_Node();
if(new_big_node == (BIG_LINK)-1)
{
printf("创建新节点失败!无法头插!\n");
return -1;
}
//分析list_add
//void list_add(struct list_head * new, struct list_head *head)
//new:新节点的小头的地址
//head:第一个小头(小头头节点)的地址
list_add(&new_big_node->list_pointer_head, &big_head->list_pointer_head);
return 0;
}
/*******************************************************************************************************
* @funname : Tail_Add_Kernel_Node
* @brief : 内核链表的尾插法
* @param :
@a: big_head_node
* @retval : int
* @date : 2024/07/26
* @version : V1.0
* @note : None
*******************************************************************************************************/
/*
思路:
第一步:判断大头头节点异常不异常
第二步:创建新的大头结点,输入新数据
第三步:调用内核链表接口 list_add_tail(要添加的新节点的小头地址,默认可填任意小头节点的地址)
list_add_tail:功能很灵活,其实可以把新结点添加到指定结点左边
*/
int Tail_Add_Kernel_Node(USER_BIG_POI big_head_node)
{
if(big_head_node == (USER_BIG_POI)NULL)
{
printf("内核链表大头头头节点异常!头插失败!\n");
return -1;
}
//创建新节点,键盘输入新数据
USER_BIG_POI new_big_node = Create_Kernel_Node();
if(new_big_node == (USER_BIG_POI)-1)
{
printf("创建新的大头结点失败!\n");
return -1;
}
//list_add_tail(新节点的小头的地址,第一个小头的地址);
list_add_tail(&new_big_node->little_head,&big_head_node->little_head);
return 0;
}
/*******************************************************************************************************
* @funname : Search_Kernel_Node
* @brief : 内核链表的结点检索
* @param :
@a: big_head_node
* @retval : int
* @date : 2024/07/26
* @version : V1.0
* @note : None
*******************************************************************************************************/
/*
思路:
第一步:判断大头头节点异常不异常
第二步:判断链表空不空,调用list_empty
第三步:如果不空,则键盘输入要检索的数据
第四步:调用list_for_each_entry进行循环遍历链表,每一次循环都要判断数据是否相等
*/
USER_BIG_POI Search_Kernel_Node(USER_BIG_POI big_head_node)
{
if(big_head_node == (USER_BIG_POI)NULL)
{
printf("内核链表大头头头节点异常!检索失败!\n");
return (USER_BIG_POI)-1;
}
else if(list_empty(&big_head_node->little_head))
{
printf("空链表无需检索!\n");
return (USER_BIG_POI)0;
}
else
{
USER_BIG_POI pos;
list_for_each_entry(pos,&big_head_node->little_head,little_head)
{
if(pos->i_Data == obj_data)
{
printf("Hit Obj Data:%d\n",pos->i_Data);
return pos;
}
}
}
printf("不好意思,没有该数据!\n");
return (USER_BIG_POI)0;
}
/*******************************************************************************************************
* @funname : Anywhere_Add_Kernel_Node
* @brief : 内核链表的指定位置添加
* @param :
@a: big_head_node
* @retval : int
* @date : 2024/07/26
* @version : V1.0
* @note : None
*******************************************************************************************************/
/*
第一步:判断大头头节点异常不异常
第二步:判断链表空不空,调用list_empty
第三步:搜索指定的位置调用自己的检索接口
第四步:如果检索到了指定的位置,再创建新节点键盘输入新数据
第五步:
如果你想在指定的位置左边添加
调用list_add_tail发挥灵活用法
如果你想在指定的位置右边添加
调用list_add发挥灵活用法
*/
int Anywhere_Add_Kernel_Node(USER_BIG_POI big_head_node)
{
if(big_head_node == (USER_BIG_POI)NULL)
{
printf("内核链表大头头节点异常!指定位置添加失败!\n");
return -1;
}
else if(list_empty(&big_head_node->little_head))
{
printf("空链表无需指定位置添加!\n");
return 0;
}
else
{
USER_BIG_POI obj_seat_big_node = Search_Kernel_Node(big_head_node);
if(obj_seat_big_node == (USER_BIG_POI) -1)
{
printf("内核链表大头头节点异常!指定位置添加失败!\n");
return -1;
}
else if(obj_seat_big_node == (USER_BIG_POI) 0)
{
printf("没有该数据节点,无法指定位置添加!\n");
return 0;
}
else
{
USER_BIG_POI new_big_node = Create_Kernel_Node();
if(new_big_node == (USER_BIG_POI)-1)
{
printf("创建新大头节点失败!\n");
return -1;
}
list_add(&new_big_node->little_head,
&obj_seat_big_node->little_head);
}
}
return 0;
}
/*******************************************************************************************************
* @funname : Delete_Kernel_Node
* @brief : 内核链表的删除节点
* @param :
@a: big_head_node
* @retval : int
* @date : 2024/07/26
* @version : V1.0
* @note : None
*******************************************************************************************************/
/*
思路:
第一步:判断大头头节点异常不异常
第二步:判断链表空不空,调用list_empty
第三步:检索有没有要删除的结点
第四步:如果有,调用list_del进行结点的删除,最后自己写free
DEL没加Init 表示:后面删除的节点要彻底的删除(free)
DEL加Init 表示:只是单纯的把节点取出来而已,另作他用 为了让删除的结点称为正常合法的新游离节点
*/
int Delete_Kernel_Node(USER_BIG_POI big_head_node)
{
if(big_head_node == (USER_BIG_POI)NULL)
{
printf("内核链表大头头节点异常!删除失败!\n");
return -1;
}
else if(list_empty(&big_head_node->little_head))
{
printf("空链表无需删除!\n");
return 0;
}
else
{
USER_BIG_POI del_node = Search_Kernel_Node(big_head_node);
if(del_node == (USER_BIG_POI)-1)
{
printf("检索结点失败!\n");
return -1;
}
else if(del_node == (USER_BIG_POI)0)
{
printf("没有该据结点,无法删除!\n");
return 0;
}
else
{
list_del(&del_node->little_head);
free(del_node);
}
}
return 0;
}
/*******************************************************************************************************
* @funname : Move_Kernel_Node
* @brief : 内核链表的移动结点
* @param :
@a: big_head_node
* @retval : int
* @date : 2024/07/26
* @version : V1.0
* @note : None
*******************************************************************************************************/
/*
思路
第一步:判断大头头节点异常不异常
第二步:判断链表空不空,调用list_empty || 如果只有一个
第三步:检索两次,检索要移动的结点,检索定点
第四步:判断移点和定点是否相同
第五步:
调用list_move内核链表接口直接实现移动
*/
int Move_Kernel_Node(USER_BIG_POI big_head_node)
{
if(big_head_node == (USER_BIG_POI)NULL)
{
printf("内核链表大头头节点异常!移动失败!\n");
return -1;
}
else if(list_empty(&big_head_node->little_head))
{
printf("空链表无需移动!\n");
return 0;
}
else if(big_head_node->little_head.next->next == &big_head_node->little_head)//如果只有一个数据结点也无法移动
{
printf("只有一个数据节点,无法移动!\n");
return 0;
}
else
{
USER_BIG_POI move_node = Search_Kernel_Node(big_head_node);
USER_BIG_POI obj_seat_node = Search_Kernel_Node(big_head_node);
if(move_node == (USER_BIG_POI)-1 || obj_seat_node == (USER_BIG_POI)-1)
{
printf("链表头节点异常,移动失败!\n");
return -1;
}
else if(move_node == (USER_BIG_POI)0 || obj_seat_node == (USER_BIG_POI)0)
{
printf("不好意思,没有满足移动需求,无法移动!\n");
return 0;
}
else if(move_node == obj_seat_node)
{
printf("你有问题?定点和移动能一样?,无法移动!\n");
return 0;
}
else
{
list_move(&move_node->little_head,&obj_seat_node->little_head);
}
}
return 0;
}
/*******************************************************************************************************
* @funname : Destroy_Kernel_List
* @brief : 内核链表的销毁
* @param :
@a: big_head_node
* @retval : int
* @date : 2024/07/26
* @version : V1.0
* @note : None
*******************************************************************************************************/
/*
思路:
第一步:判断大头头节点异常不异常
第二步:判断链表空不空,调用list_empty
第三步:遍历链表,永远只删除是否头的下一个结点
注意遍历的时候使用list_for_each_entry(pos,,????)函数会出现段错误
而是用list_entry去获取每一次循环的头节点的下一个结点
*/
int Destroy_Kernel_List(USER_BIG_POI big_head_node)
{
if(big_head_node == (USER_BIG_POI)NULL)
{
printf("内核链表大头头节点异常!摧毁失败!\n");
return -1;
}
else if(list_empty(&big_head_node->little_head))
{
printf("空链表,只需释放头节点即可!\n");
}
else
{
while(!list_empty(&big_head_node->little_head))
{
//list_entry(小头地址, 大头结构体类型, 成员名字)
USER_BIG_POI free_big_node = list_entry(big_head_node->little_head.next,USER_BIG_NODE,little_head);
list_del(&free_big_node->little_head);
free(free_big_node);
}
}
free(big_head_node);
return 0;
}
十一、遍历链表,没有找到container_of函数的警告问题
因为数据域(再大头)和小头是分离的,遍历之遍历小头,并不能直接通过小头去输出数据。
数据域和小头都是大头结构体的同级别结构体成员
使用list_for_each_entry接口实现遍历小头并且获取对应的大头
十二、offsetof kernel函数的功能就是计算结构体成员变量在结构体变量中的偏移量
十三、如何通过结构体成员地址找到结构体变量本身的地址
//补充
//如何验证头插成功,只需要把添加的数据通过大头头节点输出即可
//思路:通过第一个小头的next后继指针获取第二个小头,再通过第二个小头获取第二个大头,就可以输出第二个大头里面的数据
//定义一个变量存放第二个小头的地址
struct list_head * little_head_poi = big_head->list.next;
//通过第二个小头的地址获取第二个大头的地址
//假设某一个大头的地址为0x0
long offset = (long)(&(((BIG_LISTLINK)0x0)->list)); //如果一个结构体的地址为0,那么他里面的成员的地址就是 偏移量
//小头到大头的偏移量
//第二个大头的地址 = 第二个小头的地址 - 小头到大头的偏移量
printf("%s\n",((BIG_LISTLINK)((char *)(little_head_poi) - offset))->data);
十四、内核链表的各种接口
**补充:搜索文件中的字符串:sudo find -name .h|grep 'offsetof' -R
sudo find -name .h 检索所有.h文件
| 重定向
grep 'offsetof',检索所有.h中出现的offsetof字段
-R:递归检索