[文章]鸿蒙操作系统总用到的“微内核”到底是什么?一篇文章带你搞懂

阅读量0
0
2
昨天公众号后台收到一位小友的惊喜问题:

“为何鸿蒙内核源码分析系列开篇就说 LOS_DL_LIST ?”

平常很少收到技术问题,大家都喜欢问老王鸿蒙认证问题,老王都还有点不习惯。

这个问题呢,是因为它在鸿蒙 LOS 内核中无处不在,在整个内核占了极大的比重,豪不夸张的说理解 LOS_DL_LIST 及相关函数是读懂鸿蒙内核的关键。



前后指针就像人的两只左右手一样灵活的指挥着系统精准的运行,越是深入分析内核源码,越能感受到内核开发者对 LOS_DL_LIST 非凡的驾驭能力,笔者仿佛看到了无数双手前后相连,拉起了一个个双向循环链表,把指针的高效能运用到了极致,这也许就是编程的艺术吧!

致敬鸿蒙内核开发者贡献了如此优秀的源码,鸿蒙内核源码可作为大学 C 语言, 操作系统,数据结构,汇编语言四门课的教学项目。

  1. 1. /**
  2. 2. * @ingroup los_list
  3. 3. * Structure of a node in a doubly linked list.
  4. 4. */
  5. 5. typedef struct LOS_DL_LIST {
  6. 6. struct LOS_DL_LIST *pstPrev; /**< Current node's pointer to the previous node */
  7. 7. struct LOS_DL_LIST *pstNext; /**< Current node's pointer to the next node */
  8. 8. } LOS_DL_LIST;
  9. 9.
  10. 10.LITE_OS_SEC_ALW_INLINE STATIC INLINE VOID LOS_ListInit(LOS_DL_LIST *list)
  11. 11.{
  12. 12. list->pstNext = list;
  13. 13. list->pstPrev = list;
  14. 14.}
复制代码

结构体很简单,就前后两个指向自己的指针,因简单才经典,因太简单才太不简单. 真的是无处不在吗?

答:是真的,看看这些使用它的源码吧,无处不在。  



1、基本概念
双向链表是指含有往前和往后两个方向的链表,即每个结点中除存放下一个节点指针外,还增加一个指向其前一个节点的指针。

其头指针 head 是唯一确定的。从双向链表中的任意一个结点开始,都可以很方便地访问它的前驱结点和后继结点,这种数据结构形式使得双向链表在查找时更加方便,特别是大量数据的遍历。

由于双向链表具有对称性,能方便地完成各种插入、删除等操作,但需要注意前后方向的操作。

2、功能接口
Huawei LiteOS 系统中的双向链表模块为用户提供下面几个接口。



