任務是操作系統一個重要的概念,是競爭系統資源的最小運行單元。任務可以使用或等待CPU
、使用內存空間等系統資源,并獨立于其它任務運行。鴻蒙輕內核的任務模塊可以給用戶提供多個任務,實現任務間的切換,幫助用戶管理業務程序流程。本文我們來一起學習下任務模塊的源代碼,所涉及的源碼,以OpenHarmony LiteOS-M
內核為例,均可以在開源站點 https://gitee.com/openharmony/kernel_liteos_m 獲取。
接下來,我們看下任務模塊的結構體,任務初始化,任務常用操作的源代碼。
1、任務模塊的結構體定義
在文件kernel\include\los_task.h
定義的任務控制塊結構體LosTaskCB
,源代碼如下,結構體成員的解釋見注釋部分。
typedef struct {VOID *stackPointer; /* 任務棧指針 */UINT16 taskStatus; /* 任務狀態 */UINT16 priority; /* 任務優先級 */INT32 timeSlice; /* 剩余的時間片 */UINT32 waitTimes;SortLinkList sortList; /* 任務超時排序鏈表節點 */UINT64 startTime;UINT32 stackSize; /* 任務棧大小 */UINT32 topOfStack; /* 棧頂指針 */UINT32 taskID; /* 任務編號Id */TSK_ENTRY_FUNC taskEntry; /* 任務入口函數 */VOID *taskSem; /* 任務持有的信號量 */VOID *taskMux; /* 導致任務阻塞的互斥鎖 */UINT32 arg; /* 任務入口函數的參數 */CHAR *taskName; /* 任務名稱 */LOS_DL_LIST pendList; /* 就緒隊列等鏈表節點 */LOS_DL_LIST timerList; /* 任務超時排序鏈表節點 */EVENT_CB_S event;UINT32 eventMask; /* 事件掩碼 */UINT32 eventMode; /* 事件模式 */VOID *msg; /* 分給給隊列的內存*/INT32 errorNo;
} LosTaskCB;
另外一個比較重要的結構體是TSK_INIT_PARAM_S
,創建任務時,需要指定任務初始化的參數。源代碼如下,結構體成員的解釋見注釋部分。
typedef struct tagTskInitParam {TSK_ENTRY_FUNC pfnTaskEntry; /** 任務入口函數 */UINT16 usTaskPrio; /** 任務參數 */UINT32 uwStackSize; /** 任務棧大小 */CHAR *pcName; /** 任務名稱 */UINT32 uwResved; /** 保留 */
} TSK_INIT_PARAM_S;
2、任務模塊初始化
在系統啟動時,在kernel\src\los_init.c
中調用OsTaskInit()
進行任務模塊初始化,還會調用OsIdleTaskCreate()
創建空閑任務。
2.1 任務模塊初始化
函數OsTaskInit()
定義在kernel\src\los_task.c
,我們分析下這個函數的執行過程。
⑴處代碼根據開發板配置的最大任務數g_taskMaxNum
,計算需要申請的內存大小size
,為任務控制塊TCB
數組(也叫作任務池)g_taskCBArray
申請內存。為什么比最大任務數多申請一個呢?在刪除任務時會使用。下文分析刪除任務的源碼時再詳細講解其用意。⑵處代碼初始化雙向鏈表g_losFreeTask
用作空閑的任務鏈表、g_taskRecyleList
可以回收的任務鏈表。⑶處循環初始化每一個任務,任務狀態未使用OS_TASK_STATUS_UNUSED
,初始化任務Id
,并把任務掛在空閑任務鏈表上。
⑷處初始化全局變量LosTask g_losTask
,該全局變量維護當前運行的任務和要調度執行的任務。初始化任務池時,設置當前運行的任務為g_taskCBArray[g_taskMaxNum]
。⑸處空閑任務編號暫時設置為無效值,后續創建空閑任務時再設置空閑任務編號。
優先級隊列,詳細的代碼實現剖析,參見之前的源碼剖析文章。⑸處互斥鎖死鎖檢測的調測特性的,后續系列文章專題進行講解。⑹處代碼初始化排序鏈表,詳細的代碼實現剖析,參見之前的源碼剖析文章。⑺處如果開啟了惰性棧,計算TCB
的成員變量stackFrame
在其結構體中的偏移量g_stackFrameOffLenInTcb
。
LITE_OS_SEC_TEXT_INIT UINT32 OsTaskInit(VOID)
{UINT32 size;UINT32 index;⑴ size = (g_taskMaxNum + 1) * sizeof(LosTaskCB);g_taskCBArray = (LosTaskCB *)LOS_MemAlloc(m_aucSysMem0, size);if (g_taskCBArray == NULL) {return LOS_ERRNO_TSK_NO_MEMORY;}(VOID)memset_s(g_taskCBArray, size, 0, size);⑵ LOS_ListInit(&g_losFreeTask);LOS_ListInit(&g_taskRecyleList);
⑶ for (index = 0; index <= LOSCFG_BASE_CORE_TSK_LIMIT; index++) {g_taskCBArray[index].taskStatus = OS_TASK_STATUS_UNUSED;g_taskCBArray[index].taskID = index;LOS_ListTailInsert(&g_losFreeTask, &g_taskCBArray[index].pendList);}// Ignore the return code when matching CSEC rule 6.6(4).
⑷ (VOID)memset_s((VOID *)(&g_losTask), sizeof(g_losTask), 0, sizeof(g_losTask));g_losTask.runTask = &g_taskCBArray[g_taskMaxNum];g_losTask.runTask->taskID = index;g_losTask.runTask->taskStatus = (OS_TASK_STATUS_UNUSED | OS_TASK_STATUS_RUNNING);g_losTask.runTask->priority = OS_TASK_PRIORITY_LOWEST + 1;⑸ g_idleTaskID = OS_INVALID;
⑹ return OsSchedInit();
}
2.2 創建空閑任務IdleCore000
除了初始化任務池,在系統啟動階段還會創建idle
空閑任務。⑴處設置任務初始化參數時,空閑任務的入口執行函數為OsIdleTask()
。⑵處調用函數把空閑任務狀態設置為就緒狀態。
LITE_OS_SEC_TEXT_INIT UINT32 OsIdleTaskCreate(VOID)
{UINT32 retVal;TSK_INIT_PARAM_S taskInitParam;// Ignore the return code when matching CSEC rule 6.6(4).(VOID)memset_s((VOID *)(&taskInitParam), sizeof(TSK_INIT_PARAM_S), 0, sizeof(TSK_INIT_PARAM_S));
⑴ taskInitParam.pfnTaskEntry = (TSK_ENTRY_FUNC)OsIdleTask;taskInitParam.uwStackSize = LOSCFG_BASE_CORE_TSK_IDLE_STACK_SIZE;taskInitParam.pcName = "IdleCore000";taskInitParam.usTaskPrio = OS_TASK_PRIORITY_LOWEST;retVal = LOS_TaskCreateOnly(&g_idleTaskID, &taskInitParam);if (retVal != LOS_OK) {return retVal;}⑵ OsSchedSetIdleTaskSchedPartam(OS_TCB_FROM_TID(g_idleTaskID));return LOS_OK;
}
我們看下空閑任務的入口執行函數為OsIdleTask()
,它調用OsRecyleFinishedTask()
回收任務棧資源,后文會分析如何回收任務資源。
LITE_OS_SEC_TEXT WEAK VOID OsIdleTask(VOID)
{while (1) {OsRecyleFinishedTask();HalEnterSleep(OS_SYS_DEEP_SLEEP);}
}
3、任務模塊常用操作
3.1 創建和刪除任務
3.1.1 創建任務
鴻蒙輕內核提供了2個創建任務的函數,有LOS_TaskCreate
、LOS_TaskCreateOnly
。LOS_TaskCreate
和LOS_TaskCreateOnly
的區別是,前者創建任務完畢就使任務進入就緒狀態,并觸發調度,如果就緒隊列中沒有更高優先級的任務,則運行該任務。后者只創建任務,設置任務狀態為阻塞suspend
狀態,需要開發者去調用LOS_TaskResume
使該任務進入ready狀態。
函數LOS_TaskCreate
代碼如下,可以看出創建任務的時候,調用⑴處的函數LOS_TaskCreateOnly()
來創建任務。創建任務后,執行⑵處的代碼使任務進入ready
就緒隊列,如果系統啟動完成,允許任務調度,則執行⑶觸發任務調度。如果新創建的任務優先級最高,則會被調度運行。
LITE_OS_SEC_TEXT_INIT UINT32 LOS_TaskCreate(UINT32 *taskID, TSK_INIT_PARAM_S *taskInitParam)
{UINT32 retVal;UINTPTR intSave;LosTaskCB *taskCB = NULL;⑴ retVal = LOS_TaskCreateOnly(taskID, taskInitParam);if (retVal != LOS_OK) {return retVal;}taskCB = OS_TCB_FROM_TID(*taskID);intSave = LOS_IntLock();
#if (LOSCFG_BASE_CORE_CPUP == 1)g_cpup[taskCB->taskID].cpupID = taskCB->taskID;g_cpup[taskCB->taskID].status = taskCB->taskStatus;
#endif⑵ OsSchedTaskEnQueue(taskCB);LOS_IntRestore(intSave);⑶ if (g_taskScheduled) {LOS_Schedule();}return LOS_OK;
}
我們接著分析下如何使用函數UINT32 LOS_TaskCreateOnly()
創建任務。⑴處調用OsTaskInitParamCheck()
檢測創建任務的參數的合法性。⑵處調用函數回收釋放的任務。⑶處如果任務池為空,無法創建任務,返回錯誤碼。⑷處從任務池獲取一個空閑的任務控制塊taskCB
,然后從空閑任務鏈表中刪除。⑸處根據指定的任務棧大小為任務棧申請內存,⑹處判斷任務棧內存申請釋放成功,如果申請失敗,則把任務控制塊歸還到空閑任務鏈表中,并返回錯誤碼。⑺處調用函數初始化任務棧,更新任務控制塊成員信息。詳細見后面對該函數的分析。
LITE_OS_SEC_TEXT_INIT UINT32 LOS_TaskCreateOnly(UINT32 *taskID, TSK_INIT_PARAM_S *taskInitParam)
{UINTPTR intSave;VOID *topOfStack = NULL;LosTaskCB *taskCB = NULL;UINT32 retVal;if (taskID == NULL) {return LOS_ERRNO_TSK_ID_INVALID;}⑴ retVal = OsTaskInitParamCheck(taskInitParam);if (retVal != LOS_OK) {return retVal;}⑵ OsRecyleFinishedTask();intSave = LOS_IntLock();
⑶ if (LOS_ListEmpty(&g_losFreeTask)) {retVal = LOS_ERRNO_TSK_TCB_UNAVAILABLE;OS_GOTO_ERREND();}⑷ taskCB = OS_TCB_FROM_PENDLIST(LOS_DL_LIST_FIRST(&g_losFreeTask));LOS_ListDelete(LOS_DL_LIST_FIRST(&g_losFreeTask));LOS_IntRestore(intSave);#if (LOSCFG_EXC_HRADWARE_STACK_PROTECTION == 1)UINTPTR stackPtr = (UINTPTR)LOS_MemAllocAlign(OS_TASK_STACK_ADDR, taskInitParam->uwStackSize +OS_TASK_STACK_PROTECT_SIZE, OS_TASK_STACK_PROTECT_SIZE);topOfStack = (VOID *)(stackPtr + OS_TASK_STACK_PROTECT_SIZE);
#else
⑸ topOfStack = (VOID *)LOS_MemAllocAlign(OS_TASK_STACK_ADDR, taskInitParam->uwStackSize,LOSCFG_STACK_POINT_ALIGN_SIZE);
#endif
⑹ if (topOfStack == NULL) {intSave = LOS_IntLock();LOS_ListAdd(&g_losFreeTask, &taskCB->pendList);LOS_IntRestore(intSave);return LOS_ERRNO_TSK_NO_MEMORY;}⑺ retVal = OsNewTaskInit(taskCB, taskInitParam, topOfStack);if (retVal != LOS_OK) {return retVal;}*taskID = taskCB->taskID;OsHookCall(LOS_HOOK_TYPE_TASK_CREATE, taskCB);return retVal;LOS_ERREND:LOS_IntRestore(intSave);return retVal;
}
我們看下創建任務函數調用的函數OsRecyleFinishedTask()
,該函數在系統進入空閑時也會調用。刪除運行狀態的任務時,會把任務掛在雙向鏈表里g_taskRecyleList
。任務回收函數就用來回收此類任務,實現任務資源回收。我們分析下它的代碼。⑴處循環遍歷回收鏈表,⑵從回收鏈表獲取第一個任務taskCB
,從回收鏈表刪除并插入到空閑任務鏈表里。任務棧保護在后續系列再深入分析,繼續往下看代碼,⑶處獲取任務棧棧頂指針,接著調用內存釋放函數來釋放任務棧占用的內存,并設置任務棧的棧頂為空。
STATIC VOID OsRecyleFinishedTask(VOID)
{LosTaskCB *taskCB = NULL;UINTPTR intSave;UINTPTR stackPtr;intSave = LOS_IntLock();
⑴ while (!LOS_ListEmpty(&g_taskRecyleList)) {
⑵ taskCB = OS_TCB_FROM_PENDLIST(LOS_DL_LIST_FIRST(&g_taskRecyleList));LOS_ListDelete(LOS_DL_LIST_FIRST(&g_taskRecyleList));LOS_ListAdd(&g_losFreeTask, &taskCB->pendList);
#if (LOSCFG_EXC_HRADWARE_STACK_PROTECTION == 1)stackPtr = taskCB->topOfStack - OS_TASK_STACK_PROTECT_SIZE;
#else
⑶ stackPtr = taskCB->topOfStack;
#endif(VOID)LOS_MemFree(OS_TASK_STACK_ADDR, (VOID *)stackPtr);taskCB->topOfStack = (UINT32)NULL;}LOS_IntRestore(intSave);
}
我們繼續分析下函數OsNewTaskInit()
,⑴處調用函數初始化任務棧,上一系列已經分析過該函數,代碼的其余部分用來更新任務控制塊的成員信息,比如⑵處任務狀態設置為阻塞狀態。
LITE_OS_SEC_TEXT_INIT UINT32 OsNewTaskInit(LosTaskCB *taskCB, TSK_INIT_PARAM_S *taskInitParam, VOID *topOfStack)
{
⑴ taskCB->stackPointer = HalTskStackInit(taskCB->taskID, taskInitParam->uwStackSize, topOfStack);taskCB->arg = taskInitParam->uwArg;taskCB->topOfStack = (UINT32)(UINTPTR)topOfStack;taskCB->stackSize = taskInitParam->uwStackSize;taskCB->taskSem = NULL;taskCB->taskMux = NULL;
⑵ taskCB->taskStatus = OS_TASK_STATUS_SUSPEND;taskCB->priority = taskInitParam->usTaskPrio;taskCB->timeSlice = 0;taskCB->waitTimes = 0;taskCB->taskEntry = taskInitParam->pfnTaskEntry;taskCB->event.uwEventID = OS_NULL_INT;taskCB->eventMask = 0;taskCB->taskName = taskInitParam->pcName;taskCB->msg = NULL;SET_SORTLIST_VALUE(&taskCB->sortList, OS_SORT_LINK_INVALID_TIME);return LOS_OK;
}
3.1.2 刪除任務UINT32 LOS_TaskDelete()
該函數根據傳入的參數UINT32 taskId
刪除任務。我們分析下刪除任務的源代碼,⑴處檢驗傳入的參數,⑵處如果任務還未創建,返回錯誤碼。⑶處如果刪除的任務正在運行,又處于鎖任務調度情況下,打印信息,告訴用戶不推薦在鎖任務調度期間進行任務刪除,然后執行⑷,把全局變量賦值0來解鎖任務調度。
⑸處調用函數處理任務狀態,如果處于就緒狀態設置為非就緒狀態,并從就緒隊列刪除。如果處于阻塞狀態,從阻塞隊列中刪除。如果任務處于超時等待狀態,從超時排序鏈表中刪除。⑹恢復任務控制塊事件相關的成員信息。⑺如果任務正在運行,設置任務為未使用狀態,接著調用函數OsRunningTaskDelete()
把任務放入回收鏈表,然后主動觸發任務調度,稍后詳細分析該函數。如果刪除的任務不是出于運行狀態,則執行⑻,設置任務為未使用狀態,接著把任務回收到空閑任務鏈表里,然后獲取任務棧的棧頂指針,調用內存釋放函數釋放任務棧的內存。
LITE_OS_SEC_TEXT_INIT UINT32 LOS_TaskDelete(UINT32 taskID)
{UINTPTR intSave;LosTaskCB *taskCB = OS_TCB_FROM_TID(taskID);UINTPTR stackPtr;⑴ UINT32 ret = OsCheckTaskIDValid(taskID);if (ret != LOS_OK) {return ret;}intSave = LOS_IntLock();⑵ if ((taskCB->taskStatus) & OS_TASK_STATUS_UNUSED) {LOS_IntRestore(intSave);return LOS_ERRNO_TSK_NOT_CREATED;}/* If the task is running and scheduler is locked then you can not delete it */
⑶ if (((taskCB->taskStatus) & OS_TASK_STATUS_RUNNING) && (g_losTaskLock != 0)) {PRINT_INFO("In case of task lock, task deletion is not recommended\n");
⑷ g_losTaskLock = 0;}OsHookCall(LOS_HOOK_TYPE_TASK_DELETE, taskCB);
⑸ OsSchedTaskExit(taskCB);⑹ taskCB->event.uwEventID = OS_NULL_INT;taskCB->eventMask = 0;
#if (LOSCFG_BASE_CORE_CPUP == 1)// Ignore the return code when matching CSEC rule 6.6(4).(VOID)memset_s((VOID *)&g_cpup[taskCB->taskID], sizeof(OsCpupCB), 0, sizeof(OsCpupCB));
#endifif (taskCB->taskStatus & OS_TASK_STATUS_RUNNING) {
⑺ taskCB->taskStatus = OS_TASK_STATUS_UNUSED;OsRunningTaskDelete(taskID, taskCB);LOS_IntRestore(intSave);LOS_Schedule();return LOS_OK;} else {
⑻ taskCB->taskStatus = OS_TASK_STATUS_UNUSED;LOS_ListAdd(&g_losFreeTask, &taskCB->pendList);
#if (LOSCFG_EXC_HRADWARE_STACK_PROTECTION == 1)stackPtr = taskCB->topOfStack - OS_TASK_STACK_PROTECT_SIZE;
#elsestackPtr = taskCB->topOfStack;
#endif(VOID)LOS_MemFree(OS_TASK_STACK_ADDR, (VOID *)stackPtr);taskCB->topOfStack = (UINT32)NULL;}LOS_IntRestore(intSave);return LOS_OK;
}
我們看下函數OsRunningTaskDelete()
的源碼。⑴處把當前運行的任務放入待回收鏈表里,然后執行⑵把當前運行的任務放入任務池的最后一個位置g_taskCBArray[g_taskMaxNum]
。為什么這么操作呢?等后續分析源碼的時候再來解答。
LITE_OS_SEC_TEXT_INIT STATIC_INLINE VOID OsRunningTaskDelete(UINT32 taskID, LosTaskCB *taskCB)
{
⑴ LOS_ListTailInsert(&g_taskRecyleList, &taskCB->pendList);
⑵ g_losTask.runTask = &g_taskCBArray[g_taskMaxNum];g_losTask.runTask->taskID = taskID;g_losTask.runTask->taskStatus = taskCB->taskStatus | OS_TASK_STATUS_RUNNING;g_losTask.runTask->topOfStack = taskCB->topOfStack;g_losTask.runTask->taskName = taskCB->taskName;
}
3.2 控制任務狀態
3.2.1 恢復掛起的任務LOS_TaskResume()
恢復掛起的任務,使該任務進入就緒狀態,和下文中的LOS_TaskSuspend()
成對使用。⑴處獲取任務的TCB
,⑵處對任務狀態進行判斷,如果任務未創建或者非阻塞狀態,則返回錯誤碼。執行⑶設置任務狀態為非掛起狀態。⑶處獲取任務的狀態進行判斷,如果任務沒有創建或者不是掛起狀態,則返回相應的錯誤碼。 ⑷檢查任務狀態是否為OS_CHECK_TASK_BLOCK
,即(OS_TASK_STATUS_DELAY | OS_TASK_STATUS_PEND | OS_TASK_STATUS_SUSPEND)
中的一種,這幾個狀態影響恢復掛起的任務。如果非上述幾個狀態,執行⑸調用函數,把任務狀態改為就緒狀態,插入任務就緒隊列。如果支持支持調度,則執行⑹觸發調度。
LITE_OS_SEC_TEXT_INIT UINT32 LOS_TaskResume(UINT32 taskID)
{UINTPTR intSave;LosTaskCB *taskCB = NULL;UINT16 tempStatus;UINT32 retErr = OS_ERROR;if (taskID > LOSCFG_BASE_CORE_TSK_LIMIT) {return LOS_ERRNO_TSK_ID_INVALID;}⑴ taskCB = OS_TCB_FROM_TID(taskID);intSave = LOS_IntLock();tempStatus = taskCB->taskStatus;⑵ if (tempStatus & OS_TASK_STATUS_UNUSED) {retErr = LOS_ERRNO_TSK_NOT_CREATED;OS_GOTO_ERREND();} else if (!(tempStatus & OS_TASK_STATUS_SUSPEND)) {retErr = LOS_ERRNO_TSK_NOT_SUSPENDED;OS_GOTO_ERREND();}⑶ taskCB->taskStatus &= (~OS_TASK_STATUS_SUSPEND);
⑷ if (!(taskCB->taskStatus & OS_CHECK_TASK_BLOCK)) {
⑸ OsSchedTaskEnQueue(taskCB);if (g_taskScheduled) {LOS_IntRestore(intSave);
⑹ LOS_Schedule();return LOS_OK;}}LOS_IntRestore(intSave);return LOS_OK;LOS_ERREND:LOS_IntRestore(intSave);return retErr;
}
3.2.2 掛起指定的任務LOS_TaskSuspend()
函數用于掛起指定的任務。⑴處獲取任務的TCB
,⑵處開始獲取任務的狀態進行判斷,如果任務沒有創建、任務已經掛起,返回相應的錯誤碼。⑶處如果任務是運行狀態,并且鎖任務調度時,跳轉到LOS_ERREND
結束掛起操作。⑷處如果任務是就緒狀態,調用函數從就緒隊列出隊,并取消任務的就緒狀態。⑸處語句設置任務狀態為阻塞狀態。⑹如果掛起的是當前運行的任務,則會主動觸發調度。
LITE_OS_SEC_TEXT_INIT UINT32 LOS_TaskSuspend(UINT32 taskID)
{UINTPTR intSave;LosTaskCB *taskCB = NULL;UINT16 tempStatus;UINT32 retErr;retErr = OsCheckTaskIDValid(taskID);if (retErr != LOS_OK) {return retErr;}⑴ taskCB = OS_TCB_FROM_TID(taskID);intSave = LOS_IntLock();
⑵ tempStatus = taskCB->taskStatus;if (tempStatus & OS_TASK_STATUS_UNUSED) {retErr = LOS_ERRNO_TSK_NOT_CREATED;OS_GOTO_ERREND();}if (tempStatus & OS_TASK_STATUS_SUSPEND) {retErr = LOS_ERRNO_TSK_ALREADY_SUSPENDED;OS_GOTO_ERREND();}⑶ if ((tempStatus & OS_TASK_STATUS_RUNNING) && (g_losTaskLock != 0)) {retErr = LOS_ERRNO_TSK_SUSPEND_LOCKED;OS_GOTO_ERREND();}⑷ if (tempStatus & OS_TASK_STATUS_READY) {OsSchedTaskDeQueue(taskCB);}⑸ taskCB->taskStatus |= OS_TASK_STATUS_SUSPEND;OsHookCall(LOS_HOOK_TYPE_MOVEDTASKTOSUSPENDEDLIST, taskCB);
⑹ if (taskID == g_losTask.runTask->taskID) {LOS_IntRestore(intSave);LOS_Schedule();return LOS_OK;}LOS_IntRestore(intSave);return LOS_OK;LOS_ERREND:LOS_IntRestore(intSave);return retErr;
}
3.2.3 任務延時等待LOS_TaskDelay()
任務延時等待,釋放CPU
,等待時間到期后該任務會重新進入就緒狀態。⑴處代碼判斷系統處于中斷,如果是,則返回錯誤碼,不允許任務延時等待。⑵如果處于鎖任務調度期間,則返回錯誤碼。
⑶處如果延遲的時間為0,則執行讓權操作,否則執行⑷,調用函數OsSchedDelay()
把當前任務設置為延時等待狀態,然后調用LOS_Schedule()
觸發調度。
LITE_OS_SEC_TEXT UINT32 LOS_TaskDelay(UINT32 tick)
{UINTPTR intSave;⑴ if (OS_INT_ACTIVE) {return LOS_ERRNO_TSK_DELAY_IN_INT;}⑵ if (g_losTaskLock != 0) {return LOS_ERRNO_TSK_DELAY_IN_LOCK;}OsHookCall(LOS_HOOK_TYPE_TASK_DELAY, tick);
⑶ if (tick == 0) {return LOS_TaskYield();} else {intSave = LOS_IntLock();
⑷ OsSchedDelay(g_losTask.runTask, tick);OsHookCall(LOS_HOOK_TYPE_MOVEDTASKTODELAYEDLIST, g_losTask.runTask);LOS_IntRestore(intSave);LOS_Schedule();}return LOS_OK;
}
另外還提供了函數LOS_Msleep()
和LOS_UDelay()
,前者以毫秒為單位進行延遲等待。后者也是以毫秒為單位進行延遲等待,但是不會觸發任務調度,當前任務不會釋放CPU
。
LITE_OS_SEC_TEXT_MINOR VOID LOS_Msleep(UINT32 mSecs)
{UINT32 interval;if (OS_INT_ACTIVE) {return;}if (mSecs == 0) {interval = 0;} else {interval = LOS_MS2Tick(mSecs);if (interval == 0) {interval = 1;}}(VOID)LOS_TaskDelay(interval);
}VOID LOS_UDelay(UINT64 microseconds)
{UINT64 endTime;if (microseconds == 0) {return;}endTime = (microseconds / OS_SYS_US_PER_SECOND) * OS_SYS_CLOCK +(microseconds % OS_SYS_US_PER_SECOND) * OS_SYS_CLOCK / OS_SYS_US_PER_SECOND;endTime = LOS_SysCycleGet() + endTime;while (LOS_SysCycleGet() < endTime) {}return;
}
3.2.4 任務讓權LOS_TaskYield()
讓權函數通過把當前任務時間片設置為0,釋放CPU
占用,重新調度給其他高優先級任務執行。⑴處調用函數把當前任務時間片設置為0,然后執行⑵主動觸發任務調度。
LITE_OS_SEC_TEXT_MINOR UINT32 LOS_TaskYield(VOID)
{UINTPTR intSave;intSave = LOS_IntLock();
⑴ OsSchedYield();LOS_IntRestore(intSave);
⑵ LOS_Schedule();return LOS_OK;
}
接下來看下函數OsSchedYield()
的源碼。代碼很簡單,獲取當前運行的任務,然后把其時間片設置為0,如下:
VOID OsSchedYield(VOID)
{LosTaskCB *runTask = g_losTask.runTask;runTask->timeSlice = 0;
}
3.3 控制任務調度
3.3.1 鎖任務調度LOS_TaskLock()
鎖任務調度LOS_TaskLock()
比較簡單,把任務鎖調度計數器全局變量增加1即可,代碼如下。
LITE_OS_SEC_TEXT_MINOR VOID LOS_TaskLock(VOID)
{UINTPTR intSave;intSave = LOS_IntLock();g_losTaskLock++;LOS_IntRestore(intSave);
}
3.3.2 解鎖任務調度LOS_TaskUnlock()
我們看看解鎖任務調度函數LOS_TaskUnlock()
,⑴處如果任務鎖調度計數器全局變量數值大于0,對其減1。⑵處如果任務鎖調度計數器等于0,則執行⑶處觸發調度。代碼如下:
LITE_OS_SEC_TEXT_MINOR VOID LOS_TaskUnlock(VOID)
{UINTPTR intSave;intSave = LOS_IntLock();
⑴ if (g_losTaskLock > 0) {g_losTaskLock--;
⑵ if (g_losTaskLock == 0) {LOS_IntRestore(intSave);
⑶ LOS_Schedule();return;}}LOS_IntRestore(intSave);
}
3.4 控制任務優先級
LiteOS-M
內核支持動態設置任務的優先級,提供了一些操作。
3.4.1 設置指定任務的優先級LOS_TaskPriSet
支持設置指定任務Id
的優先級,也支持對當前運行任務進行優先級設置。⑴處開始,做些基礎校驗,包含檢驗傳入的優先級參數taskPrio
,指定任務的Id
,任務是否未創建等,如果沒有通過參數校驗,則返回錯誤碼。⑵處調用函數設置任務優先級,稍后分析該函數。如果任務處于就緒狀態或者運行狀態,則會執行⑶主動觸發任務調度。
LITE_OS_SEC_TEXT_MINOR UINT32 LOS_TaskPriSet(UINT32 taskID, UINT16 taskPrio)
{BOOL isReady = FALSE;UINTPTR intSave;LosTaskCB *taskCB = NULL;UINT16 tempStatus;⑴ if (taskPrio > OS_TASK_PRIORITY_LOWEST) {return LOS_ERRNO_TSK_PRIOR_ERROR;}if (taskID == g_idleTaskID) {return LOS_ERRNO_TSK_OPERATE_IDLE;}if (taskID == g_swtmrTaskID) {return LOS_ERRNO_TSK_OPERATE_SWTMR;}if (OS_CHECK_TSK_PID_NOIDLE(taskID)) {return LOS_ERRNO_TSK_ID_INVALID;}taskCB = OS_TCB_FROM_TID(taskID);intSave = LOS_IntLock();tempStatus = taskCB->taskStatus;if (tempStatus & OS_TASK_STATUS_UNUSED) {LOS_IntRestore(intSave);return LOS_ERRNO_TSK_NOT_CREATED;}⑵ isReady = OsSchedModifyTaskSchedParam(taskCB, taskPrio);LOS_IntRestore(intSave);if (isReady) {
⑶ LOS_Schedule();}return LOS_OK;
}
接下來,我們分析下函數OsSchedModifyTaskSchedParam()
。⑴處如果任務處于就緒狀態,需要先出隊設置優先級,然后入隊就緒隊列。如果非就緒狀態,可以直接執行⑵處語句修改任務優先級。如果任務正在運行,需要返回TRUE,標記下需要任務調度。
BOOL OsSchedModifyTaskSchedParam(LosTaskCB *taskCB, UINT16 priority)
{if (taskCB->taskStatus & OS_TASK_STATUS_READY) {
⑴ OsSchedTaskDeQueue(taskCB);taskCB->priority = priority;OsSchedTaskEnQueue(taskCB);return TRUE;}⑵ taskCB->priority = priority;OsHookCall(LOS_HOOK_TYPE_TASK_PRIMODIFY, taskCB, taskCB->priority);if (taskCB->taskStatus & OS_TASK_STATUS_RUNNING) {return TRUE;}return FALSE;
}
3.4.2 獲取指定任務的優先級LOS_TaskPriGet
獲取指定任務的優先級LOS_TaskPriGet()
代碼比較簡單,⑴處如果任務編號無效,返回錯誤碼。⑵處如果任務未創建返回錯誤碼。如果參數校驗通過,執行⑶獲取任務的優先級數值。
LITE_OS_SEC_TEXT_MINOR UINT16 LOS_TaskPriGet(UINT32 taskID)
{UINTPTR intSave;LosTaskCB *taskCB = NULL;UINT16 priority;⑴ if (OS_CHECK_TSK_PID_NOIDLE(taskID)) {return (UINT16)OS_INVALID;}taskCB = OS_TCB_FROM_TID(taskID);intSave = LOS_IntLock();⑵ if (taskCB->taskStatus & OS_TASK_STATUS_UNUSED) {LOS_IntRestore(intSave);return (UINT16)OS_INVALID;}⑶ priority = taskCB->priority;LOS_IntRestore(intSave);return priority;
}
3.5 任務阻塞和喚醒
最后,我們分析下函數OsSchedTaskWait()
和OsSchedTaskWake()
,這2個函數定義在文件kernel\src\los_sched.c
中。任務在申請互斥鎖、信號量、出入隊列、讀寫事件時,都可能導致任務進入阻塞狀態,對應地也需要任務喚醒重新進入就緒隊列狀態。這2個函數就負責任務的阻塞和喚醒,我們分析下他們的代碼。
3.5.1 任務阻塞
我們分析下任務阻塞的函數OsSchedTaskWait()
,需要2個參數:LOS_DL_LIST *list
是互斥鎖等資源的阻塞鏈表,阻塞的任務會掛這個鏈表里;UINT32 ticks
是任務阻塞的時間。分析下具體代碼:
⑴獲取正在請求互斥鎖等資源的當前任務,⑵設置任務狀態為阻塞狀態。⑶把任務插入互斥鎖等資源的阻塞鏈表的尾部。⑷如果不是永久阻塞等待,任務的狀態還需要設置為OS_TASK_STATUS_PEND_TIME
,然后設置任務的等待時間為傳入的參數。
VOID OsSchedTaskWait(LOS_DL_LIST *list, UINT32 ticks)
{
⑴ LosTaskCB *runTask = g_losTask.runTask;⑵ runTask->taskStatus |= OS_TASK_STATUS_PEND;
⑶ LOS_ListTailInsert(list, &runTask->pendList);if (ticks != LOS_WAIT_FOREVER) {
⑷ runTask->taskStatus |= OS_TASK_STATUS_PEND_TIME;runTask->waitTimes = ticks;}
}
3.5.2 任務喚醒
我們分析下任務喚醒的函數OsSchedTaskWake()
,需要1個參數:LosTaskCB *resumedTask
是需要喚醒的任務;任務喚醒函數會從阻塞鏈表里刪除并加入就緒隊列,下面分析下具體代碼:
⑴把要喚醒的任務從所在的阻塞隊列中刪除,然后更改狀態不再為阻塞狀態。⑵如果任務不是永久等待,需要從定時器排序鏈表中刪除,并設置狀態不再是等待超時。⑶如果任務是阻塞狀態,改為就緒狀態并加入就緒隊列。
VOID OsSchedTaskWake(LosTaskCB *resumedTask)
{
⑴ LOS_ListDelete(&resumedTask->pendList);resumedTask->taskStatus &= ~OS_TASK_STATUS_PEND;⑵ if (resumedTask->taskStatus & OS_TASK_STATUS_PEND_TIME) {OsDeleteSortLink(&resumedTask->sortList, OS_SORT_LINK_TASK);resumedTask->taskStatus &= ~OS_TASK_STATUS_PEND_TIME;}⑶ if (!(resumedTask->taskStatus & OS_TASK_STATUS_SUSPEND)) {OsSchedTaskEnQueue(resumedTask);}
}
小結
本文帶領大家一起剖析了鴻蒙輕內核任務模塊的源代碼,包含任務模塊的結構體,任務初始化過程源代碼,任務常用操作的源代碼。
如果大家想更加深入的學習 OpenHarmony 開發的內容,不妨可以參考以下相關學習文檔進行學習,助你快速提升自己:
OpenHarmony 開發環境搭建:https://qr18.cn/CgxrRy
《OpenHarmony源碼解析》:https://qr18.cn/CgxrRy
- 搭建開發環境
- Windows 開發環境的搭建
- Ubuntu 開發環境搭建
- Linux 與 Windows 之間的文件共享
- ……
系統架構分析:https://qr18.cn/CgxrRy
- 構建子系統
- 啟動流程
- 子系統
- 分布式任務調度子系統
- 分布式通信子系統
- 驅動子系統
- ……