鸿蒙内核源码分析(双向链表) | 谁是内核最重要结构体?

news2024/9/23 17:24:09

答案一定是: LOS_DL_LIST(双向链表),它长这样.

typedef struct LOS_DL_LIST {//双向链表,内核最重要结构体
    struct LOS_DL_LIST *pstPrev; /**< Current node's pointer to the previous node *///前驱节点(左手)
    struct LOS_DL_LIST *pstNext; /**< Current node's pointer to the next node *///后继节点(右手)
} LOS_DL_LIST;

结构体够简单了吧,只有前后两个指向自己的指针,但恰恰是因为太简单,所以才太不简单. 就像氢原子一样,宇宙中无处不在,占比最高,原因是因为它最简单,最稳定!

内核的各个模块都能看到双向链表的身影,下图是各处初始化双向链表的操作,因为太多了,只截取了部分:

很多人问图怎么来的, source insight 4.0 是阅读大型C/C++工程的必备工具,要用4.0否则中文有乱码. [下载 source insight 4.0 破解版]

可以豪不夸张的说理解LOS_DL_LIST及相关函数是读懂鸿蒙内核的关键。前后指针(注者后续将比喻成一对左右触手)灵活的指挥着系统精准的运行,越是深入分析内核源码,越能感受到内核开发者对LOS_DL_LIST非凡的驾驭能力,笔者仿佛看到了无数双手前后相连,拉起了一个个双向循环链表,把指针的高效能运用到了极致,这也许就是编程的艺术吧!这么重要的结构体还是需详细讲解一下.

基本概念

双向链表是指含有往前和往后两个方向的链表,即每个结点中除存放下一个节点指针外,还增加一个指向其前一个节点的指针。其头指针head是唯一确定的。从双向链表中的任意一个结点开始,都可以很方便地访问它的前驱结点和后继结点,这种数据结构形式使得双向链表在查找时更加方便,特别是大量数据的遍历。由于双向链表具有对称性,能方便地完成各种插入、删除等操作,但需要注意前后方向的操作。

有好几个同学问数据在哪? 确实LOS_DL_LIST这个结构看起来怪怪的,它竟没有数据域!所以看到这个结构的人第一反应就是我们怎么访问数据?其实LOS_DL_LIST不是拿来单独用的,它是寄生在内容结构体上的,谁用它谁就是它的数据.看图就明白了.

功能接口

鸿蒙系统中的双向链表模块为用户提供下面几个接口。

功能分类            接口名	                    描述
初始化链表          LOS_ListInit                对链表进行初始化
增加节点            LOSListAdd                  将新节点添加到链表中
在链表尾部插入节点   LOS_ListTailInsert          将节点插入到双向链表尾部
在链表头部插入节点   LOS_ListHeadInsert          将节点插入到双向链表头部
删除节点            LOS_ListDelete              将指定的节点从链表中删除
判断双向链表是否为空 LOS_ListEmpty               判断链表是否为空
删除节点并初始化链表 LOS_ListDelInit             将指定的节点从链表中删除使用该节点初始化链表
在链表尾部插入链表   LOS_ListTailInsertList	将链表插入到双向链表尾部
在链表头部插入链表   LOS_ListHeadInsertList	将链表插入到双向链表头部

请结合下面的代码和图去理解双向链表,不管花多少时间,一定要理解它的插入/删除动作, 否则后续内容将无从谈起.

//将指定节点初始化为双向链表节点
LITE_OS_SEC_ALW_INLINE STATIC INLINE VOID LOS_ListInit(LOS_DL_LIST *list)
{
    list->pstNext = list;
    list->pstPrev = list;
}

//将指定节点挂到双向链表头部
LITE_OS_SEC_ALW_INLINE STATIC INLINE VOID LOS_ListAdd(LOS_DL_LIST *list, LOS_DL_LIST *node)
{
    node->pstNext = list->pstNext;
    node->pstPrev = list;
    list->pstNext->pstPrev = node;
    list->pstNext = node;
}
//将指定节点从链表中删除,自己把自己摘掉
LITE_OS_SEC_ALW_INLINE STATIC INLINE VOID LOS_ListDelete(LOS_DL_LIST *node)
{
    node->pstNext->pstPrev = node->pstPrev;
    node->pstPrev->pstNext = node->pstNext;
    node->pstNext = NULL;
    node->pstPrev = NULL;
}
//将指定节点从链表中删除,并使用该节点初始化链表
LITE_OS_SEC_ALW_INLINE STATIC INLINE VOID LOS_ListDelInit(LOS_DL_LIST *list)
{
    list->pstNext->pstPrev = list->pstPrev;
    list->pstPrev->pstNext = list->pstNext;
    LOS_ListInit(list);
}