鸿蒙使用了双向循环链表来实现结构体数据结构之间的关联,支持单个节点的头尾插入,更精妙的是链表中支持插入另一个链表,将两个循环链表合成一个大循环链表,实现极为巧妙和简单。详见代码。

  1. 1. //双向链表初始化
  2. 2. LITE_OS_SEC_ALW_INLINE STATIC INLINE VOID LOS_ListInit(LOS_DL_LIST *list)
  3. 3. {
  4. 4. list->pstNext = list; // 前后指针都指向自己
  5. 5. list->pstPrev = list;
  6. 6. }
  7. 7.
  8. 8. //链表判空,检查前后指针是否指向自己
  9. 9. LITE_OS_SEC_ALW_INLINE STATIC INLINE BOOL LOS_ListEmpty(LOS_DL_LIST *list)
  10. 10.{
  11. 11. return (BOOL)(list->pstNext == list);
  12. 12.}
  13. 13.
  14. 14.//从链表中删除节点
  15. 15.LITE_OS_SEC_ALW_INLINE STATIC INLINE VOID LOS_ListDelete(LOS_DL_LIST *node)
  16. 16.{
  17. 17. node->pstNext->pstPrev = node->pstPrev;
  18. 18. node->pstPrev->pstNext = node->pstNext;
  19. 19. node->pstNext = NULL;
  20. 20. node->pstPrev = NULL;
  21. 21.}
  22. 22.
  23. 23.//指针互换,具体向双向循环链表中插入节点
  24. 24.LITE_OS_SEC_ALW_INLINE STATIC INLINE VOID LOS_ListAdd(LOS_DL_LIST *list, LOS_DL_LIST *node)
  25. 25.{
  26. 26. node->pstNext = list->pstNext;
  27. 27. node->pstPrev = list;
  28. 28. list->pstNext->pstPrev = node;
  29. 29. list->pstNext = node;
  30. 30.}
  31. 31.
  32. 32.// 两个循环链表合成一个大循环列表 33.LITE_OS_SEC_ALW_INLINE STATIC INLINE VOID LOS_ListAddList(LOS_DL_LIST *oldList, LOS_DL_LIST *newList)
  33. 34.{
  34. 35. // 先用临时指针记录头尾位置
  35. 36. LOS_DL_LIST *oldListHead = oldList->pstNext;
  36. 37. LOS_DL_LIST *oldListTail = oldList;
  37. 38. LOS_DL_LIST *newListHead = newList;
  38. 39. LOS_DL_LIST *newListTail = newList->pstPrev;
  39. 40. // 前后指针完成切换
  40. 41. oldListTail->pstNext = newListHead;
  41. 42. newListHead->pstPrev = oldListTail;
  42. 43. oldListHead->pstPrev = newListTail;
  43. 44. newListTail->pstNext = oldListHead;
  44. 45.}
  45. 46.// 这里与其说插入不如说合并,同样支持从头或尾部合并
  46. 47.LITE_OS_SEC_ALW_INLINE STATIC INLINE VOID LOS_ListTailInsertList(LOS_DL_LIST *oldList, LOS_DL_LIST *newList)
  47. 48.{
  48. 49. LOS_ListAddList(oldList->pstPrev, newList);
  49. 50.}
  50. 51.LITE_OS_SEC_ALW_INLINE STATIC INLINE VOID LOS_ListHeadInsertList(LOS_DL_LIST *oldList, LOS_DL_LIST *newList)
  51. 52.{
  52. 53. LOS_ListAddList(oldList, newList);
  53. 54.}
  54. 55.
复制代码

大家在阅读鸿蒙内核源码要时刻带着 LOS_DL_LIST 去理解结构体之间的关联, 构想运行时的场景是怎样的,就能体会到内核代码之精妙。

