[文章]

鸿蒙内核源码分析(双循环链表篇) :内核最重要结构体

2020-11-24 13:39:32  447 鸿蒙系统 内核 源码
分享
2
鸿蒙源码分析系列文章图解鸿蒙内核, 从 HarmonyOS 架构层视角整理成文, 并首创用生活场景讲故事的方式试图去解构内核,一窥究竟。

为何鸿蒙内核源码分析系列开篇就说 LOS_DL_LIST ?因为它在鸿蒙 LOS 内核中无处不在,在整个内核占了极大的比重,豪不夸张的说理解LOS_DL_LIST及相关函数是读懂鸿蒙内核的关键。前后指针就像人的两只左右手一样灵活的指挥着系统精准的运行,越是深入分析内核源码,越能感受到内核开发者对LOS_DL_LIST非凡的驾驭能力,笔者仿佛看到了无数双手前后相连,拉起了一个个双向循环链表,把指针的高效能运用到了极致,这也许就是编程的艺术吧!

致敬鸿蒙内核开发者贡献了如此优秀的源码,鸿蒙内核源码可作为大学C语言,操作系统,数据结构,汇编语言四门课的教学项目。
  1. /**
  2. * @ingroup los_list
  3. * Structure of a node in a doubly linked list.
  4. */
  5. typedef struct LOS_DL_LIST {
  6.     struct LOS_DL_LIST *pstPrev; /**< Current node's pointer to the previous node */
  7.     struct LOS_DL_LIST *pstNext; /**< Current node's pointer to the next node */
  8. } LOS_DL_LIST;

  9. LITE_OS_SEC_ALW_INLINE STAtiC INLINE VOID LOS_ListInit(LOS_DL_LIST *list)
  10. {
  11.     list->pstNext = list;
  12.     list->pstPrev = list;
  13. }
复制代码
结构体很简单,就前后两个指向自己的指针,因简单才经典,因太简单才太不简单. 真的是无处不在吗?答:是真的,看看这些使用它的源码吧,无处不在。
基本概念
双向链表是指含有往前和往后两个方向的链表,即每个结点中除存放下一个节点指针外,还增加一个指向其前一个节点的指针。其头指针head是唯一确定的。从双向链表中的任意一个结点开始,都可以很方便地访问它的前驱结点和后继结点,这种数据结构形式使得双向链表在查找时更加方便,特别是大量数据的遍历。由于双向链表具有对称性,能方便地完成各种插入、删除等操作,但需要注意前后方向的操作。

功能接口
Huawei LiteOS系统中的双向链表模块为用户提供下面几个接口。
鸿蒙使用了双向循环链表来实现结构体数据结构之间的关联,支持单个节点的头尾插入,更精妙的是链表中支持插入另一个链表,将两个循环链表合成一个大循环链表,实现极为巧妙和简单。详见代码:
  1. //双向链表初始化
  2. LITE_OS_SEC_ALW_INLINE STATIC INLINE VOID LOS_ListInit(LOS_DL_LIST *list)
  3. {
  4.     list->pstNext = list; // 前后指针都指向自己
  5.     list->pstPrev = list;
  6. }

  7. //链表判空,检查前后指针是否指向自己
  8. LITE_OS_SEC_ALW_INLINE STATIC INLINE BOOL LOS_ListEmpty(LOS_DL_LIST *list)
  9. {
  10.     return (BOOL)(list->pstNext == list);
  11. }

  12. //从链表中删除节点
  13. LITE_OS_SEC_ALW_INLINE STATIC INLINE VOID LOS_ListDelete(LOS_DL_LIST *node)
  14. {
  15.     node->pstNext->pstPrev = node->pstPrev;
  16.     node->pstPrev->pstNext = node->pstNext;
  17.     node->pstNext = NULL;
  18.     node->pstPrev = NULL;
  19. }

  20. //指针互换,具体向双向循环链表中插入节点
  21. LITE_OS_SEC_ALW_INLINE STATIC INLINE VOID LOS_ListAdd(LOS_DL_LIST *list, LOS_DL_LIST *node)
  22. {
  23.     node->pstNext = list->pstNext;
  24.     node->pstPrev = list;
  25.     list->pstNext->pstPrev = node;
  26.     list->pstNext = node;
  27. }

  28. // 两个循环链表合成一个大循环列表
  29. LITE_OS_SEC_ALW_INLINE STATIC INLINE VOID LOS_ListAddList(LOS_DL_LIST *oldList, LOS_DL_LIST *newList)
  30. {
  31.     // 先用临时指针记录头尾位置
  32.     LOS_DL_LIST *oldListHead = oldList->pstNext;
  33.     LOS_DL_LIST *oldListTail = oldList;
  34.     LOS_DL_LIST *newListHead = newList;
  35.     LOS_DL_LIST *newListTail = newList->pstPrev;
  36.     // 前后指针完成切换
  37.     oldListTail->pstNext = newListHead;
  38.     newListHead->pstPrev = oldListTail;
  39.     oldListHead->pstPrev = newListTail;
  40.     newListTail->pstNext = oldListHead;
  41. }
  42. // 这里与其说插入不如说合并,同样支持从头或尾部合并
  43. LITE_OS_SEC_ALW_INLINE STATIC INLINE VOID LOS_ListTailInsertList(LOS_DL_LIST *oldList, LOS_DL_LIST *newList)
  44. {
  45.     LOS_ListAddList(oldList->pstPrev, newList);
  46. }
  47. LITE_OS_SEC_ALW_INLINE STATIC INLINE VOID LOS_ListHeadInsertList(LOS_DL_LIST *oldList, LOS_DL_LIST *newList)
  48. {
  49.     LOS_ListAddList(oldList, newList);
  50. }
