OpenHarmony開發者文檔 | 同步最新鴻蒙官方文檔 | OpenHarmony | HarmonyOS < 國内通路 | 國外通路 >
百篇部落格系列篇.本篇為:
- v01.xx 鴻蒙核心源碼分析(雙向連結清單篇) | 誰是核心最重要結構體 | 51 .c .h .o
誰是鴻蒙核心最重要的結構體?
答案一定是:
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
不是拿來單獨用的,它是寄生在内容結構體上的,誰用它誰就是它的資料.看圖就明白了.
功能接口
鴻蒙系統中的雙向連結清單子產品為使用者提供下面幾個接口。
功能分類 接口名 描述
初始化連結清單 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_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; /**< 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)))
或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();
#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
-
孩子連結清單,所有由它fork出來的程序都挂到這個連結清單上.上面的孩子程序在死亡前會将自己從上面摘出去,轉而挂到childrenList
連結清單上.exitChildList
-
退出孩子連結清單,進入死亡程式的程序要挂到這個連結清單上,一個程序的死亡是件挺麻煩的事,程序池的數量有限,需要及時回收程序資源,但家族管理關系複雜,要去很多地方消除痕迹.尤其還有其他程序在看你笑話,等你死亡(exitChildList
/wait
)了通知它們一聲.waitpid
-
兄弟連結清單,和你同一個父親的程序都挂到了這個連結清單上.siblingList
-
朋友圈連結清單,裡面是因為興趣愛好(程序組)而挂在一起的程序,它們可以不是一個父親,不是一個祖父,但一定是同一個老祖宗(使用者态和核心态根程序).subordinateGroupList
-
線程連結清單,上面挂的是程序ID都是這個程序的線程(任務),程序和線程的關系是1:N的關系,一個線程隻能屬于一個程序.這裡要注意任務在其生命周期中是不能改所屬程序的.threadSiblingList
-
線程的排程隊列數組,一共32個,任務和程序一樣有32個優先級,排程算法的過程是先找到優先級最高的程序,在從該程序的任務隊列裡去最高的優先級任務運作.threadPriQueueList
-
是等待子程序消亡的任務連結清單,注意上面挂的是任務.任務是通過系統調用waitList
将任務挂到pid_t wait(int *status); pid_t waitpid(pid_t pid, int *status, int options);
上.鴻蒙waitList
系統調用為waitpid
,具體看程序回收篇.SysWait
雙向連結清單是核心最重要的結構體,精讀核心的路上它會反複的映入你的眼簾,了解它是了解核心運作的關鍵所在!
百篇部落格.往期回顧
在加注過程中,整理出以下文章.内容立足源碼,常以生活場景打比方盡可能多的将核心知識點置入某種場景,具有畫面感,容易了解記憶.
說别人能聽得懂的話很重要! 百篇部落格絕不是百度教條式的在說一堆诘屈聱牙的概念,那沒什麼意思.更希望讓核心變得栩栩如生,倍感親切.确實有難度,自不量力,但已經出發,回頭已是不可能的了.😛
與寫代碼有bug需不斷debug一樣,文章和注解内容會将錯漏之處反複修正,持續更新,
代表修改的次數,精雕細琢,言簡意赅,力求打造精品内容.
.xx
- v52.xx 鴻蒙核心源碼分析(靜态站點篇) | 五一哪也沒去就幹了這事 | 51 .c .h .o
- v51.xx 鴻蒙核心源碼分析(ELF格式篇) | 應用程式入口并不是main | 51 .c .h .o
- v50.xx 鴻蒙核心源碼分析(編譯環境篇) | 編譯鴻蒙看這篇或許真的夠了 | 51 .c .h .o
- v49.xx 鴻蒙核心源碼分析(信号消費篇) | 誰讓CPU連續四次換棧運作 | 51 .c .h .o
- v48.xx 鴻蒙核心源碼分析(信号生産篇) | 年過半百,依然活力十足 | 51 .c .h .o
- v47.xx 鴻蒙核心源碼分析(程序回收篇) | 臨終前如何向老祖宗托孤 | 51 .c .h .o
- v46.xx 鴻蒙核心源碼分析(特殊程序篇) | 龍生龍鳳生鳳老鼠生兒會打洞 | 51 .c .h .o
- v45.xx 鴻蒙核心源碼分析(Fork篇) | 一次調用,兩次傳回 | 51 .c .h .o
- v44.xx 鴻蒙核心源碼分析(中斷管理篇) | 江湖從此不再怕中斷 | 51 .c .h .o
- v43.xx 鴻蒙核心源碼分析(中斷概念篇) | 海公公的日常工作 | 51 .c .h .o
- v42.xx 鴻蒙核心源碼分析(中斷切換篇) | 系統因中斷活力四射 | 51 .c .h .o
- v41.xx 鴻蒙核心源碼分析(任務切換篇) | 看彙編如何切換任務 | 51 .c .h .o
- v40.xx 鴻蒙核心源碼分析(彙編彙總篇) | 彙編可愛如鄰家女孩 | 51 .c .h .o
- v39.xx 鴻蒙核心源碼分析(異常接管篇) | 社會很單純,複雜的是人 | 51 .c .h .o
- v38.xx 鴻蒙核心源碼分析(寄存器篇) | 小強乃宇宙最忙存儲器 | 51 .c .h .o
- v37.xx 鴻蒙核心源碼分析(系統調用篇) | 開發者永遠的口頭禅 | 51 .c .h .o
- v36.xx 鴻蒙核心源碼分析(工作模式篇) | CPU是韋小寶,七個老婆 | 51 .c .h .o
- v35.xx 鴻蒙核心源碼分析(時間管理篇) | 誰是核心基本時間機關 | 51 .c .h .o
- v34.xx 鴻蒙核心源碼分析(原子操作篇) | 誰在為原子操作保駕護航 | 51 .c .h .o
- v33.xx 鴻蒙核心源碼分析(消息隊列篇) | 程序間如何異步傳遞大資料 | 51 .c .h .o
- v32.xx 鴻蒙核心源碼分析(CPU篇) | 整個核心就是一個死循環 | 51 .c .h .o
- v31.xx 鴻蒙核心源碼分析(定時器篇) | 哪個任務的優先級最高 | 51 .c .h .o
- v30.xx 鴻蒙核心源碼分析(事件控制篇) | 任務間多對多的同步方案 | 51 .c .h .o
- v29.xx 鴻蒙核心源碼分析(信号量篇) | 誰在負責解決任務的同步 | 51 .c .h .o
- v28.xx 鴻蒙核心源碼分析(程序通訊篇) | 九種程序間通訊方式速攬 | 51 .c .h .o
- v27.xx 鴻蒙核心源碼分析(互斥鎖篇) | 比自旋鎖豐滿的互斥鎖 | 51 .c .h .o
- v26.xx 鴻蒙核心源碼分析(自旋鎖篇) | 自旋鎖當立貞節牌坊 | 51 .c .h .o
- v25.xx 鴻蒙核心源碼分析(并發并行篇) | 聽過無數遍的兩個概念 | 51 .c .h .o
- v24.xx 鴻蒙核心源碼分析(程序概念篇) | 程序在管理哪些資源 | 51 .c .h .o
- v23.xx 鴻蒙核心源碼分析(彙編傳參篇) | 如何傳遞複雜的參數 | 51 .c .h .o
- v22.xx 鴻蒙核心源碼分析(彙編基礎篇) | CPU在哪裡打卡上班 | 51 .c .h .o
- v21.xx 鴻蒙核心源碼分析(線程概念篇) | 是誰在不斷的折騰CPU | 51 .c .h .o
- v20.xx 鴻蒙核心源碼分析(用棧方式篇) | 程式運作場地由誰提供 | 51 .c .h .o
- v19.xx 鴻蒙核心源碼分析(位圖管理篇) | 誰能一分錢分兩半花 | 51 .c .h .o
- v18.xx 鴻蒙核心源碼分析(源碼結構篇) | 核心每個檔案的含義 | 51 .c .h .o
- v17.xx 鴻蒙核心源碼分析(實體記憶體篇) | 怎麼管理實體記憶體 | 51 .c .h .o
- v16.xx 鴻蒙核心源碼分析(記憶體規則篇) | 記憶體管理到底在管什麼 | 51 .c .h .o
- v15.xx 鴻蒙核心源碼分析(記憶體映射篇) | 虛拟記憶體虛在哪裡 | 51 .c .h .o
- v14.xx 鴻蒙核心源碼分析(記憶體彙編篇) | 誰是虛拟記憶體實作的基礎 | 51 .c .h .o
- v13.xx 鴻蒙核心源碼分析(源碼注釋篇) | 鴻蒙必定成功,也必然成功 | 51 .c .h .o
- v12.xx 鴻蒙核心源碼分析(記憶體管理篇) | 虛拟記憶體全景圖是怎樣的 | 51 .c .h .o
- v11.xx 鴻蒙核心源碼分析(記憶體配置設定篇) | 記憶體有哪些配置設定方式 | 51 .c .h .o
- v10.xx 鴻蒙核心源碼分析(記憶體主奴篇) | 皇上和奴才如何相處 | 51 .c .h .o
- v09.xx 鴻蒙核心源碼分析(排程故事篇) | 用故事說核心排程過程 | 51 .c .h .o
- v08.xx 鴻蒙核心源碼分析(總目錄) | 百萬漢字注解 百篇部落格分析 | 51 .c .h .o
- v07.xx 鴻蒙核心源碼分析(排程機制篇) | 任務是如何被排程執行的 | 51 .c .h .o
- v06.xx 鴻蒙核心源碼分析(排程隊列篇) | 核心有多少個排程隊列 | 51 .c .h .o
- v05.xx 鴻蒙核心源碼分析(任務管理篇) | 任務池是如何管理的 | 51 .c .h .o
- v04.xx 鴻蒙核心源碼分析(任務排程篇) | 任務是核心排程的單元 | 51 .c .h .o
- v03.xx 鴻蒙核心源碼分析(時鐘任務篇) | 觸發排程誰的貢獻最大 | 51 .c .h .o
- v02.xx 鴻蒙核心源碼分析(程序管理篇) | 誰在管理核心資源 | 51 .c .h .o
- v01.xx 鴻蒙核心源碼分析(雙向連結清單篇) | 誰是核心最重要結構體 | 51 .c .h .o
關于 51 .c .h .o
看系列篇文章會常看到
51 .c .h .o
,希望這對大家閱讀不會造成影響.
分别對應以下四個站點的首個字元,感謝這些站點一直以來對系列篇的支援和推薦,尤其是 oschina gitee ,很喜歡它的界面風格,簡潔大方,讓人感覺到開源的偉大!
- 51cto
- csdn
- harmony
- oschina
而巧合的是
.c .h .o
是C語言的頭/源/目标檔案,這就很有意思了,冥冥之中似有天數,将這四個寶貝以這種方式融合在一起.
51 .c .h .o
, 我要CHO ,嗯嗯,hin 順口 : )
百萬漢字注解.百篇部落格分析
百萬漢字注解 >> 精讀鴻蒙源碼,中文注解分析, 深挖地基工程,大腦永久記憶,四大碼倉每日同步更新< gitee | github | csdn | coding >
百篇部落格分析 >> 故事說核心,問答式導讀,生活式比喻,表格化說明,圖形化展示,主流站點定期更新中< 51cto | csdn | harmony | osc >
關注不迷路.代碼即人生
熱愛是所有的理由和答案 - turing
原創不易,歡迎轉載,但麻煩請注明出處.