前言
在鴻蒙內核中,廣義上可理解為一個Task就是一個線程
一、怎么理解Task
1. 官方文檔是怎么描述線程
基本概念
從系統的角度看,線程是競爭系統資源的最小運行單元。線程可以使用或等待CPU、使用內存空間等系統資源,并獨立于其它線程運行。
鴻蒙內核每個進程內的線程獨立運行、獨立調度,當前進程內線程的調度不受其它進程內線程的影響。
鴻蒙內核中的線程采用搶占式調度機制,同時支持時間片輪轉調度和FIFO調度方式。
鴻蒙內核的線程一共有32個優先級(0-31),最高優先級為0,最低優先級為31。
當前進程內高優先級的線程可搶占當前進程內低優先級線程,當前進程內低優先級線程必須在當前進程內高優先級線程阻塞或結束后才能得到調度。
線程狀態說明:
初始化(Init):該線程正在被創建。
就緒(Ready):該線程在就緒列表中,等待CPU調度。
運行(Running):該線程正在運行。
阻塞(Blocked):該線程被阻塞掛起。Blocked狀態包括:pend(因為鎖、事件、信號量等阻塞)、suspend(主動pend)、delay(延時阻塞)、pendtime(因為鎖、事件、信號量時間等超時等待)。
退出(Exit):該線程運行結束,等待父線程回收其控制塊資源。
圖 1 線程狀態遷移示意圖
注意官方文檔說的是線程,沒有提到task(任務),但內核源碼中卻有大量 task代碼,很少有線程(thread)代碼 ,這是怎么回事?
其實在鴻蒙內核中, task就是線程, 初學者完全可以這么理解,但二者還是有區別,否則干嘛要分兩個詞描述。
到底有什么區別?是管理上的區別,task是調度層面的概念,線程是進程層面的概念。 就像同一個人在不同的管理體系中會有不同的身份一樣,一個男人既可以是 孩子,爸爸,丈夫,或者程序員,視角不同功能也會不同。
如何證明是一個東西,繼續再往下看。
2. 執行task命令
鴻蒙 task 命令的執行結果:
task命令 查出每個任務在生命周期內的運行情況,它運行的內存空間,優先級,時間片,入口執行函數,進程ID,狀態等等信息,非常的復雜。這么復雜的信息就需要一個結構體來承載。而這個結構體就是 LosTaskCB(任務控制塊)
對應張大爺的故事:task就是一個用戶的節目清單里的一個節目,用戶總清單就是一個進程,所以上面會有很多的節目。
3. task長得什么樣子?
說LosTaskCB之前先說下官方文檔任務狀態對應的 define,可以看出task和線程是一個東西。
#define OS_TASK_STATUS_INIT 0x0001U #define OS_TASK_STATUS_READY 0x0002U #define OS_TASK_STATUS_RUNNING 0x0004U #define OS_TASK_STATUS_SUSPEND 0x0008U #define OS_TASK_STATUS_PEND 0x0010U #define OS_TASK_STATUS_DELAY 0x0020U #define OS_TASK_STATUS_TIMEOUT 0x0040U #define OS_TASK_STATUS_PEND_TIME 0x0080U #define OS_TASK_STATUS_EXIT 0x0100U
LosTaskCB長什么樣?抱歉,它確實有點長,但還是要全部貼出全貌。
typedef struct { VOID *stackPointer; /**< Task stack pointer */ UINT16 taskStatus; /**< Task status */ UINT16 priority; /**< Task priority */ UINT16 policy; UINT16 timeSlice; /**< Remaining time slice */ UINT32 stackSize; /**< Task stack size */ UINTPTR topOfStack; /**< Task stack top */ UINT32 taskID; /**< Task ID */ TSK_ENTRY_FUNC taskEntry; /**< Task entrance function */ VOID *joinRetval; /**< pthread adaption */ VOID *taskSem; /**< Task-held semaphore */ VOID *taskMux; /**< Task-held mutex */ VOID *taskEvent; /**< Task-held event */ UINTPTR args[4]; /**< Parameter, of which the maximum number is 4 */ CHAR taskName[OS_TCB_NAME_LEN]; /**< Task name */ LOS_DL_LIST pendList; /**< Task pend node */ LOS_DL_LIST threadList; /**< thread list */ SortLinkList sortList; /**< Task sortlink node */ UINT32 eventMask; /**< Event mask */ UINT32 eventMode; /**< Event mode */ UINT32 priBitMap; /**< BitMap for recording the change of task priority, the priority can not be greater than 31 */ INT32 errorNo; /**< Error Num */ UINT32 signal; /**< Task signal */ sig_cb sig; #if (LOSCFG_KERNEL_SMP == YES) UINT16 currCpu; /**< CPU core number of this task is running on */ UINT16 lastCpu; /**< CPU core number of this task is running on last time */ UINT16 cpuAffiMask; /**< CPU affinity mask, support up to 16 cores */ UINT32 timerCpu; /**< CPU core number of this task is delayed or pended */ #if (LOSCFG_KERNEL_SMP_TASK_SYNC == YES) UINT32 syncSignal; /**< Synchronization for signal handling */ #endif #if (LOSCFG_KERNEL_SMP_LOCKDEP == YES) LockDep lockDep; #endif #if (LOSCFG_KERNEL_SCHED_STATISTICS == YES) SchedStat schedStat; /**< Schedule statistics */ #endif #endif UINTPTR userArea; UINTPTR userMapBase; UINT32 userMapSize; /**< user thread stack size ,real size : userMapSize + USER_STACK_MIN_SIZE */ UINT32 processID; /**< Which belong process */ FutexNode futex; LOS_DL_LIST joinList; /**< join list */ LOS_DL_LIST lockList; /**< Hold the lock list */ UINT32 waitID; /**< Wait for the PID or GID of the child process */ UINT16 waitFlag; /**< The type of child process that is waiting, belonging to a group or parent, a specific child process, or any child process */ #if (LOSCFG_KERNEL_LITEIPC == YES) UINT32 ipcStatus; LOS_DL_LIST msgListHead; BOOL accessMap[LOSCFG_BASE_CORE_TSK_LIMIT]; #endif } LosTaskCB;
結構體LosTaskCB內容很多,各代表什么含義?
LosTaskCB相當于任務在內核中的身份證,它反映出每個任務在生命周期內的運行情況。既然是周期就會有狀態,要運行就需要內存空間,就需要被內核算法調度,被選中CPU就去執行代碼段指令,CPU要執行就需要告訴它從哪里開始執行,因為是多線程,但只有一個CPU就需要不斷的切換任務,那執行會被中斷,也需要再恢復后繼續執行,又如何保證恢復的任務執行不會出錯,這些問題都需要說明白。
二、Task怎么管理
1.什么是任務池?
前面已經說了任務是內核調度層面的概念,調度算法保證了task有序的執行,調度機制詳見其他姊妹篇的介紹。
如此多的任務怎么管理和執行?管理靠任務池和就緒隊列,執行靠調度算法。
代碼如下(OsTaskInit):
LITE_OS_SEC_TEXT_INIT UINT32 OsTaskInit(VOID) { UINT32 index; UINT32 ret; UINT32 size; g_taskMaxNum = LOSCFG_BASE_CORE_TSK_LIMIT;//任務池中最多默認128個,可謂鐵打的任務池流水的線程 size = (g_taskMaxNum + 1) * sizeof(LosTaskCB); /* * This memory is resident memory and is used to save the system resources * of task control block and will not be freed. */ 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 < g_taskMaxNum; index++) { g_taskCBArray[index].taskStatus = OS_TASK_STATUS_UNUSED; g_taskCBArray[index].taskID = index;//任務ID最大默認127 LOS_ListTailInsert(&g_losFreeTask, &g_taskCBArray[index].pendList);//都插入空閑任務鏈表 } ret = OsPriQueueInit();//創建32個任務優先級隊列,即32個雙向循環鏈表 if (ret != LOS_OK) { return LOS_ERRNO_TSK_NO_MEMORY; } /* init sortlink for each core */ for (index = 0; index < LOSCFG_KERNEL_CORE_NUM; index++) { ret = OsSortLinkInit(&g_percpu[index].taskSortLink);//每個CPU內核都有一個執行任務鏈表 if (ret != LOS_OK) { return LOS_ERRNO_TSK_NO_MEMORY; } } return LOS_OK; }
g_taskCBArray 就是個任務池,默認創建128個任務,常駐內存,不被釋放。
g_losFreeTask是空閑任務鏈表,想創建任務時來這里申請一個空閑任務,用完了就回收掉,繼續給后面的申請使用。
g_taskRecyleList是回收任務鏈表,專用來回收exit 任務,任務所占資源被確認歸還后被徹底刪除,就像員工離職一樣,得有個離職隊列和流程,要歸還電腦,郵箱,有沒有借錢要還的 等操作。
對應張大爺的故事:用戶要來場館領取表格填節目單,場館只準備了128張表格,領完就沒有了,但是節目表演完了會回收表格,這樣多了一張表格就可以給其他人領取了,這128張表格對應鴻蒙內核這就是任務池,簡單吧。
2.就緒隊列是怎么回事
CPU執行速度是很快的,鴻蒙內核默認一個時間片是 10ms, 資源有限,需要在眾多任務中來回的切換,所以絕不能讓CPU等待任務,CPU就像公司最大的領導,下面很多的部門等領導來審批,吃飯。只有大家等領導,哪有領導等你們的道理,所以工作要提前準備好,每個部門的優先級又不一樣,所以每個部門都要有個任務隊列,里面放的是領導能直接處理的任務,沒準備好的不要放進來,因為這是給CPU提前準備好的糧食!
這就是就緒隊列的原理,一共有32個就緒隊列,進程和線程都有,因為線程的優先級是默認32個, 每個隊列中放同等優先級的task.
還是看源碼吧
#define OS_PRIORITY_QUEUE_NUM 32 UINT32 OsPriQueueInit(VOID) { UINT32 priority; /* system resident resource */ g_priQueueList = (LOS_DL_LIST *)LOS_MemAlloc(m_aucSysMem0, (OS_PRIORITY_QUEUE_NUM * sizeof(LOS_DL_LIST)));//常駐內存,不被釋放 if (g_priQueueList == NULL) { return LOS_NOK; } for (priority = 0; priority < OS_PRIORITY_QUEUE_NUM; ++priority) { LOS_ListInit(&g_priQueueList[priority]); } return LOS_OK; }
注意看g_priQueueList 的內存分配,就是32個LOS_DL_LIST,還記得LOS_DL_LIST的妙用嗎,不清楚的去鴻蒙源碼分析(總目錄)里面翻。
對應張大爺的故事:就是門口那些排隊的都是至少有一個節目單是符合表演標準的,資源都到位了,沒有的連排隊的資格都木有,就慢慢等吧。
3.任務棧是怎么回事
每個任務都是獨立開的,任務之間也相互獨立,之間通訊通過IPC,這里的“獨立”指的是每個任務都有自己的運行環境 —— ??臻g,稱為任務棧,??臻g里保存的信息包含局部變量、寄存器、函數參數、函數返回地址等等
但系統中只有一個CPU,任務又是獨立的,調度的本質就是CPU執行一個新task,老task在什么地方被中斷誰也不清楚,是隨機的。那如何保證老任務被再次調度選中時還能從上次被中斷的地方繼續玩下去呢?
答案是:任務上下文,CPU內有一堆的寄存器,CPU運行本質的就是這些寄存器的值不斷的變化,只要切換時把這些值保存起來,再還原回去就能保證task的連續執行,讓用戶毫無感知。鴻蒙內核給一個任務執行的時間是 20ms ,也就是說有多任務競爭的情況下,一秒鐘內最多要來回切換50次。
對應張大爺的故事:就是碰到節目沒有表演完就必須打斷的情況下,需要把當時的情況記錄下來,比如小朋友在演躲貓貓的游戲,一半不演了,張三正在樹上,李四正在廁所躲,都記錄下來,下次再回來你們上次在哪就會哪呆著去,就位了繼續表演。這樣就接上了,觀眾就木有感覺了。
任務上下文(TaskContext)是怎樣的呢?還是直接看源碼
/* The size of this structure must be smaller than or equal to the size specified by OS_TSK_STACK_ALIGN (16 bytes). */ typedef struct { #if !defined(LOSCFG_ARCH_FPU_DISABLE) UINT64 D[FP_REGS_NUM]; /* D0-D31 */ UINT32 regFPSCR; /* FPSCR */ UINT32 regFPEXC; /* FPEXC */ #endif UINT32 resved; /* It's stack 8 aligned */ UINT32 regPSR; UINT32 R[GEN_REGS_NUM]; /* R0-R12 */ UINT32 SP; /* R13 */ UINT32 LR; /* R14 */ UINT32 PC; /* R15 */ } TaskContext;
發現基本都是CPU寄存器的恢復現場值, 具體各寄存器有什么作用大家可以去網上詳查,后續也有專門的文章來介紹。這里說其中的三個寄存器 SP, LR, PC
LR
用途有二,一是保存子程序返回地址,當調用BL、BX、BLX等跳轉指令時會自動保存返回地址到LR;二是保存異常發生的異常返回地址。
PC(Program Counter)
為程序計數器,用于保存程序的執行地址,在ARM的三級流水線架構中,程序流水線包括取址、譯碼和執行三個階段,PC指向的是當前取址的程序地址,所以32位ARM中,譯碼地址(正在解析還未執行的程序)為PC-4,執行地址(當前正在執行的程序地址)為PC-8, 當突然發生中斷的時候,保存的是PC的地址。
SP
每一種異常模式都有其自己獨立的r13,它通常指向異常模式所專用的堆棧,當ARM進入異常模式的時候,程序就可以把一般通用寄存器壓入堆棧,返回時再出棧,保證了各種模式下程序的狀態的完整性。
4.任務棧初始化
任務棧的初始化就是任務上下文的初始化,因為任務沒開始執行,里面除了上下文不會有其他內容,注意上下文存放的位置在棧的底部。
三、Task函數集
LITE_OS_SEC_TEXT_INIT VOID *OsTaskStackInit(UINT32 taskID, UINT32 stackSize, VOID *topStack, BOOL initFlag) { UINT32 index = 1; TaskContext *taskContext = NULL; if (initFlag == TRUE) { OsStackInit(topStack, stackSize); } taskContext = (TaskContext *)(((UINTPTR)topStack + stackSize) - sizeof(TaskContext));//注意看上下文將存放在棧的底部 /* initialize the task context */ #ifdef LOSCFG_GDB taskContext->PC = (UINTPTR)OsTaskEntrySetupLoopFrame; #else taskContext->PC = (UINTPTR)OsTaskEntry;//程序計數器,CPU首次執行task時跑的第一條指令位置 #endif taskContext->LR = (UINTPTR)OsTaskExit; /* LR should be kept, to distinguish it's THUMB or ARM instruction */ taskContext->resved = 0x0; taskContext->R[0] = taskID; /* R0 */ taskContext->R[index++] = 0x01010101; /* R1, 0x01010101 : reg initialed magic word */ for (; index < GEN_REGS_NUM; index++) { //R2 - R12的初始化很有意思,為什么要這么做? taskContext->R[index] = taskContext->R[index - 1] + taskContext->R[1]; /* R2 - R12 */ } #ifdef LOSCFG_INTERWORK_THUMB // 16位模式 taskContext->regPSR = PSR_MODE_SVC_THUMB; /* CPSR (Enable IRQ and FIQ interrupts, THUMNB-mode) */ #else taskContext->regPSR = PSR_MODE_SVC_ARM; /* CPSR (Enable IRQ and FIQ interrupts, ARM-mode) */ #endif #if !defined(LOSCFG_ARCH_FPU_DISABLE) /* 0xAAA0000000000000LL : float reg initialed magic word */ for (index = 0; index < FP_REGS_NUM; index++) { taskContext->D[index] = 0xAAA0000000000000LL + index; /* D0 - D31 */ } taskContext->regFPSCR = 0; taskContext->regFPEXC = FP_EN; #endif return (VOID *)taskContext; }
1.使用場景和功能
任務創建后,內核可以執行鎖任務調度,解鎖任務調度,掛起,恢復,延時等操作,同時也可以設置任務優先級,獲取任務優先級。任務結束的時候,則進行當前任務自刪除操作。
Huawei LiteOS 系統中的任務管理模塊為用戶提供下面幾種功能。
功能分類 | 接口名 | 描述 |
---|---|---|
任務的創建和刪除 | LOS_TaskCreateOnly | 創建任務,并使該任務進入suspend狀態,并不調度。 |
LOS_TaskCreate | 創建任務,并使該任務進入ready狀態,并調度。 | |
LOS_TaskDelete | 刪除指定的任務。 | |
任務狀態控制 | LOS_TaskResume | 恢復掛起的任務。 |
LOS_TaskSuspend | 掛起指定的任務。 | |
LOS_TaskDelay | 任務延時等待。 | |
LOS_TaskYield | 顯式放權,調整指定優先級的任務調度順序。 | |
任務調度的控制 | LOS_TaskLock | 鎖任務調度。 |
LOS_TaskUnlock | 解鎖任務調度。 | |
任務優先級的控制 | LOS_CurTaskPriSet | 設置當前任務的優先級。 |
LOS_TaskPriSet | 設置指定任務的優先級。 | |
LOS_TaskPriGet | 獲取指定任務的優先級。 | |
任務信息獲取 | LOS_CurTaskIDGet | 獲取當前任務的ID。 |
LOS_TaskInfoGet | 設置指定任務的優先級。 | |
LOS_TaskPriGet | 獲取指定任務的信息。 | |
LOS_TaskStatusGet | 獲取指定任務的狀態。 | |
LOS_TaskNameGet | 獲取指定任務的名稱。 | |
LOS_TaskInfoMonitor | 監控所有任務,獲取所有任務的信息。 | |
LOS_NextTaskIDGet | 獲取即將被調度的任務的ID。 |
2.創建任務的過程
創建任務之前先了解另一個結構體 tagTskInitParam
typedef struct tagTskInitParam { TSK_ENTRY_FUNC pfnTaskEntry; /**< Task entrance function */ UINT16 usTaskPrio; /**< Task priority */ UINT16 policy; /**< Task policy */ UINTPTR auwArgs[4]; /**< Task parameters, of which the maximum number is four */ UINT32 uwStackSize; /**< Task stack size */ CHAR *pcName; /**< Task name */ #if (LOSCFG_KERNEL_SMP == YES) UINT16 usCpuAffiMask; /**< Task cpu affinity mask */ #endif UINT32 uwResved; /**< It is automatically deleted if set to LOS_TASK_STATUS_DETACHED. It is unable to be deleted if set to 0. */ UINT16 consoleID; /**< The console id of task belongs */ UINT32 processID; UserTaskParam userParam; } TSK_INIT_PARAM_S;
這些初始化參數是外露的任務初始參數,pfnTaskEntry對java來說就是你new進程的run(),需要上層使用者提供.
看個例子吧:shell中敲 ping 命令看下它創建的過程
u32_t osShellPing(int argc, const char **argv) { int ret; u32_t i = 0; u32_t count = 0; int count_set = 0; u32_t interval = 1000; /* default ping interval */ u32_t data_len = 48; /* default data length */ ip4_addr_t dst_ipaddr; TSK_INIT_PARAM_S stPingTask; // ...省去一些中間代碼 /* start one task if ping forever or ping count greater than 60 */ if (count == 0 || count > LWIP_SHELL_CMD_PING_RETRY_TIMES) { if (ping_taskid > 0) { PRINTK("Ping task already running and only support one now "); return LOS_NOK; } stPingTask.pfnTaskEntry = (TSK_ENTRY_FUNC)ping_cmd;//線程的執行函數 stPingTask.uwStackSize = LOSCFG_BASE_CORE_TSK_DEFAULT_STACK_SIZE;//0x4000 = 16K stPingTask.pcName = "ping_task"; stPingTask.usTaskPrio = 8; /* higher than shell 優先級高于10,屬于內核態線程*/ stPingTask.uwResved = LOS_TASK_STATUS_DETACHED; stPingTask.auwArgs[0] = dst_ipaddr.addr; /* network order */ stPingTask.auwArgs[1] = count; stPingTask.auwArgs[2] = interval; stPingTask.auwArgs[3] = data_len; ret = LOS_TaskCreate((UINT32 *)(&ping_taskid), &stPingTask); } // ... return LOS_OK; ping_error: lwip_ping_usage(); return LOS_NOK; }
發現ping的調度優先級是8,比shell 還高,那shell的是多少?答案是:看源碼是 9
LITE_OS_SEC_TEXT_MINOR UINT32 ShellTaskInit(ShellCB *shellCB) { CHAR *name = NULL; TSK_INIT_PARAM_S initParam = { 0}; if (shellCB->consoleID == CONSOLE_SERIAL) { name = SERIAL_SHELL_TASK_NAME; } else if (shellCB->consoleID == CONSOLE_TELNET) { name = TELNET_SHELL_TASK_NAME; } else { return LOS_NOK; } initParam.pfnTaskEntry = (TSK_ENTRY_FUNC)ShellTask; initParam.usTaskPrio = 9; /* 9:shell task priority */ initParam.auwArgs[0] = (UINTPTR)shellCB; initParam.uwStackSize = 0x3000; initParam.pcName = name; initParam.uwResved = LOS_TASK_STATUS_DETACHED; (VOID)LOS_EventInit(&shellCB->shellEvent); return LOS_TaskCreate(&shellCB->shellTaskHandle, &initParam); }關于shell后續會詳細介紹,請持續關注。
前置條件了解清楚后,具體看任務是如何一步步創建的,如何和進程綁定,加入調度就緒隊列,還是繼續看源碼
LITE_OS_SEC_TEXT_INIT UINT32 LOS_TaskCreate(UINT32 *taskID, TSK_INIT_PARAM_S *initParam) { UINT32 ret; UINT32 intSave; LosTaskCB *taskCB = NULL; if (initParam == NULL) { return LOS_ERRNO_TSK_PTR_NULL; } if (OS_INT_ACTIVE) { return LOS_ERRNO_TSK_YIELD_IN_INT; } if (initParam->uwResved & OS_TASK_FLAG_IDLEFLAG) { initParam->processID = OsGetIdleProcessID(); } else if (OsProcessIsUserMode(OsCurrProcessGet())) { initParam->processID = OsGetKernelInitProcessID(); } else { initParam->processID = OsCurrProcessGet()->processID; } initParam->uwResved &= ~OS_TASK_FLAG_IDLEFLAG; initParam->uwResved &= ~OS_TASK_FLAG_PTHREAD_JOIN; if (initParam->uwResved & LOS_TASK_STATUS_DETACHED) { initParam->uwResved = OS_TASK_FLAG_DETACHED; } ret = LOS_TaskCreateOnly(taskID, initParam); if (ret != LOS_OK) { return ret; } taskCB = OS_TCB_FROM_TID(*taskID); SCHEDULER_LOCK(intSave); taskCB->taskStatus &= ~OS_TASK_STATUS_INIT; OS_TASK_SCHED_QUEUE_ENQUEUE(taskCB, 0); SCHEDULER_UNLOCK(intSave); /* in case created task not running on this core, schedule or not depends on other schedulers status. */ LOS_MpSchedule(OS_MP_CPU_ALL); if (OS_SCHEDULER_ACTIVE) { LOS_Schedule();//*kyf 任務創建完了 申請調度 } return LOS_OK; }對應張大爺的故事:就是節目單要怎么填,按格式來,從哪里開始演,要多大的空間,王場館好協調好現場的環境。這里注意在同一個節目單只要節目沒演完,王場館申請場地的空間就不能給別人用,這個場地空間對應的就是鴻蒙任務的棧空間,除非整個節目單都完了,就回收了。把整個場地干干凈凈的留給下一個人的節目單來表演。
至此的創建已經完成,已各就各位,源碼最后還申請了一次LOS_Schedule();因為鴻蒙的調度方式是搶占式的,如何本次task的任務優先級高于其他就緒隊列,那么接下來要執行的任務就是它了!
編輯:hfy
-
內核
+關注
關注
3文章
1363瀏覽量
40228 -
線程
+關注
關注
0文章
504瀏覽量
19651 -
鴻蒙系統
+關注
關注
183文章
2634瀏覽量
66213
發布評論請先 登錄
相關推薦
評論