首页 > 系统相关 >Linux内核链表源码的简单操作

Linux内核链表源码的简单操作

时间:2024-07-26 21:40:46浏览次数:14  
标签:node head big list 链表 BIG 源码 Linux

一、Linux内核链表源码的获取

下载系统源码的方法常见的有两种:
    第一种访问网站下载: kernel.org
    第二种输入Linux命令下载:sudo apt install linux-source-5.15.0  (一般这种下载的是当前系统所用到的系统源码版本)
    下载完之后在/usr/src中可找到系统源码的压缩包,可以解压到共享路径

ps:内核链表形状
内核链表的内置结点类型: struct list_head ,只存放指针域,需要用户自己改造。把只有指针域的结构体存放在用户自定义的大头结构体(自带数据域)里面
image


二、在Linux系统源码压缩包里面查找内核链表的源码对应的.h文件

疑问:list.h存放的是什么?里面存放了关于链表操作的宏函数,函数里面主要是指针域操作的代码(链表的添加 删除 移动 遍历都是指针域操作),
但是并没有数据域操作的代码(需要我们自己完成),因为Linux系统链表偏向于高移植性和复用性。
解压,进入到相应对路径,把"list.h"复制出来自己需要的位置。
image


三、内核链表源码出现的第一个编译错误

包含的头文件是系统源码里面的,我们没有必要把他们全部抠出来(开发的时候一般是把整个源码包和自己的代码进入一起编译),直接删掉即可。

image


解决方案:
image


四、第二个错误,WRITE_ONCE是属于系统内核接口(在内核空间里面使用的),我们应用层(用户空间)无法使用

write_once 是提高效率,可以快速的把list赋值给list的next域,找到write_once函数,将第二个形参赋值于第一个形参(其中,__hlist开头的函数不需要操作,因为是哈希表,与双向循环链表没有关系)
image


五、第三个出错 找不到结构体类型struct list_head ,需要我们自己定义类型

image

解决方法:自己定义类型
image


六、第四个出错 加入布尔变量的头文件 stdbool.h

image


七、第五个出错:这里宏定义是在内核里面起到标记作用,被标记的节点的前驱后继等于内核特定的地址那么一般是被删掉的节点

image


八、第六个出错 在623左右是哈希表的代码,直接干掉,记得最后留一个#endif

image


九、研究内核链表的每一个结点的形状,所以每一个结点的形状是一样的。

image


十、READ_ONCE函数的警告

image


/******************************************************************************************************
 * @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接口实现遍历小头并且获取对应的大头

image
image


十二、offsetof kernel函数的功能就是计算结构体成员变量在结构体变量中的偏移量

image


image


十三、如何通过结构体成员地址找到结构体变量本身的地址

    
    //补充
    //如何验证头插成功,只需要把添加的数据通过大头头节点输出即可
    //思路:通过第一个小头的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);

十四、内核链表的各种接口

image


**补充:搜索文件中的字符串:sudo find -name .h|grep 'offsetof' -R
sudo find -name .h 检索所有.h文件
| 重定向
grep 'offsetof',检索所有.h中出现的offsetof字段
-R:递归检索

标签:node,head,big,list,链表,BIG,源码,Linux
From: https://www.cnblogs.com/hhail08/p/18326196

相关文章

  • SpringBoot源码初学者(二):SpringBoot事件监听器
    ps:真正适合阅读源码的新手来看的SpringBoot源码讲解,如果你真的想读懂SpringBoot源码,可以按照以下推荐的方式来阅读文章打开ide,打开SpringBoot源码,跟着文章一起写注释,写自己的注释不要过于纠结没讲到的地方,毕竟SpringBoot源码那么多,想全讲完是不可能的,只要跟着文章认真阅......
  • 体积计算器(三种语言)源码、效果图
    声明⚠️:英文、藏语翻译均来源网络,若不准确,请于本人联系,请勿抄袭!源码#A-1V=0#体积r=0#小半径R=0#大半径a=0#棱长&长b=0#宽h=0#高DMJ=0#底面积PI=3.14#π(保留两位小数)four_three=1/4*3#四分之三three_one=1/3*1#三分之一R......
  • 零基础STM32单片机编程入门(二十二) ESP8266 WIFI模块实战含源码
    文章目录一.概要二.ESP8266WIFI模块主要性能参数三.ESP8266WIFI模块芯片内部框图四.ESP8266WIFI模块原理图五.ESP8266WIFI模块与单片机通讯方法1.硬件连接2.ESP8266模块AT指令介绍六.STM32单片机与ESP8266WIFI模块通讯实验1.硬件准备2.软件工程3.软件主要代码4.实验......
  • Linux下学习Python包管理器Poetry教程 零基础入门到精通
    Poetry[官网-Poetry]https://python-poetry.org/安装pipinstallpoetry简单使用初始化poetry项目cd~&&mkdirdemopoetryinit管理虚拟环境poetry预设了很多自己的虚拟环境配置,这些配置可以通过poetryconfig进行修改当用户在执行poetryadd等指令......
  • selinux对linux服务的影响
    实验一:使用web服务演示安全上下文值的设定[root@localhost~]#systemctlrestartnginx通过客户端测试,出现403状态码#修改自定义目录的安全上下文的值:[root@localhost~]#chcon-thttpd_sys_content_t/www/-R也可以将自定义目录的......
  • 反转链表(206)
    双指针法一个节点为cur最开始的时候指向head,pre最开始的时候指向null,然后cur,pre节点一次向后移动进行遍历操作,直至cur指向null,链表遍历结束,最后返回pre节点就是反转链表后的一个头节点classSolution{publicListNodereverseList(ListNodehead){ListNodepr......
  • 两两交换链表中的节点(24)
    两两交换,我们定义一个虚拟头节点指向我们链表的头节点,然后我们就可以将链表的第一个节点的下一个节点指向为第二个节点的下一个节点,然后第二个节点的下一个节点指向第一个节点,然后虚拟头节点指向我们的第二个节点就完成了前两个节点的交换classSolution{publicListNodes......
  • 移除链表元素
    这里注意我们操作链表的时候都要使用临时指针来进行遍历链表的操作,不然会改变链表的原始数据,这里我使用两种方式来进行删除的操作原链表删除元素classSolution{publicListNoderemoveElements(ListNodehead,intval){//if(head==null){//ret......
  • 【一手源码展示】Java代码TikTok内嵌商城代码程序,TikTok跨境电商系统源码,TK商城源码
    这套程序已经做了很久了我这边修复二开优化也好几个版本搭建起来做起来确实费劲前后端分离的程序 二开效果页面展示:......
  • 单链表的实现和操作
    目录一.前言二.单链表的定义和结构三.单链表的操作一.前言    线性表的链式表示又称为非顺序映像或链式映像。简而言之,链表可以理解为由指针链连接的n个结点组成的。其中每一个结点包括数据域和指针域。值得注意的是,与顺序表不同,链表中的逻辑次序与物理次序不......