此处仅列出 LOS_ListDelInit

强大的宏

除了内联函数,对双向链表的初始化,偏移定位,遍历 等等操作提供了更强大的宏支持.使内核以极其简洁高效的代码实现复杂逻辑的处理.

//定义一个节点并初始化为双向链表节点
#define LOS_DL_LIST_HEAD(list) LOS_DL_LIST list = { &(list), &(list) }

//获取指定结构体内的成员相对于结构体起始地址的偏移量
#define LOS_OFF_SET_OF(type, member) ((UINTPTR)&((type *)0)->member)

//获取包含链表的结构体地址,接口的第一个入参表示的是链表中的某个节点,第二个入参是要获取的结构体名称,第三个入参是链表在该结构体中的名称
#define LOS_DL_LIST_ENTRY(item, type, member) \
    ((type *)(VOID *)((CHAR *)(item) - LOS_OFF_SET_OF(type, member)))

//遍历双向链表
#define LOS_DL_LIST_FOR_EACH(item, list) \
    for (item = (list)->pstNext;         \
         (item) != (list);               \
         item = (item)->pstNext)

//遍历指定双向链表,获取包含该链表节点的结构体地址,并存储包含当前节点的后继节点的结构体地址
#define LOS_DL_LIST_FOR_EACH_ENTRY_SAFE(item, next, list, type, member)               \
    for (item = LOS_DL_LIST_ENTRY((list)->pstNext, type, member),                     \
         next = LOS_DL_LIST_ENTRY((item)->member.pstNext, type, member);              \
         &(item)->member != (list);                                                   \
         item = next, next = LOS_DL_LIST_ENTRY((item)->member.pstNext, type, member))

//遍历指定双向链表,获取包含该链表节点的结构体地址
#define LOS_DL_LIST_FOR_EACH_ENTRY(item, list, type, member)             \
    for (item = LOS_DL_LIST_ENTRY((list)->pstNext, type, member);        \
         &(item)->member != (list);                                      \
         item = LOS_DL_LIST_ENTRY((item)->member.pstNext, type, member))

LOS_OFF_SET_OF 和 LOS_DL_LIST_ENTRY

这里要重点说下 LOS_OFF_SET_OFLOS_DL_LIST_ENTRY两个宏,个人认为它们是链表操作中最关键,最重要的宏.在读内核源码的过程会发现LOS_DL_LIST_ENTRY高频的出现,它们解决了通过结构体的任意一个成员变量来找到结构体的入口地址.
这个意义重大,因为在运行过程中,往往只能提供成员变量的地址,那它是如何做到通过个人找到组织的呢?

  • LOS_OFF_SET_OF找到成员变量在结构体中的相对偏移位置. 在系列篇 用栈方式篇中 已说过 鸿蒙采用的是递减满栈的方式.以ProcessCB 结构体举例
typedef struct ProcessCB {
    //...此处省略其他变量
    LOS_DL_LIST          pendList;                     /**< Block list to which the process belongs */ //进程所属的阻塞列表,如果因拿锁失败,就由此节点挂到等锁链表上
    LOS_DL_LIST          childrenList;                 /**< my children process list */	//孩子进程都挂到这里,形成双循环链表
    LOS_DL_LIST          exitChildList;                /**< my exit children process list */	//那些要退出孩子进程挂到这里,白发人送黑发人。
    LOS_DL_LIST          siblingList;                  /**< linkage in my parent's children list */ //兄弟进程链表, 56个民族是一家,来自同一个父进程.
    LOS_DL_LIST          subordinateGroupList;         /**< linkage in my group list */ //进程是组长时,有哪些组员进程
    LOS_DL_LIST          threadSiblingList;            /**< List of threads under this process *///进程的线程(任务)列表
    LOS_DL_LIST          threadPriQueueList[OS_PRIORITY_QUEUE_NUM]; /**< The process's thread group schedules thepriority hash table */	//进程的线程组调度优先级哈希表
    LOS_DL_LIST          waitList;     /**< The process holds the waitLits to support wait/waitpid *///进程持有等待链表以支持wait/waitpid
} LosProcessCB;

waitList因为在结构体的后面,所以它内存地址会比在前面的pendList高,有了顺序方向就很容易得到ProcessCB的第一个变量的地址.LOS_OFF_SET_OF就是干这个的,含义就是相对第一个变量地址,你waitList偏移了多少.

  • 如此,当外面只提供waitList的地址再减去偏移地址 就可以得到ProcessCB的起始地址.
