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

双向链表是什么?

谁是鸿蒙内核最重要的结构体 ? 一定是: 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;

在 linux 中是 list_head, 很简单,只有两个指向自己的指针,但因为太简单,所以不简单。站长更愿意将它比喻成人的左右手,其意义是通过寄生在宿主结构体上来体现,可想象成在宿主结构体装上一对对勤劳的双手,它真的很会来事,超级活跃分子,为宿主到处拉朋友,建圈子。

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

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

  • 可以豪不夸张的说理解 LOS_DL_LIST 及相关函数是读懂鸿蒙内核的关键。前后指针 (左右触手) 灵活的指挥着系统精准的运行,越是深挖内核代码越是能体会到它在内核举足轻重的地位, 笔者仿佛看到了无数双手前后相连,拉起了一个个双向循环链表,把指针的高效能运用到了极致,这也许就是编程的艺术吧!

怎么实现 ?

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

其插入 | 删除 | 遍历操作是它最常用的社交三大件,若不理解透彻在分析源码过程中很容易卡壳。虽在网上能找到很多它的图,但怎么看都不是自己想要的,干脆重画了它的主要操作。

//将指定节点初始化为双向链表节点
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_DL_LIST 这个结构看起来怪怪的,它竟没有数据域!所以看到这个结构的人第一反应就是我们怎么访问数据?其实 LOS_DL_LIST 不是拿来单独用的,它是寄生在内容结构体上的,谁用它谁就是它的数据。看图就明白了。

强大的宏

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

//定义一个节点并初始化为双向链表节点
#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_OF 和 LOS_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;                 /**< Children process list | 孩子进程都挂到这里,形成双循环链表*/LOS_DL_LIST          exitChildList;                /**< Exit children process list | 要退出的孩子进程链表,白发人要送黑发人.*/LOS_DL_LIST          siblingList;                  /**< Linkage in parent's children list | 兄弟进程链表, 56个民族是一家,来自同一个父进程.*/LOS_DL_LIST          subordinateGroupList;         /**< Linkage in group list | 进程组员链表*/LOS_DL_LIST          threadSiblingList;            /**< List of threads under this process | 进程的线程(任务)列表 */LOS_DL_LIST          waitList;     /**< The process holds the waitLits to support 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)))

当然如果提供 pendList 或 exitChildList 的地址道理一样。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();
#endifLosProcessCB *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)) {
#endifnewTask->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(进程控制块) 举例,它是描述一个进程的所有信息,其中用到了 7 个双向链表,这简直比章鱼还牛逼,章鱼也才四双触手,但进程有 7 双 (14 只) 触手。

typedef struct ProcessCB {LOS_DL_LIST          pendList;                     /**< Block list to which the process belongs | 进程所在的阻塞列表,进程因阻塞挂入相应的链表.*/LOS_DL_LIST          childrenList;                 /**< Children process list | 孩子进程都挂到这里,形成双循环链表*/LOS_DL_LIST          exitChildList;                /**< Exit children process list | 要退出的孩子进程链表,白发人要送黑发人.*/LOS_DL_LIST          siblingList;                  /**< Linkage in parent's children list | 兄弟进程链表, 56个民族是一家,来自同一个父进程.*/LOS_DL_LIST          subordinateGroupList;         /**< Linkage in group list | 进程组员链表*/LOS_DL_LIST          threadSiblingList;            /**< List of threads under this process | 进程的线程(任务)列表 */LOS_DL_LIST          waitList;     /**< The process holds the waitLits to support wait/waitpid | 父进程通过进程等待的方式,回收子进程资源,获取子进程退出信息*/
} LosProcessCB;

解读

  • pendList 个人认为它是鸿蒙内核功能最多的一个链表,它远不止字面意思阻塞链表这么简单,只有深入解读源码后才能体会它真的是太会来事了,一般把它理解为阻塞链表就行。上面挂的是处于阻塞状态的进程。

  • childrenList 孩子链表,所有由它 fork 出来的进程都挂到这个链表上。上面的孩子进程在死亡前会将自己从上面摘出去,转而挂到 exitChildList 链表上。

  • exitChildList 退出孩子链表,进入死亡程序的进程要挂到这个链表上,一个进程的死亡是件挺麻烦的事,进程池的数量有限,需要及时回收进程资源,但家族管理关系复杂,要去很多地方消除痕迹。尤其还有其他进程在看你笑话,等你死亡 (wait/waitpid) 了通知它们一声。

  • siblingList 兄弟链表,和你同一个父亲的进程都挂到了这个链表上。

  • subordinateGroupList 朋友圈链表,里面是因为兴趣爱好 (进程组) 而挂在一起的进程,它们可以不是一个父亲,不是一个祖父,但一定是同一个老祖宗 (用户态和内核态根进程)。

  • threadSiblingList 线程链表,上面挂的是进程 ID 都是这个进程的线程 (任务),进程和线程的关系是 1:N 的关系,一个线程只能属于一个进程。这里要注意任务在其生命周期中是不能改所属进程的。

  • waitList 是等待子进程消亡的任务链表,注意上面挂的是任务。任务是通过系统调用

    pid_t wait(int *status);
    pid_t waitpid(pid_t pid, int *status, int options);

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

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

