Note: Based on core open source obscurity analysis, the official source [ kernel_liteos_a ] official documents [ docs ] Reference Document [ Huawei LiteOS ]
author: Hong Meng core enthusiasts, will continue to study obscurity kernel, update blog, so stay tuned. The content only represents personal views, errors are welcome, everyone is welcome to correct and improve. View all articles in this series into the obscurity system source code analysis (List)
This article analyzes the source code of Task/thread management in detail: los_task.c
table of Contents
1. How does the official document describe threads
3. What does the task look like?
2. What's the matter with the ready queue
3. What's the matter with the task stack
1. Usage scenarios and functions
2. The process of creating tasks
3. What are the important contents of the Task/Thread part that have not been mentioned?
For more information, click Hongmeng System Source Code Analysis (General Catalog)
Preface
In the Hongmeng kernel, a task can be understood as a thread in a broad sense
1. How to understand Task
1. How does the official document describe threads
Basic Concepts
From a system perspective, a thread is the smallest unit of operation that competes for system resources. Threads can use or wait for system resources such as CPU and memory space, and run independently of other threads.
The threads in each process of Hongmeng Kernel run independently and are scheduled independently, and the scheduling of threads in the current process is not affected by other threads in the process.
The threads in the Hongmeng kernel adopt a preemptive scheduling mechanism, and support time slice round-robin scheduling and FIFO scheduling.
The threads of the Hongmeng kernel have a total of 32 priorities (0-31), the highest priority is 0, and the lowest priority is 31.
The high-priority thread in the current process can preempt the low-priority thread in the current process, and the low-priority thread in the current process can only be scheduled after the high-priority thread in the current process is blocked or terminated.
Thread status description:
Initialization (Init): The thread is being created.
Ready (Ready): The thread is in the ready list, waiting for CPU scheduling.
Running: The thread is running.
Blocked: The thread is blocked and suspended. Blocked states include: pend (because of locks, events, semaphores, etc.), suspend (active pend), delay (delayed blocking), pendtime (because of locks, events, semaphore time, etc. overtime waiting).
Exit (Exit): The thread ends, waiting for the parent thread to reclaim its control block resources.
Figure 1 Schematic diagram of thread state migration.
Note that the official document talks about threads and does not mention tasks. However, there are a lot of task codes in the kernel source code and very few thread codes. What is going on?
In fact, in the Hongmeng kernel, tasks are threads. Beginners can understand them in this way, but there are still differences between the two. Otherwise, why should they be described in two words.
What is the difference? It is the difference in management. Task is the concept of scheduling level, and thread is the concept of process level. Just as the same person has different identities in different management systems, a man can be a child, a father, a husband, or a programmer, with different perspectives and functions.
How to prove it is one thing, continue to look down.
2. Execute the task command
The execution result of the Hongmeng task command:
The task command finds out the running status of each task in the life cycle, its running memory space, priority, time slice, entry execution function, process ID, status and other information, which is very complicated. Such complex information needs a structure to carry. And this structure is LosTaskCB (task control block)
3. What does the task look like?
Before talking about LosTaskCB, let's talk about the define corresponding to the task state of the official document. It can be seen that task and thread are the same thing.
#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
What does LosTaskCB look like? Sorry, it is a bit long, but I still have to post the full picture.
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;
The structure of LosTaskCB has a lot of content, what do they mean?
LosTaskCB is equivalent to the ID of the task in the kernel, which reflects the operation of each task in the life cycle. Since it is a cycle, there is a state, and it needs memory space to run, and it needs to be scheduled by the kernel algorithm. The selected CPU executes the code segment instructions. If the CPU wants to execute, it needs to tell it where to start execution, because it is multithreaded, but Only one CPU needs to constantly switch tasks, then the execution will be interrupted, and the execution needs to be resumed. How to ensure that the resumed task execution will not go wrong? These problems need to be clarified.
Second, how to manage Task
1. What is a task pool?
As mentioned above, tasks are the concept of kernel scheduling level. The scheduling algorithm ensures the orderly execution of tasks. There will be detailed chapters on scheduling algorithms in the follow-up.
How to manage and execute so many tasks? Management relies on task pools and ready queues, and execution relies on scheduling algorithms.
The code is as follows (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 is a task pool, 128 tasks are created by default, resident in memory, and not released.
g_losFreeTask is a linked list of idle tasks. When you want to create a task, come here to apply for a free task. When it is used up, it will be recycled and continue to be used for subsequent applications.
g_taskRecyleList is a list of recycling tasks, dedicated to recycling exit tasks. The resources occupied by tasks are completely deleted after they are confirmed and returned. Just like employee resignation, there must be a resignation queue and process. The computer and mailbox must be returned. And other operations.
2. What's the matter with the ready queue
The CPU execution speed is very fast. The default time slice of the Hongmeng kernel is 10ms. The resources are limited and need to switch back and forth among many tasks. Therefore, the CPU must not be allowed to wait for tasks. The CPU is like the company's biggest leader, many departments below, etc. The leaders come to approve and eat. Only everyone is waiting for the leader, there is no reason for the leader to wait for you, so the work must be prepared in advance, and the priority of each department is different, so each department must have a task queue, which is placed in the leader can directly handle Tasks, don't put them in if you are not ready, because this is food prepared in advance for the CPU!
This is the principle of the ready queue. There are 32 ready queues, both processes and threads. Because the thread priority is 32 by default, each queue puts tasks of the same priority.
Let's see the source code .
#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;
}
Pay attention to the memory allocation of g_priQueueList, which is 32 LOS_DL_LIST. Do you remember the magical effect of LOS_DL_LIST? Go to Hongmeng System Source Code Analysis (General Catalog) if you are not sure .
3. What's the matter with the task stack
Each task is opened independently, and the tasks are also independent of each other. The communication between them is through IPC. The "independent" here means that each task has its own operating environment-stack space, called task stack, stack The information stored in the space includes local variables, registers, function parameters, function return addresses, etc.
However, there is only one CPU in the system, and the tasks are independent. The essence of scheduling is that the CPU executes a new task, and where is the old task interrupted? It's not clear, it's random. So how to ensure that the old task can continue to play from the place where it was interrupted last time when it is scheduled again?
The answer is: task context, there are a bunch of registers in the CPU. The essence of CPU operation is that the values of these registers are constantly changing. As long as these values are saved when switching, and then restored, the continuous execution of the task can be guaranteed, leaving the user without Perception.
This is the same as when we played peekaboo games when we were young. We were interrupted by our parents halfway through the game and called back to eat. We continued to play in the afternoon. Zhang San, you should go back to the tree and hide, Li Si continued to hide under the bed, Wang The fifth is to catch Zhang San and Li Si continue to catch, first restore the scene, and then continue to play. This records everyone's location, and the role information is the task context. In fact, a task is to be repeatedly interrupted. The Hongmeng kernel gives a task execution time of 20ms, which means that in the case of multi-task competition, it has to switch back and forth 50 times in one second at most.
What is the task context (TaskContext)? Still look at the source code directly
/* 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;
It is found that it is basically the recovery field value of the CPU register. You can check the specific functions of each register on the Internet, and there will be a special article to introduce it later. Here are three of the registers SP, LR, PC
LR
has two purposes. The first is to save the return address of the subroutine. When a jump instruction such as BL, BX, BLX, etc. is called, the return address is automatically saved to LR; the second is to save the exception return address when an exception occurs.
PC (Program Counter)
is the program counter, used to save the execution address of the program. In ARM's three-stage pipeline architecture, the program pipeline includes three stages: addressing, decoding, and execution. PC points to the program address of the current address , So in 32-bit ARM, the decoding address (the program being parsed and not yet executed) is PC-4, and the execution address (the address of the program currently being executed) is PC-8. When an interruption occurs suddenly, the PC is saved the address of.
Each exception mode of SP has its own independent r13, which usually points to the stack dedicated to the exception mode. When ARM enters the exception mode, the program can push general-purpose registers onto the stack, and then pop the stack when it returns. The completeness of the state of the program under various modes.
4. Task stack initialization
The initialization of the task stack is the initialization of the task context, because the task has not started to execute, there will be no other content except the context. Note that the context is stored at the bottom of the stack.
Three, Task function set
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. Usage scenarios and functions
After the task is created, the kernel can perform operations such as locking task scheduling, unlocking task scheduling, suspending, resuming, and delaying. At the same time, it can also set task priority and obtain task priority. When the task ends, the current task self-delete operation is performed.
The task management module in the Huawei LiteOS system provides users with the following functions.
Function classification | Interface name | description |
---|---|---|
Task creation and deletion | LOS_TaskCreateOnly | Create a task and make the task enter the suspend state without scheduling. |
LOS_TaskCreate | Create a task, make the task enter the ready state, and schedule it. | |
LOS_TaskDelete | Delete the specified task. | |
Task status control | LOS_TaskResume | Resume suspended tasks. |
LOS_TaskSuspend | Suspend the specified task. | |
LOS_TaskDelay | The task is delayed. | |
LOS_TaskYield | Explicit decentralization, adjust the task scheduling order of the specified priority. | |
Task scheduling control | LOS_TaskLock | Lock task scheduling. |
LOS_TaskUnlock | Unlock task scheduling. | |
Task priority control | LOS_CurTaskPriSet | Set the priority of the current task. |
LOS_TaskPriSet | Set the priority of the specified task. | |
LOS_TaskPriGet | Get the priority of the specified task. | |
Task information acquisition | LOS_CurTaskIDGet | Get the ID of the current task. |
LOS_TaskInfoGet | Set the priority of the specified task. | |
LOS_TaskPriGet | Get information about the specified task. | |
LOS_TaskStatusGet | Get the status of the specified task. | |
LOS_TaskNameGet | Get the name of the specified task. | |
LOS_TaskInfoMonitor | Monitor all tasks and obtain information about all tasks. | |
LOS_NextTaskIDGet | Get the ID of the task to be scheduled. |
2. The process of creating tasks
Before creating a task, learn about another structure 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;
These initialization parameters are the exposed initial parameters of the task and pfnTaskEntry 对java来说就是你new进程的run(),
need to be provided by the upper user.
Let's look at an example: type the ping command in the shell to see the process of its creation
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\n");
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;
}
It is found that the scheduling priority of ping is 8, which is higher than that of shell. What is the priority of shell? The answer is: see the source code is 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);
}
Please continue to pay attention to the detailed introduction of shell in the follow-up.
After understanding the preconditions, it depends on how the task is created step by step, how to bind to the process, join the scheduling ready queue, or continue to look at the source code
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;
}
So far, the creation has been completed, and they are all in place. The source code finally applied for LOS_Schedule(); because Hongmeng's scheduling method is preemptive, how the task priority of this task is higher than other ready queues, then the task to be executed next That's it!
3. What are the important contents of the Task/Thread part that have not been mentioned?
How the memory is allocated, how to communicate between threads, and how the stack is executed during operation. After the follow-up talk about memory, the IPC part will be disassembled for analysis.
For more information, click Hongmeng System Source Code Analysis (General Catalog)