#define LOS_DL_LIST_ENTRY(item, type, member) \
    ((type *)(VOID *)((CHAR *)(item) - LOS_OFF_SET_OF(type, member)))

当然如果提供pendListexitChildList的地址道理一样.LOS_DL_LIST_ENTRY实现了通过任意成员变量来获取ProcessCB的起始地址.

OsGetTopTask

有了以上对链表操作的宏,可以使得代码变得简洁易懂,例如在调度算法中获取当前最高优先级的任务时,就需要遍历整个进程和其任务的就绪列表.LOS_DL_LIST_FOR_EACH_ENTRY高效的解决了层层循环的问题.

LITE_OS_SEC_TEXT_MINOR LosTaskCB *OsGetTopTask(VOID)
{
    UINT32 priority, processPriority;
    UINT32 bitmap;
    UINT32 processBitmap;
    LosTaskCB *newTask = NULL;
#if (LOSCFG_KERNEL_SMP == YES)
    UINT32 cpuid = ArchCurrCpuid();
#endif
    LosProcessCB *processCB = NULL;
    processBitmap = g_priQueueBitmap;
    while (processBitmap) {
        processPriority = CLZ(processBitmap);
        LOS_DL_LIST_FOR_EACH_ENTRY(processCB, &g_priQueueList[processPriority], LosProcessCB, pendList) {
            bitmap = processCB->threadScheduleMap;
            while (bitmap) {
                priority = CLZ(bitmap);
                LOS_DL_LIST_FOR_EACH_ENTRY(newTask, &processCB->threadPriQueueList[priority], LosTaskCB, pendList) {
#if (LOSCFG_KERNEL_SMP == YES)
                    if (newTask->cpuAffiMask & (1U << cpuid)) {
#endif
                        newTask->taskStatus &= ~OS_TASK_STATUS_READY;
                        OsPriQueueDequeue(processCB->threadPriQueueList,
                                          &processCB->threadScheduleMap,
                                          &newTask->pendList);
                        OsDequeEmptySchedMap(processCB);
                        goto OUT;
#if (LOSCFG_KERNEL_SMP == YES)
                    }
#endif
                }
                bitmap &= ~(1U << (OS_PRIORITY_QUEUE_NUM - priority - 1));
            }
        }
        processBitmap &= ~(1U << (OS_PRIORITY_QUEUE_NUM - processPriority - 1));
    }

OUT:
    return newTask;
}

结构体的最爱

LOS_DL_LIST是复杂结构体的最爱,再以 ProcessCB(进程控制块)举例,它是描述一个进程的所有信息,其中用到了 8个双向链表,这简直比章鱼还牛逼,章鱼也才四双触手,但进程有8双(16只)触手.

typedef struct ProcessCB {
    //...此处省略其他变量
    LOS_DL_LIST          pendList;                     /**< Block list to which the process belongs */ //进程所属的阻塞列表,如果因拿锁失败,就由此节点挂到等锁链表上
    LOS_DL_LIST          childrenList;                 /**< my children process list */	//孩子进程都挂到这里,形成双循环链表
    LOS_DL_LIST          exitChildList;                /**< my exit children process list */	//那些要退出孩子进程挂到这里,白发人送黑发人。
    LOS_DL_LIST          siblingList;                  /**< linkage in my parent's children list */ //兄弟进程链表, 56个民族是一家,来自同一个父进程.
    LOS_DL_LIST          subordinateGroupList;         /**< linkage in my group list */ //进程是组长时,有哪些组员进程
    LOS_DL_LIST          threadSiblingList;            /**< List of threads under this process *///进程的线程(任务)列表
    LOS_DL_LIST          threadPriQueueList[OS_PRIORITY_QUEUE_NUM]; /**< The process's thread group schedules thepriority hash table */	//进程的线程组调度优先级哈希表
    LOS_DL_LIST          waitList;     /**< The process holds the waitLits to support wait/waitpid *///进程持有等待链表以支持wait/waitpid
} LosProcessCB;