为了能让大家更好的学习鸿蒙(HarmonyOS NEXT)开发技术,这边特意整理了《鸿蒙开发学习手册》(共计890页),希望对大家有所帮助:https://qr21.cn/FV7h05

《鸿蒙开发学习手册》:

如何快速入门:https://qr21.cn/FV7h05

  1. 基本概念
  2. 构建第一个ArkTS应用
  3. ……

开发基础知识:https://qr21.cn/FV7h05

  1. 应用基础知识
  2. 配置文件
  3. 应用数据管理
  4. 应用安全管理
  5. 应用隐私保护
  6. 三方应用调用管控机制
  7. 资源分类与访问
  8. 学习ArkTS语言
  9. ……

基于ArkTS 开发:https://qr21.cn/FV7h05

  1. Ability开发
  2. UI开发
  3. 公共事件与通知
  4. 窗口管理
  5. 媒体
  6. 安全
  7. 网络与链接
  8. 电话服务
  9. 数据管理
  10. 后台任务(Background Task)管理
  11. 设备管理
  12. 设备使用信息统计
  13. DFX
  14. 国际化开发
  15. 折叠屏系列
  16. ……

鸿蒙开发面试真题(含参考答案):https://qr18.cn/F781PH

鸿蒙开发面试大盘集篇(共计319页):https://qr18.cn/F781PH

1.项目开发必备面试题
2.性能优化方向
3.架构方向
4.鸿蒙开发系统底层方向
5.鸿蒙音视频开发方向
6.鸿蒙车载开发方向
7.鸿蒙南向开发方向

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

AFCI 应用笔记三、使用 mlflow 管理模型

1. 简介 由于 AI 神经网络涉及多种参数&#xff0c;需要频繁修改各种超参数&#xff0c;比如&#xff1a;learning rate&#xff0c;batchsize&#xff0c;filter numbers&#xff0c;alpha 等等&#xff0c;每个参数都有可能影响到模型最终的准确率&#xff0c;所以比较这些参…

如何保证全部流量走代理

最近因为某些原因&#xff0c;需要做一些确保高匿的事情&#xff0c;便花时间做了一定的调研&#xff0c;至于是什么事情这里不便多说。 本文主要还是聊聊我看到的一些使用代理软件误区和确保流量全部走代理的方法&#xff0c;甚至也可以说是Proxifier的用户使用手册&#xff…

2023年下半年中级软件设计师上午真题及答案解析

01 02 03 04 05 06 07 08 09 10 篇幅有限&#xff0c;私我获取免费完整 pdf文件

如何在Linux中安装软件

文章目录 一、Linux应用程序基础1.Linux软件安装包分类2.应用程序和系统命令的关系3.常见的软件包的封装类型 二、安装软件的方式1.RPM包管理工具2.yum安装3.编译 一、Linux应用程序基础 1.Linux软件安装包分类 Linux源码包&#xff1a; 实际上&#xff0c;源码包就是一大堆源…

海纳斯删除广告位

找到文件 vim /var/www/html/home.php 删除代码段 <div class"adleft" id"adleftContainer"><button onclick"closeAd()">关闭</button><a href"https://www.ecoo.top/ad.html" target"_blank">&l…

bugku-misc 啊哒

拿到题目得到一张图片 尝试查看属性看到照相机型号 应该是加密字符&#xff0c;用010打开图片查看源码 文件结尾看到50 4B&#xff0c;是压缩包形式并且看到flag.txt 猜测是文件包含 kali用foremost尝试分离图片 得到zip文件&#xff0c;打开显示需要密码 想到一开始图片属…

GESP Python编程五级认证真题 2024年3月

Python 五级 2024 年 03 月 1 单选题&#xff08;每题 2 分&#xff0c;共 30 分&#xff09; 第 1 题 下面流程图在yr输入2024时&#xff0c;可以判定yr代表闰年&#xff0c;并输出 2月是29天 &#xff0c;则图中菱形框中应该填入&#xff08; &#xff09;。 A. (yr % 400 0…

LDR6328助力Type-C普及,便捷充电,绿色生活更精彩

随着科技的进步和全球统一接口的需求&#xff0c;Type-C接口正日益受到青睐。越来越多的设备正选择采纳这一先进的接口设计&#xff0c;它的普及无疑在改善着我们的日常生活。 在过往&#xff0c;许多小功率设备如小风扇、蓝牙音箱、桌面台灯以及家用加湿器等&#xff0c;都普遍…