3、具体的使用场景
看下它其中的一个使用场景吧,体验设计者的奇妙用心,上代码。
  1. 1. typedef struct ProcessCB {
  2. 2. CHAR processName[OS_PCB_NAME_LEN]; /**< Process name */
  3. 3. UINT32 processID; /**< process ID = leader thread ID */
  4. 4. UINT16 processStatus; /**< [15:4] process Status; [3:0] The number of threads currently
  5. 5. running in the process */
  6. 6. LOS_DL_LIST pendList; /**< Block list to which the process belongs */
  7. 7. LOS_DL_LIST childrenList; /**< my children process list */
  8. 8. LOS_DL_LIST exitChildList; /**< my exit children process list */
  9. 9. LOS_DL_LIST siblingList; /**< linkage in my parent's children list */
  10. 10. ProcessGroup *group; /**< Process group to which a process belongs */
  11. 11. LOS_DL_LIST subordinateGroupList; /**< linkage in my group list */
  12. 12. UINT32 threadGroupID; /**< Which thread group , is the main thread ID of the process */
  13. 13. UINT32 threadScheduleMap; /**< The scheduling bitmap table for the thread group of the
  14. 14. process */
  15. 15. LOS_DL_LIST threadSiblingList; /**< List of threads under this process */
  16. 16. LOS_DL_LIST threadPriQueueList[OS_PRIORITY_QUEUE_NUM]; /**< The process's thread group schedules the
  17. 17.
  18. 18. LOS_DL_LIST waitList; /**< The process holds the waitLits to support
  19. 19.} LosProcessCB;
  20. 20.
复制代码

这是 LosProcessCB(进程控制块),因为结构体很复杂,省去了其他定义,留下LOS_DL_LIST 相关的,LosProcessCB 包含了 七个双向循环链表,这些链表承 载的是一个进程在期生命周期内的运行的过程逻辑,与其他模块的关系逻辑等等, 是的,必须要有如此复杂的又简洁的数据结构才能将进程的实现复杂度降到最低。

内核源码就像那个每次考试都要考 100 分的孩子一样,   不管从宏观架构层面还是微观代码层面都要优秀到极致才能让用户进程安全而稳定的运行。

4、再看调度队列涉及的代码
这是进程/任务 出队入队的操作,背后都是 LOS_DL_LIST 的增删过程,这些宏在内核中大规模的使用,请用心体会这些宏。

5、内联函数 inline
鸿蒙内核大量的使用了内联函数,源码中只有 los_list.h,木有.c 文件!这些调用最最频繁的内联函数,免去了像普通函数要出栈入栈的时间和空间,效率极高。

  1. 1. #define OS_PROCESS_PRI_QUEUE_SIZE(processCB) OsPriQueueProcessSize(g_priQueueList, (processCB)->priority)
  2. 2.
  3. 3. #define OS_TASK_PRI_QUEUE_ENQUEUE(processCB, taskCB)  
  4. 4. OsPriQueueEnqueue((processCB)->threadPriQueueList, &((processCB)->threadScheduleMap),  
  5. 5. &((taskCB)->pendList), (taskCB)->priority)
  6. 6. #define OS_TASK_PRI_QUEUE_ENQUEUE_HEAD(processCB, taskCB)  
  7. 7. OsPriQueueEnqueueHead((processCB)->threadPriQueueList, &((processCB)->threadScheduleMap),  
  8. 8. &((taskCB)->pendList), (taskCB)->priority)
  9. 9.
  10. 10.#define OS_TASK_PRI_QUEUE_DEQUEUE(processCB, taskCB)  
  11. 11. OsPriQueueDequeue((processCB)->threadPriQueueList, &((processCB)->threadScheduleMap), &((taskCB)->pendList))
  12. 12.
  13. 13.
  14. 14.#define OS_TASK_SCHED_QUEUE_ENQUEUE(taskCB, status) OsTaskSchedQueueEnqueue(taskCB, status)
  15. 15.#define OS_TASK_SCHED_QUEUE_DEQUEUE(taskCB, status) OsTaskSchedQueueDequeue(taskCB, status)
  16. 16.
  17. 17.#define OS_PROCESS_PRI_QUEUE_ENQUEUE(processCB)  
  18. 18. OsPriQueueEnqueue(g_priQueueList, &g_priQueueBitmap, &((processCB)->pendList), (processCB)->priority)
  19. 19.#define OS_PROCESS_PRI_QUEUE_ENQUEUE_HEAD(processCB)
  20. 20. OsPriQueueEnqueueHead(g_priQueueList, &g_priQueueBitmap, &((processCB)->pendList), (processCB)->priority)
  21. 21.#define OS_PROCESS_PRI_QUEUE_DEQUEUE(processCB) OsPriQueueProcessDequeue(&((processCB)->pendList))
  22. 22.
  23. 23.#define OS_TASK_PRI_QUEUE_SIZE(processCB, taskCB) OsPriQueueSize((processCB)->threadPriQueueList, (taskCB)->priority)
  24. 24.#define OS_TASK_GET_NEW(processCB) LOS_DL_LIST_ENTRY(OsPriQueueTop((processCB)->threadPriQueueList,  
  25. 25.&((processCB)->threadScheduleMap)),  
  26. 26. LosTaskCB, pendList)
复制代码

LOS_DL_LIST 让内核结构变得简单,高效,是最重要的结构体。

  1. 1. /* Define OS code data sections */
  2. 2. /* The indicator function is inline */
  3. 3. #ifndef LITE_OS_SEC_ALW_INLINE
  4. 4. #define LITE_OS_SEC_ALW_INLINE /* __attribute__((always_inline)) */
  5. 5. #endif
  6. 6.
  7. 7. LITE_OS_SEC_ALW_INLINE STATIC INLINE VOID LOS_ListAdd(LOS_DL_LIST *list, LOS_DL_LIST *node)
  8. 8. {
  9. 9. node->pstNext = list->pstNext;
  10. 10. node->pstPrev = list;
  11. 11. list->pstNext->pstPrev = node;
  12. 12. list->pstNext = node;
  13. 13.}
  14. 14.LITE_OS_SEC_ALW_INLINE STATIC INLINE VOID LOS_ListTailInsert(LOS_DL_LIST *list, LOS_DL_LIST *node)
  15. 15.{
  16. 16. LOS_ListAdd(list->pstPrev, node);
  17. 17.}
复制代码

——————

原创:老王丨【个人微信:spoto777;公众号:鸿蒙开发者老王】华为认证讲师 / 腾讯认证讲师 / 鸿蒙开发先行者,欢迎大家技术交流咨询

回帖

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