解读

  • pendList 个人认为它是鸿蒙内核功能最多的一个链表,它远不止字面意思阻塞链表这么简单,只有深入解读源码后才能体会它真的是太会来事了,一般把它理解为阻塞链表就行.上面挂的是处于阻塞状态的进程.
  • childrenList孩子链表,所有由它fork出来的进程都挂到这个链表上.上面的孩子进程在死亡前会将自己从上面摘出去,转而挂到exitChildList链表上.
  • exitChildList退出孩子链表,进入死亡程序的进程要挂到这个链表上,一个进程的死亡是件挺麻烦的事,进程池的数量有限,需要及时回收进程资源,但家族管理关系复杂,要去很多地方消除痕迹.尤其还有其他进程在看你笑话,等你死亡(wait/waitpid)了通知它们一声.
  • siblingList兄弟链表,和你同一个父亲的进程都挂到了这个链表上.
  • subordinateGroupList 朋友圈链表,里面是因为兴趣爱好(进程组)而挂在一起的进程,它们可以不是一个父亲,不是一个祖父,但一定是同一个老祖宗(用户态和内核态根进程).
  • threadSiblingList线程链表,上面挂的是进程ID都是这个进程的线程(任务),进程和线程的关系是1:N的关系,一个线程只能属于一个进程.这里要注意任务在其生命周期中是不能改所属进程的.
  • threadPriQueueList线程的调度队列数组,一共32个,任务和进程一样有32个优先级,调度算法的过程是先找到优先级最高的进程,在从该进程的任务队列里去最高的优先级任务运行.
  • waitList 是等待子进程消亡的任务链表,注意上面挂的是任务.任务是通过系统调用
pid_t wait(int *status);
pid_t waitpid(pid_t pid, int *status, int options);

将任务挂到waitList上.鸿蒙waitpid系统调用为SysWait,具体看进程回收篇.

双向链表是内核最重要的结构体,精读内核的路上它会反复的映入你的眼帘,理解它是理解内核运作的关键所在!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2074064.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Linux-部署YUM仓库及NFS共享服务

系列文章目录 提示&#xff1a;仅用于个人学习&#xff0c;进行查漏补缺使用。 1.Linux-网络设置 2.Linux-DHCP服务、vsftp 3.Linux-DNS域名解析服务 4.Linux-远程访问及控制 提示&#xff1a;写完文章后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档…

【GIS系列】多源异构原始影像解析:策略模式与规则引擎的应用

作者&#xff1a;后端小肥肠 &#x1f347; 我写过的文章中的相关代码放到了gitee&#xff0c;地址&#xff1a;xfc-fdw-cloud: 公共解决方案 &#x1f34a; 有疑问可私信或评论区联系我。 &#x1f951; 创作不易未经允许严禁转载。 1. 前言 在遥感技术和地球观测领域&#…