在ChatGPT中,能用DALL·E 3编辑图片啦!

4月3日&#xff0c;OpenAI开始向部分用户&#xff0c;提供在ChatGPT中的DALLE 3图片编辑功能。 DALLE 3是OpenAI在2023年9月20日发布的一款文生图模型&#xff0c;其生成的图片效果可以与Midjourney、leonardo、ideogram等顶级产品媲美&#xff0c;随后被融合到ChatGPT中增强其…

20.2k stars项目搭建私人网盘界面美功能全

Nextcloud是一套用于创建网络硬盘的客户端&#xff0d;服务器软件。其功能与Dropbox相近&#xff0c;但Nextcloud是自由及开放源代码软件&#xff0c;每个人都可以在私人服务器上安装并执行它。 GitHub数据 20.2k stars561 watching3.2k forks 开源地址:https://github.com/ne…

博弈论(Nim+sg)

Nim游戏 给定 n 堆石子&#xff0c;两位玩家轮流操作&#xff0c;每次操作可以从任意一堆石子中拿走任意数量的石子&#xff08;可以拿完&#xff0c;但不能不拿&#xff09;&#xff0c;最后无法进行操作的人视为失败。 问如果两人都采用最优策略&#xff0c;先手是否必胜。…

Android Studio学习15——多页面情况下再看Activity生命周期

按返回键退出APP时&#xff1a; 走正常页面的退出流程&#xff1a;onPause–>onStop–>onDestroy(会Destroy,因为它从任务栈中退出了) 再点击图标回来时&#xff1a; 走正常页面的创建流程&#xff1a;onCreate–>onStart–>onResume 按Home键退出App时&#xff1a…

测试基础|为啥大多数功能测试会觉得测试平台不好用?自动化测试的几点思考

一、接口自动化到底要验证什么 个人觉得做什么事情前&#xff0c;应该想下做的动机和想要达成的目的&#xff0c;这样会减少很多不必要的弯路。 1. 自动化的原因 测试界普遍认为应该加自动化用于提高测试效率和保障&#xff1b; 测试kpi任务&#xff1b; 应对需要频繁执行…

0基础如何进入IT行业?

0基础如何进入IT行业&#xff1f; 简介&#xff1a;对于没有任何相关背景知识的人来说&#xff0c;如何才能成功进入IT行业&#xff1f;是否有一些特定的方法或技巧可以帮助他们实现这一目标&#xff1f; IT 行业可能是当今门槛最低的行业之一&#xff0c;想要进入 IT 行业&a…

【机器学习·浙江大学】机器学习概述、支持向量机SVM(线性模型)

机器学习概述 机器学习 特征提取(feature) 根据提取的特征构造算法&#xff0c;实现特定的任务⭐&#xff08;这门课程的重点&#xff1a;假设所有的特征已经存在且有效&#xff0c;如何根据这些特征来构造学习算法&#xff09; 如下图所示&#xff0c;机器学习主要就是来进行…

FreeRTOSFreeRTOS列表和列表项

FreeRTOS列表和列表项 今天继续跟着正点原子学习FreeRTOS列表和列表项的内容。列表和列表项这个知识点用到了C语言链表的知识点。所以必须对C语言中的链表这个数据结构才能更好的理解这部分内容。TIPS&#xff1a;正点原子这节课内容讲的特别好&#xff0c;强烈推荐&#xff1…

基于SSM的基于个人需求和地域特色的外卖推荐系统(有报告)。Javaee项目。ssm项目。

演示视频&#xff1a; 基于SSM的基于个人需求和地域特色的外卖推荐系统&#xff08;有报告&#xff09;。Javaee项目。ssm项目。 项目介绍&#xff1a; 采用M&#xff08;model&#xff09;V&#xff08;view&#xff09;C&#xff08;controller&#xff09;三层体系结构&…

关于 Python 的 import,你了解多少?

多线程和多进程是 Python 中两种实现多任务的不同策略&#xff0c;二者都可以在特定的场景下在一定程度上提高程序的运行速度、性能以及吞吐&#xff0c;但二者的运行机制却有很大的差别。 在 Python 中&#xff0c;多线程以_并发&#xff08;concurrent&#xff09;的方式运行…

Chapter 1 Basic Concepts of Communication and Communication Systems

1.1 The Concept of Communication communication【通信】:It is the process of using signals to transmit messages containing information in space. To put it simply, communication is the spatial transmission of information【信息的空间传递】Information【信息】…

C语言数组:数据的集合艺术(续)

前言 在上一篇文章中&#xff0c;我们深入探讨了C语言数组的基本概念、操作以及多维数组的应用。今天&#xff0c;我们将继续探索数组的更多高级特性&#xff0c;包括动态内存分配、指针与数组的关系以及数组在实际编程中的应用案例。 一、动态内存分配与数组 在C语言中&…