為什么學(xué)個(gè)東西要學(xué)那么多的概念?
鴻蒙的內(nèi)核中 Task和 線程在廣義上可以理解為是一個(gè)東西,但狹義上肯定會(huì)有區(qū)別,區(qū)別在于管理體系的不同,Task是調(diào)度層面的概念,線程是進(jìn)程層面概念。比如 main()函數(shù)中首個(gè)函數(shù) OsSetMainTask();就是設(shè)置啟動(dòng)任務(wù),但此時(shí)啥都還沒開始呢,Kprocess進(jìn)程都沒創(chuàng)建,怎么會(huì)有大家一般意義上所理解的線程呢。狹義上的后續(xù)有鴻蒙內(nèi)核源碼分析(啟動(dòng)過程篇)來說明。不知道大家有沒有這種體會(huì),學(xué)一個(gè)東西的過程中要接觸很多新概念,尤其像 Java/android 的生態(tài),概念賊多,很多同學(xué)都被繞在概念中出不來,痛苦不堪。那問題是為什么需要這么多的概念呢?
舉個(gè)例子就明白了:
假如您去深圳參加一個(gè)面試?yán)习鍐柲隳睦锶??你?huì)說是江西人,湖南人...而不會(huì)說是張家村二組的張全蛋,這樣還誰敢要你。但如果你參加同鄉(xiāng)會(huì)別人問你同樣問題,你不會(huì)說是來自東北那旮沓的,卻反而要說張家村二組的張全蛋。明白了嗎?張全蛋還是那個(gè)張全蛋,但因?yàn)閳鼍白兞?,您的說法就得必須跟著變,否則沒法愉快的聊天。程序設(shè)計(jì)就是源于生活,歸于生活,大家對(duì)程序的理解就是要用生活中的場景去打比方,更好的理解概念。
那在內(nèi)核的調(diào)度層面,咱們只說task,task是內(nèi)核調(diào)度的單元,調(diào)度就是圍著它轉(zhuǎn)。
進(jìn)程和線程的狀態(tài)遷移圖
先看看task從哪些渠道產(chǎn)生:
渠道很多,可能是shell的一個(gè)命令,也可能由內(nèi)核創(chuàng)建,更多的是大家編寫應(yīng)用程序new出來的一個(gè)線程。
調(diào)度的內(nèi)容task已經(jīng)有了,那他們是如何被有序調(diào)度的呢?答案:是32個(gè)進(jìn)程和線程就緒隊(duì)列,各32個(gè)哈,為什么是32個(gè),鴻蒙系統(tǒng)源碼分析(總目錄) 文章里有詳細(xì)說明,自行去翻。這張進(jìn)程狀態(tài)遷移示意圖一定要看明白.
注意:進(jìn)程和線程的隊(duì)列內(nèi)的內(nèi)容只針對(duì)就緒狀態(tài),其他狀態(tài)內(nèi)核并沒有用隊(duì)列去描述它,(線程的阻塞狀態(tài)用的是pendlist鏈表),因?yàn)榫途w就意味著工作都準(zhǔn)備好了就等著被調(diào)度到CPU來執(zhí)行了。所以理解就緒隊(duì)列很關(guān)鍵,有三種情況會(huì)加入就緒隊(duì)列。
Init→Ready:
進(jìn)程創(chuàng)建或fork時(shí),拿到該進(jìn)程控制塊后進(jìn)入Init狀態(tài),處于進(jìn)程初始化階段,當(dāng)進(jìn)程初始化完成將進(jìn)程插入調(diào)度隊(duì)列,此時(shí)進(jìn)程進(jìn)入就緒狀態(tài)。
Pend→Ready / Pend→Running:
阻塞進(jìn)程內(nèi)的任意線程恢復(fù)就緒態(tài)時(shí),進(jìn)程被加入到就緒隊(duì)列,同步轉(zhuǎn)為就緒態(tài),若此時(shí)發(fā)生進(jìn)程切換,則進(jìn)程狀態(tài)由就緒態(tài)轉(zhuǎn)為運(yùn)行態(tài)。
Running→Ready:
進(jìn)程由運(yùn)行態(tài)轉(zhuǎn)為就緒態(tài)的情況有以下兩種:
有更高優(yōu)先級(jí)的進(jìn)程創(chuàng)建或者恢復(fù)后,會(huì)發(fā)生進(jìn)程調(diào)度,此刻就緒列表中最高優(yōu)先級(jí)進(jìn)程變?yōu)檫\(yùn)行態(tài),那么原先運(yùn)行的進(jìn)程由運(yùn)行態(tài)變?yōu)榫途w態(tài)。
若進(jìn)程的調(diào)度策略為SCHED_RR,且存在同一優(yōu)先級(jí)的另一個(gè)進(jìn)程處于就緒態(tài),則該進(jìn)程的時(shí)間片消耗光之后,該進(jìn)程由運(yùn)行態(tài)轉(zhuǎn)為就緒態(tài),另一個(gè)同優(yōu)先級(jí)的進(jìn)程由就緒態(tài)轉(zhuǎn)為運(yùn)行態(tài)。
誰來觸發(fā)調(diào)度工作?
就緒隊(duì)列讓task各就各位,在其生命周期內(nèi)不停的進(jìn)行狀態(tài)流轉(zhuǎn),調(diào)度是讓task交給CPU處理,那又是什么讓調(diào)度去工作的呢?它是如何被觸發(fā)的?
筆者能想到的觸發(fā)方式是以下四個(gè):
Tick(時(shí)鐘管理),類似于JAVA的定時(shí)任務(wù),時(shí)間到了就觸發(fā)。系統(tǒng)定時(shí)器是內(nèi)核時(shí)間機(jī)制中最重要的一部分,它提供了一種周期性觸發(fā)中斷機(jī)制,即系統(tǒng)定時(shí)器以HZ(時(shí)鐘節(jié)拍率)為頻率自行觸發(fā)時(shí)鐘中斷。當(dāng)時(shí)鐘中斷發(fā)生時(shí),內(nèi)核就通過時(shí)鐘中斷處理程序OsTickHandler對(duì)其進(jìn)行處理。鴻蒙內(nèi)核默認(rèn)是10ms觸發(fā)一次,執(zhí)行以下中斷函數(shù):
/* * Description : Tick interruption handler */ LITE_OS_SEC_TEXT VOID OsTickHandler(VOID) { UINT32 intSave; TICK_LOCK(intSave); g_tickCount[ArchCurrCpuid()]++; TICK_UNLOCK(intSave); #ifdef LOSCFG_KERNEL_VDSO OsUpdateVdsoTimeval(); #endif #ifdef LOSCFG_KERNEL_TICKLESS OsTickIrqFlagSet(OsTicklessFlagGet()); #endif #if (LOSCFG_BASE_CORE_TICK_HW_TIME == YES) HalClockIrqClear(); /* diff from every platform */ #endif OsTimesliceCheck();//時(shí)間片檢查 OsTaskScan(); /* task timeout scan *///任務(wù)掃描,發(fā)起調(diào)度 #if (LOSCFG_BASE_CORE_SWTMR == YES) OsSwtmrScan();//軟時(shí)鐘掃描檢查 #endif }
里面對(duì)任務(wù)進(jìn)行了掃描,時(shí)間片到了或就緒隊(duì)列有高或同級(jí)task,會(huì)執(zhí)行調(diào)度。
第二個(gè)是各種軟硬中斷,如何USB插拔,鍵盤,鼠標(biāo)這些外設(shè)引起的中斷,需要去執(zhí)行中斷處理函數(shù)。
第三個(gè)是程序主動(dòng)中斷,比如運(yùn)行過程中需要申請(qǐng)其他資源,而主動(dòng)讓出控制權(quán),重新調(diào)度。
最后一個(gè)是創(chuàng)建一個(gè)新進(jìn)程或新任務(wù)后主動(dòng)發(fā)起的搶占式調(diào)度,新進(jìn)程會(huì)默認(rèn)創(chuàng)建一個(gè)main task, task的首條指令(入口函數(shù))就是我們上層程序的main函數(shù),它被放在代碼段的第一的位置。
哪些地方會(huì)申請(qǐng)調(diào)度?看一張圖。
這里提下圖中的OsCopyProcess(),這是fork進(jìn)程的主體函數(shù),可以看出fork之后立即申請(qǐng)了一次調(diào)度。
LITE_OS_SEC_TEXT INT32 LOS_Fork(UINT32 flags, const CHAR *name, const TSK_ENTRY_FUNC entry, UINT32 stackSize) { UINT32 cloneFlag = CLONE_PARENT | CLONE_THREAD | CLONE_VFORK | CLONE_FILES; if (flags & (~cloneFlag)) { PRINT_WARN("Clone dont support some flags!\n"); } flags |= CLONE_FILES; return OsCopyProcess(cloneFlag & flags, name, (UINTPTR)entry, stackSize); } STATIC INT32 OsCopyProcess(UINT32 flags, const CHAR *name, UINTPTR sp, UINT32 size) { UINT32 intSave, ret, processID; LosProcessCB *run = OsCurrProcessGet(); LosProcessCB *child = OsGetFreePCB(); if (child == NULL) { return -LOS_EAGAIN; } processID = child->processID; ret = OsForkInitPCB(flags, child, name, sp, size); if (ret != LOS_OK) { goto ERROR_INIT; } ret = OsCopyProcessResources(flags, child, run); if (ret != LOS_OK) { goto ERROR_TASK; } ret = OsChildSetProcessGroupAndSched(child, run); if (ret != LOS_OK) { goto ERROR_TASK; } LOS_MpSchedule(OS_MP_CPU_ALL); if (OS_SCHEDULER_ACTIVE) { LOS_Schedule();// 申請(qǐng)調(diào)度 } return processID; ERROR_TASK: SCHEDULER_LOCK(intSave); (VOID)OsTaskDeleteUnsafe(OS_TCB_FROM_TID(child->threadGroupID), OS_PRO_EXIT_OK, intSave); ERROR_INIT: OsDeInitPCB(child); return -ret; }
原來創(chuàng)建一個(gè)進(jìn)程這么簡單,真的就是在COPY!
源碼告訴你調(diào)度過程是怎樣的
以上是需要提前了解的信息,接下來直接上源碼看調(diào)度過程吧,文件就三個(gè)函數(shù),主要就是這個(gè)了:
VOID OsSchedResched(VOID) { LOS_ASSERT(LOS_SpinHeld(&g_taskSpin));//調(diào)度過程要上鎖 newTask = OsGetTopTask(); //獲取最高優(yōu)先級(jí)任務(wù) OsSchedSwitchProcess(runProcess, newProcess);//切換進(jìn)程 (VOID)OsTaskSwitchCheck(runTask, newTask);//任務(wù)檢查 OsCurrTaskSet((VOID*)newTask);//*設(shè)置當(dāng)前任務(wù) if (OsProcessIsUserMode(newProcess)) {//判斷是否為用戶態(tài),使用用戶空間 OsCurrUserTaskSet(newTask->userArea);//設(shè)置任務(wù)空間 } /* do the task context switch */ OsTaskSchedule(newTask, runTask); //切換CPU任務(wù)上下文,匯編代碼實(shí)現(xiàn) }
函數(shù)有點(diǎn)長,筆者留了最重要的幾行,看這幾行就夠了,流程如下:
調(diào)度過程要自旋鎖,多核情況下只能被一個(gè)CPU core執(zhí)行. 不允許任何中斷發(fā)生, 沒錯(cuò),說的是任何事是不能去打斷它,否則后果太嚴(yán)重了,這可是內(nèi)核在切換進(jìn)程和線程的操作啊。
在就緒隊(duì)列里找個(gè)最高優(yōu)先級(jí)的task
切換進(jìn)程,就是task歸屬的那個(gè)進(jìn)程設(shè)為運(yùn)行進(jìn)程,這里要注意,老的task和老進(jìn)程只是讓出了CPU指令執(zhí)行權(quán),其他都還在內(nèi)存,資源也都沒有釋放.
設(shè)置新任務(wù)為當(dāng)前任務(wù)
用戶模式下需要設(shè)置task運(yùn)行空間,因?yàn)槊總€(gè)task棧是不一樣的.空間部分具體在系列篇內(nèi)存中查看
是最重要的,切換任務(wù)上下文,參數(shù)是新老兩個(gè)任務(wù),一個(gè)要保存現(xiàn)場,一個(gè)要恢復(fù)現(xiàn)場。
什么是任務(wù)上下文?鴻蒙內(nèi)核源碼分析(總目錄)任務(wù)切換篇已有詳細(xì)的描述,請(qǐng)自行翻看.
請(qǐng)讀懂OsGetTopTask()
讀懂OsGetTopTask(),就明白了就緒隊(duì)列是怎么回事了。這里提下goto語句,幾乎所有內(nèi)核代碼都會(huì)大量的使用goto語句,鴻蒙內(nèi)核有617個(gè)goto遠(yuǎn)大于264個(gè)break,還有人說要廢掉goto,你知道內(nèi)核開發(fā)者青睞goto的真正原因嗎?
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; } #ifdef __cplusplus #if __cplusplus }
編輯:hfy
-
cpu
+關(guān)注
關(guān)注
68文章
10911瀏覽量
213147 -
線程
+關(guān)注
關(guān)注
0文章
507瀏覽量
19763 -
進(jìn)程
+關(guān)注
關(guān)注
0文章
204瀏覽量
14003
發(fā)布評(píng)論請(qǐng)先 登錄
相關(guān)推薦
鴻蒙內(nèi)核源碼Task/線程技術(shù)分析
![<b class='flag-5'>鴻蒙</b><b class='flag-5'>內(nèi)核</b><b class='flag-5'>源碼</b>Task/線程技術(shù)分析](https://file.elecfans.com/web1/M00/CA/33/o4YBAF-Lqy6AVRPzAAA48147isQ437.png)
【HarmonyOS】鴻蒙內(nèi)核源碼分析(調(diào)度機(jī)制篇)
鴻蒙源碼分析系列(總目錄) | 給HarmonyOS源碼逐行加上中文注釋
鴻蒙內(nèi)核源碼分析(調(diào)度機(jī)制篇):Task是如何被調(diào)度執(zhí)行的
鴻蒙內(nèi)核源碼分析(調(diào)度隊(duì)列篇):進(jìn)程和Task的就緒隊(duì)列對(duì)調(diào)度的作用
鴻蒙內(nèi)核源碼分析(Task管理篇):task是內(nèi)核調(diào)度的單元
鴻蒙內(nèi)核源碼分析(Task管理篇):task是內(nèi)核調(diào)度的單元
鴻蒙內(nèi)核源碼分析(調(diào)度故事篇)
淺談鴻蒙內(nèi)核代碼調(diào)度隊(duì)列
鴻蒙內(nèi)核源碼分析:鴻蒙內(nèi)核的每段匯編代碼解析
![<b class='flag-5'>鴻蒙</b><b class='flag-5'>內(nèi)核</b><b class='flag-5'>源碼</b>分析:<b class='flag-5'>鴻蒙</b><b class='flag-5'>內(nèi)核</b>的每段匯編代碼解析](https://file.elecfans.com/web1/M00/E2/8D/o4YBAGA9mhqAITVgAAKa3KWMsqU730.png)
鴻蒙內(nèi)核源碼分析:task是內(nèi)核調(diào)度的單元
![<b class='flag-5'>鴻蒙</b><b class='flag-5'>內(nèi)核</b><b class='flag-5'>源碼</b>分析:task是<b class='flag-5'>內(nèi)核</b><b class='flag-5'>調(diào)度</b>的單元](https://file.elecfans.com/web1/M00/D0/C8/pIYBAF-7Z_eAKw9iAABqmsljMWw560.png)
鴻蒙內(nèi)核源碼分析:進(jìn)程和Task的就緒隊(duì)列對(duì)調(diào)度的作用
![<b class='flag-5'>鴻蒙</b><b class='flag-5'>內(nèi)核</b><b class='flag-5'>源碼</b>分析:進(jìn)程和Task的就緒隊(duì)列對(duì)<b class='flag-5'>調(diào)度</b>的作用](https://file.elecfans.com/web1/M00/D0/C8/pIYBAF-7ZyWAcOUoAABRK-7Fsdk048.png)
鴻蒙內(nèi)核源碼分析:時(shí)鐘是觸發(fā)調(diào)度最大的源動(dòng)力
![<b class='flag-5'>鴻蒙</b><b class='flag-5'>內(nèi)核</b><b class='flag-5'>源碼</b>分析:時(shí)鐘是<b class='flag-5'>觸發(fā)</b><b class='flag-5'>調(diào)度</b>最大的源動(dòng)力](https://file.elecfans.com/web1/M00/D0/EE/pIYBAF-81VWAeXlLAABkRZxZzFA734.png)
鴻蒙內(nèi)核源碼分析 :內(nèi)核最重要結(jié)構(gòu)體
![<b class='flag-5'>鴻蒙</b><b class='flag-5'>內(nèi)核</b><b class='flag-5'>源碼</b>分析 :<b class='flag-5'>內(nèi)核</b>最重要結(jié)構(gòu)體](https://file.elecfans.com/web1/M00/D0/79/o4YBAF-81kiAFJ_7AACy6GpuKHw329.png)
評(píng)論