机器学习周报(8.19-8.25

文章目录 摘要Abstract1.PyTorch环境的配置及安装使用PyCharm配置环境安装配置jupyter 2.两个Python常用函数3.DataSet4.TensorBoard的使用绘制一幅坐标图使用tendorboard对一幅图片进行操作 5.torchvison中的transforms总结 摘要 在之前学习了一些机器学习相关理论之后&#…

【限时免费发放】2000+热门Scratch作品源码

欢迎访问小虎鲸Scratch资源站&#xff01;在Scratch编程的学习过程中&#xff0c;优质的资源和灵感是至关重要的。因此&#xff0c;我们特别准备了这份【免费下载】的Scratch作品源码合集&#xff0c;包含超过2000个精选的Scratch项目源码&#xff0c;旨在为Scratch编程爱好者和…

Nginx+keepalived实现高可用

目录 一、准备工作 1、安装软件 二、配置master主服务器 1、修改nginx主配置文件 ​编辑 2、修改keepalived主配置文件 三、配置backup备服务器 四、配置节点服务器 1、配置第一个节点服务器 1.1、设置虚拟网卡 1.2、忽略ARP请求 1.3、安装并启动nginx软件 2、配置…

【STM32】SPI接口(非连续传输)

本篇博客重点在于标准库函数的理解与使用&#xff0c;搭建一个框架便于快速开发 目录 前言 SPI简介 IO口初始化 SPI配置 时钟使能 SPI初始化 SPI使能 数据接收与发送 硬件SPI代码 MySPI.h MySPI.c 前言 【通信协议】SPI总线-CSDN博客 本篇博客学习使用STM32的…

Expo 开发ReactNative 后切换 eas 账号

修改slug app.json中的sulg字段更新为新账号应用sulg 修改projectId app.json中的extra.eas.projectId字段更新为新账号应用projectId 退出账号&#xff1a; eas logout 重新登录&#xff1a; eas login

基于WEB的学生综合测评系统的设计与实现

第1章 绪 论 1.1 系统开发背景 随着计算机技术的不断发展,在现代化社会中,信息的处理越来越依赖计算机来完成。在没有测评系统之前&#xff0c;所有的信息记录都是由人工记录维护&#xff0c;不但不方便&#xff0c;还经常出错&#xff0c;有了测评系统就可以方便的对各种信息…

.net maui安卓开发中使用明文传输(一)

背景:最近在做一个pad上的项目,目的是执行每日点检功能(就是检查设备的各项保养指标);前期用HBuilder做了一个,但是现场的触摸屏选用的是TouchPie 安卓版本是6.0版本,上次开发的软件可以在安卓7.0上完美兼容,但由于触摸屏安卓版本太低不能兼容;询问厂商才知道这款触摸…

【Datawhale AI夏令营】从零上手CV竞赛Task1

【Datawhale AI夏令营】从零上手CV竞赛Task1 前言赛事官网学习任务TASK 1 前言 这个暑假我通过微信群关注到了【Datawhale AI夏令营】&#xff0c;并且记录我的代码学习以及调试的过程。 赛事官网 赛事官网&#xff1a;https://www.marsbigdata.com/competition/details?id…

RING总线(环形总线)Intel大小核的由来

想到Intel处理器&#xff0c;无一例外大家想到的就是现在的它具有大小核的结构&#xff0c;也就是性能核和效能核。这种结构的由来是由RING总线布局结构而诞生。ring总线&#xff08;环形总线&#xff09;&#xff0c;这个名字经常出现在讨论普通消费级处理器的时候&#xff0c…

算法【Java】—— 二分查找

二分查找算法分析 二分查找算法其实也是对撞指针的另一种用法&#xff0c;左右两个指针分别指向数据的左右端点&#xff0c;然后双指针向中间移动。 朴素二分查找 上面这道题是朴素的二分查找算法&#xff0c;由于数据是有序的&#xff0c;我们可以从中间值入手 如果中间值大…

国货之光|暴雨机推出面向大模型训练的AI服务器

当前&#xff0c;“百模大战”带来了算力需求的爆发&#xff0c;尤其是以ChatGPT为代表的多模态AI大模型&#xff0c;‌其参数规模和训练数据量均达到了前所未有的规模。‌GPT-3.5、‌GPT-4等模型的参数规模突破了万亿级别&#xff0c;‌训练数据量达到了TB级别&#xff0c;‌这…

AI多模态模型架构之模态生成器:Modality Generator

〔探索AI的无限可能&#xff0c;微信关注“AIGCmagic”公众号&#xff0c;让AIGC科技点亮生活〕 本文作者&#xff1a;AIGCmagic社区 刘一手 前言 AI多模态大模型发展至今&#xff0c;每年都有非常优秀的工作产出&#xff0c;按照当前模型设计思路&#xff0c;多模态大模型的…

Ai+若依(定时任务):【01篇】

若依为定时任务功能提供方便友好的web界面,实现动态管理任务。 案例 每间隔5秒,控制台输出系统时间。 实现步骤: ①创建任务类 在服务 ==》ruoyi-quartz==》task==》MyTask package com.ruoyi.quartz.task;import org.springframework.stereotype.Component;@Component…

程序员修炼之道 10:并发

不记录&#xff0c;等于没读。 这里是我阅读《程序员修炼之道》这本书的记录和思考。 并发和并行代码曾经是很新奇的玩意&#xff0c;但现在已经是标配。 并发 &#xff1a;Concurrency&#xff0c;指的是两个或更多个代码段在执行过程中表现得像是在同时运行一样。 并行 &am…

AD的3D模型格式是什么

AD通常指的是Altium Designer&#xff0c;这是一款用于电子设计自动化的软件&#xff0c;主要用于电路板的设计。在Altium Designer中&#xff0c;3D模型主要用于PCB&#xff08;印制电路板&#xff09;设计中的可视化&#xff0c;以便设计师能够在三维空间中查看组件和板的布局…

21:【stm32】定时器二:输入捕获

输出捕获 1、输出捕获的简介2、输入捕获原理2.1、输入滤波2.2、信号选择2.3、CCx事件和CCx中断 3、标准库编程 1、输出捕获的简介 定时器通过通道捕获输入电平的变化&#xff0c;而通道有CH1~CH4&#xff0c;这4个通道。 我们设定对通道CH3的输入电平进行捕获。假如CH3突然捕获…

Java排序算法详解

在Java编程中&#xff0c;排序算法是数据处理和算法设计中的基础且重要的部分。它们广泛应用于各种场景&#xff0c;如数据库管理、数据分析、图形界面排序等。掌握几种常见的排序算法对于提升程序效率和优化性能至关重要。本文将详细解析几种经典的Java排序算法&#xff0c;包…

OpenGL笔记二十之深度检测概念

OpenGL笔记二十之深度检测概念 —— 2024-08-25 晚上 bilibili赵新政老师的教程看后笔记 code review! 文章目录 OpenGL笔记二十之深度检测概念1.课程PPT截图2.运行3.代码 1.课程PPT截图 2.运行 3.代码 关键部分 main.cpp #include <iostream>#include "glfra…