复制代码
大家在阅读鸿蒙内核源码要时刻带着 LOS_DL_LIST 去理解结构体之间的关联,构想运行时的场景是怎样的,就能体会到内核代码之精妙。

具体的使用场景
看下它其中的一个使用场景吧,体验设计者的奇妙用心,上代码。
  1. typedef struct ProcessCB {
  2.     CHAR                 processName[OS_PCB_NAME_LEN]; /**< Process name */
  3.     UINT32               processID;                    /**< process ID = leader thread ID */
  4.     UINT16               processStatus;                /**< [15:4] process Status; [3:0] The number of threads currently
  5.                                                             running in the process */
  6.     LOS_DL_LIST          pendList;                     /**< Block list to which the process belongs */
  7.     LOS_DL_LIST          childrenList;                 /**< my children process list */
  8.     LOS_DL_LIST          exitChildList;                /**< my exit children process list */
  9.     LOS_DL_LIST          siblingList;                  /**< linkage in my parent's children list */
  10.     ProcessGroup         *group;                       /**< Process group to which a process belongs */
  11.     LOS_DL_LIST          subordinateGroupList;         /**< linkage in my group list */
  12.     UINT32               threadGroupID;                /**< Which thread group , is the main thread ID of the process */
  13.     UINT32               threadScheduleMap;            /**< The scheduling bitmap table for the thread group of the
  14.                                                             process */
  15.     LOS_DL_LIST          threadSiblingList;            /**< List of threads under this process */
  16.     LOS_DL_LIST          threadPriQueueList[OS_PRIORITY_QUEUE_NUM]; /**< The process's thread group schedules the
  17.     LOS_DL_LIST          waitList;     /**< The process holds the waitLits to support
  18. } LosProcessCB;
复制代码
这是LosProcessCB(进程控制块),因为结构体很复杂,省去了其他定义,留下LOS_DL_LIST 相关的,LosProcessCB包含了 七个双向循环链表,这些链表承载的是一个进程在期生命周期内的运行的过程逻辑,与其他模块的关系逻辑等等,是的,必须要有如此复杂的又简洁的数据结构才能将进程的实现复杂度降到最低。内核源码就像那个每次考试都要考100分的孩子一样,  不管从宏观架构层面还是微观代码层面都要优秀到极致才能让用户进程安全而稳定的运行。

再看调度队列涉及的代码这是 进程/任务 出队入队的操作,背后都是LOS_DL_LIST的增删过程,这些宏在内核中大规模的使用, 请用心体会这些宏。
  1. #define OS_PROCESS_PRI_QUEUE_SIZE(processCB) OsPriQueueProcessSize(g_priQueueList, (processCB)->priority)

  2. #define OS_TASK_PRI_QUEUE_ENQUEUE(processCB, taskCB) \
  3.     OsPriQueueEnqueue((processCB)->threadPriQueueList, &((processCB)->threadScheduleMap), \
  4.                       &((taskCB)->pendList), (taskCB)->priority)
  5. #define OS_TASK_PRI_QUEUE_ENQUEUE_HEAD(processCB, taskCB) \
  6.     OsPriQueueEnqueueHead((processCB)->threadPriQueueList, &((processCB)->threadScheduleMap), \
  7.                       &((taskCB)->pendList), (taskCB)->priority)

  8. #define OS_TASK_PRI_QUEUE_DEQUEUE(processCB, taskCB) \
  9.     OsPriQueueDequeue((processCB)->threadPriQueueList, &((processCB)->threadScheduleMap), &((taskCB)->pendList))


  10. #define OS_TASK_SCHED_QUEUE_ENQUEUE(taskCB, status) OsTaskSchedQueueEnqueue(taskCB, status)
  11. #define OS_TASK_SCHED_QUEUE_DEQUEUE(taskCB, status) OsTaskSchedQueueDequeue(taskCB, status)

  12. #define OS_PROCESS_PRI_QUEUE_ENQUEUE(processCB) \
  13.     OsPriQueueEnqueue(g_priQueueList, &g_priQueueBitmap, &((processCB)->pendList), (processCB)->priority)
  14. #define OS_PROCESS_PRI_QUEUE_ENQUEUE_HEAD(processCB) \
  15.     OsPriQueueEnqueueHead(g_priQueueList, &g_priQueueBitmap, &((processCB)->pendList), (processCB)->priority)
  16. #define OS_PROCESS_PRI_QUEUE_DEQUEUE(processCB) OsPriQueueProcessDequeue(&((processCB)->pendList))

  17. #define OS_TASK_PRI_QUEUE_SIZE(processCB, taskCB) OsPriQueueSize((processCB)->threadPriQueueList, (taskCB)->priority)
  18. #define OS_TASK_GET_NEW(processCB) LOS_DL_LIST_ENTRY(OsPriQueueTop((processCB)->threadPriQueueList,     \
  19.                                                                     &((processCB)->threadScheduleMap)), \
  20.                                                      LosTaskCB, pendList)
复制代码
内联函数 inline鸿蒙内核大量的使用了内联函数,源码中只有los_list.h,木有.c 文件!这些调用最最频繁的内联函数,免去了像普通函数要出栈入栈的时间和空间,效率极高。
  1. /* Define OS code data sections */
  2. /* The indicator function is inline */
  3. #ifndef LITE_OS_SEC_ALW_INLINE
  4. #define LITE_OS_SEC_ALW_INLINE  /* __attribute__((always_inline)) */
  5. #endif

  6. LITE_OS_SEC_ALW_INLINE STATIC INLINE VOID LOS_ListAdd(LOS_DL_LIST *list, LOS_DL_LIST *node)
  7. {
  8.     node->pstNext = list->pstNext;
  9.     node->pstPrev = list;
  10.     list->pstNext->pstPrev = node;
  11.     list->pstNext = node;
  12. }
  13. LITE_OS_SEC_ALW_INLINE STATIC INLINE VOID LOS_ListTailInsert(LOS_DL_LIST *list, LOS_DL_LIST *node)
  14. {
  15.     LOS_ListAdd(list->pstPrev, node);
  16. }
复制代码
LOS_DL_LIST让内核结构变得简单,高效,是最重要的结构体。

系列篇文章 进入 >> 鸿蒙系统源码分析(总目录) 查看
注释中文版 进入 >> 鸿蒙内核源码注释中文版 【 Gitee仓】阅读
文章来源:图解鸿蒙原始码逐行注释分析

评论

您需要登录后才可以回帖 登录 | 注册

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容图片侵权或者其他问题,请联系本站作侵删。 侵权投诉
发文章