誰是鴻蒙內核最重要的結構體?
答案一定是: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否則中文有亂碼.
可以豪不夸張的說理解LOS_DL_LIST及相關函數是讀懂鴻蒙內核的關鍵。前后指針(注者后續將比喻成一對左右觸手)靈活的指揮著系統精準的運行,越是深入分析內核源碼,越能感受到內核開發者對LOS_DL_LIST非凡的駕馭能力,筆者仿佛看到了無數雙手前后相連,拉起了一個個雙向循環鏈表,把指針的高效能運用到了極致,這也許就是編程的藝術吧!這么重要的結構體還是需詳細講解一下.
基本概念
雙向鏈表是指含有往前和往后兩個方向的鏈表,即每個結點中除存放下一個節點指針外,還增加一個指向其前一個節點的指針。其頭指針head是唯一確定的。從雙向鏈表中的任意一個結點開始,都可以很方便地訪問它的前驅結點和后繼結點,這種數據結構形式使得雙向鏈表在查找時更加方便,特別是大量數據的遍歷。由于雙向鏈表具有對稱性,能方便地完成各種插入、刪除等操作,但需要注意前后方向的操作。
有好幾個同學問數據在哪? 確實LOS_DL_LIST這個結構看起來怪怪的,它竟沒有數據域!所以看到這個結構的人第一反應就是我們怎么訪問數據?其實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; }
強大的宏
除了內聯函數,對雙向遍歷的初始化,定位,遍歷 等等操作提供了更強大的宏支持.使內核以極其簡潔高效的代碼實現復雜邏輯的處理.
//定義一個節點并初始化為雙向鏈表節點 #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_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,具體看進程回收篇.
雙向鏈表是內核最重要的結構體,精讀內核的路上它會反復的映入你的眼簾,理解它是理解內核運作的關鍵所在!
編輯:hfy
-
內核
+關注
關注
3文章
1372瀏覽量
40281 -
鴻蒙系統
+關注
關注
183文章
2634瀏覽量
66308
發布評論請先 登錄
相關推薦
評論