Abstract: This article led everyone to learn the basic concepts of the task stack and task context of the Hongmeng light kernel, and analyzed the initialization code of the task stack.

This article is shared from the HUAWEI cloud community " light core M core source code analysis series of seven tasks and task scheduling (1) task stack ", the original author: zhushy.

We begin this article to analyze the task and task scheduling module. First, we introduce the basic concepts of the task stack. The task stack is a decreasing stack that grows from high addresses to low addresses, and the stack pointer points to the location of the element to be pushed onto the stack. After initialization, the unused stack space is initialized to the value 0xCACACACA represented by the macro OS_TASK_STACK_INIT, and the top of the stack is initialized to the value 0xCCCCCCCC represented by the macro OS_TASK_MAGIC_WORD. A schematic diagram of a task stack is as follows, where the bottom pointer of the stack is the largest memory address of the stack, the top pointer is the smallest memory address of the stack, and the stack pointer grows from the bottom of the stack to the top of the stack.
image.png

Task Context (Task Context) is another important concept of tasks and task scheduling modules. It refers to the environment in which tasks run, such as program counters, stack pointers, general registers, and so on. In multi-task scheduling, Task Context Switching belongs to the core content and is the basis for multiple tasks running on the same CPU core. During task scheduling, the register information used by the task exiting the running state is saved to the task stack, and the context information is also read from the stack of the task entering the running state to restore the register information.

Next, we analyze the source code of the task stack and task stack initialization. If the development board is involved, take the development board project targets\cortex-m7_nucleo_f767zi_gcc\ as an example for source code analysis. First, look at the task context structure.

1. TaskContext context structure definition

In the file kernel\arch\arm\cortex-m7\gcc\los_arch_context.h, the structure of the defined context is as follows, mainly floating-point registers and general-purpose registers.

typedef struct TagTskContext {
#if ((defined(__FPU_PRESENT) && (__FPU_PRESENT == 1U)) && \
     (defined(__FPU_USED) && (__FPU_USED == 1U)))
    UINT32 S16;
    UINT32 S17;
    UINT32 S18;
    UINT32 S19;
    UINT32 S20;
    UINT32 S21;
    UINT32 S22;
    UINT32 S23;
    UINT32 S24;
    UINT32 S25;
    UINT32 S26;
    UINT32 S27;
    UINT32 S28;
    UINT32 S29;
    UINT32 S30;
    UINT32 S31;
#endif
    UINT32 uwR4;
    UINT32 uwR5;
    UINT32 uwR6;
    UINT32 uwR7;
    UINT32 uwR8;
    UINT32 uwR9;
    UINT32 uwR10;
    UINT32 uwR11;
    UINT32 uwPriMask;
    UINT32 uwR0;
    UINT32 uwR1;
    UINT32 uwR2;
    UINT32 uwR3;
    UINT32 uwR12;
    UINT32 uwLR;
    UINT32 uwPC;
    UINT32 uwxPSR;
#if ((defined(__FPU_PRESENT) && (__FPU_PRESENT == 1U)) && \
     (defined(__FPU_USED) && (__FPU_USED == 1U)))
    UINT32 S0;
    UINT32 S1;
    UINT32 S2;
    UINT32 S3;
    UINT32 S4;
    UINT32 S5;
    UINT32 S6;
    UINT32 S7;
    UINT32 S8;
    UINT32 S9;
    UINT32 S10;
    UINT32 S11;
    UINT32 S12;
    UINT32 S13;
    UINT32 S14;
    UINT32 S15;
    UINT32 FPSCR;
    UINT32 NO_NAME;
#endif
} TaskContext;

2. Task stack related functions

2.1 Task stack initialization function

The task stack initialization function VOID *HalTskStackInit(t() is defined in the file kernel\arch\arm\cortex-m7\gcc\los_context.c. This function is used by the function UINT32 OsNewTaskInit() in the file kernel\src\los_task.c Call to complete the task initialization, and further call in the task creation function UINT32 LOS_TaskCreateOnly() to complete the task stack initialization of the newly created task.

This function uses 3 parameters, one is the task number UINT32 taskID, the other is the size of the initialized stack UINT32 stackSize, and the third parameter is the top pointer VOID *topStack. (1) The code initializes the stack content to OS_TASK_STACK_INIT, and (2) initializes the top of the stack to OS_TASK_MAGIC_WORD.

(3) The code obtains the pointer address TaskContext *context of the task context. For newly created tasks, starting from the bottom of the stack, the stack space of sizeof(TaskContext) stores context data. ⑷ If the floating-point number calculation is supported, the registers related to the floating-point number need to be initialized. ⑸Initialize general registers, among which .uwLR is initialized to (UINT32)(UINTPTR)HalSysExit. .uwPC is initialized to (UINT32)(UINTPTR)OsTaskEntry, which is the position of the first instruction executed when the CPU executes the task for the first time. These 2 functions will be analyzed below.

⑹ The return value is the pointer (VOID ) taskContext. This is the stack pointer after the task is initialized. Note that it does not start from the bottom of the stack. The bottom of the stack saves the context. The stack pointer must be subtracted from the stack size occupied by the context. In the stack, from increases, the first member and the second member of the context structure are sequentially saved... In addition, when the stack is initialized, except for a few special registers, the initial values of different registers are not What's the point, there are also some initialization rules. For example, the R2 register is initialized to 0x02020202L, and the R12 register is initialized to 0x12121212L. The initialized content is related to the register number, and the rest are similar.

LITE_OS_SEC_TEXT_INIT VOID *HalTskStackInit(UINT32 taskID, UINT32 stackSize, VOID *topStack)
{
    TaskContext *context = NULL;
    errno_t result;

    /* initialize the task stack, write magic num to stack top */
⑴  result = memset_s(topStack, stackSize, (INT32)(OS_TASK_STACK_INIT & 0xFF), stackSize);
    if (result != EOK) {
        printf("memset_s is failed:%s[%d]\r\n", __FUNCTION__, __LINE__);
    }
⑵  *((UINT32 *)(topStack)) = OS_TASK_MAGIC_WORD;

⑶  context = (TaskContext *)(((UINTPTR)topStack + stackSize) - sizeof(TaskContext));

#if ((defined(__FPU_PRESENT) && (__FPU_PRESENT == 1U)) && \
     (defined(__FPU_USED) && (__FPU_USED == 1U)))
⑷  context->S16 = 0xAA000010;
    context->S17 = 0xAA000011;
    context->S18 = 0xAA000012;
    context->S19 = 0xAA000013;
    context->S20 = 0xAA000014;
    context->S21 = 0xAA000015;
    context->S22 = 0xAA000016;
    context->S23 = 0xAA000017;
    context->S24 = 0xAA000018;
    context->S25 = 0xAA000019;
    context->S26 = 0xAA00001A;
    context->S27 = 0xAA00001B;
    context->S28 = 0xAA00001C;
    context->S29 = 0xAA00001D;
    context->S30 = 0xAA00001E;
    context->S31 = 0xAA00001F;
    context->S0 = 0xAA000000;
    context->S1 = 0xAA000001;
    context->S2 = 0xAA000002;
    context->S3 = 0xAA000003;
    context->S4 = 0xAA000004;
    context->S5 = 0xAA000005;
    context->S6 = 0xAA000006;
    context->S7 = 0xAA000007;
    context->S8 = 0xAA000008;
    context->S9 = 0xAA000009;
    context->S10 = 0xAA00000A;
    context->S11 = 0xAA00000B;
    context->S12 = 0xAA00000C;
    context->S13 = 0xAA00000D;
    context->S14 = 0xAA00000E;
    context->S15 = 0xAA00000F;
    context->FPSCR = 0x00000000;
    context->NO_NAME = 0xAA000011;
#endif

⑸  context->uwR4 = 0x04040404L;
    context->uwR5 = 0x05050505L;
    context->uwR6 = 0x06060606L;
    context->uwR7 = 0x07070707L;
    context->uwR8 = 0x08080808L;
    context->uwR9 = 0x09090909L;
    context->uwR10 = 0x10101010L;
    context->uwR11 = 0x11111111L;
    context->uwPriMask = 0;
    context->uwR0 = taskID;
    context->uwR1 = 0x01010101L;
    context->uwR2 = 0x02020202L;
    context->uwR3 = 0x03030303L;
    context->uwR12 = 0x12121212L;
    context->uwLR = (UINT32)(UINTPTR)HalSysExit;
    context->uwPC = (UINT32)(UINTPTR)OsTaskEntry;
    context->uwxPSR = 0x01000000L;

⑹  return (VOID *)context;
}

2.2 Get the task stack waterline function

As the task stack is pushed into and out of the stack, the size of the current stack is not necessarily the maximum value. UINT32 OsGetTaskWaterLine (UINT32 taskID) can obtain the maximum value of the stack used, which is the waterline WaterLine. This function is defined in the file kernel\src\los_task.c. It requires 1 parameter, namely the UINT32 taskID task number, and the return value UINT32 peakUsed represents the acquired watermark value, that is, the maximum value used by the task stack.

Let's take a look at the code in detail. The code at (1) indicates that if the top of the stack is equal to the set magic word, it means that the stack has not been destroyed by overflow. From the top of the stack, the stack content is filled with the macro OS_TASK_STACK_INIT is unused stack space. Use the temporary stack pointer stackPtr pointer variable to increase to the bottom of the stack in turn to determine whether the stack has been used, the while loop ends, and the stack pointer stackPtr points to the largest unused stack address. (2) The code gets the largest used stack space size, that is, the required waterline. (3) If the top of the stack overflows, the invalid value OS_NULL_INT is returned.

This function is called by the function LOS_TaskInfoGet(UINT32 taskId, TSK_INFO_S *taskInfo) in kernel\base\los_task.c to obtain task information. The incoming or stack information is also used in the shell module.

UINT32 OsStackWaterLineGet(const UINTPTR *stackBottom, const UINTPTR *stackTop, UINT32 *peakUsed)
{
    UINT32 size;
    const UINTPTR *tmp = NULL;
⑴  if (*stackTop == OS_STACK_MAGIC_WORD) {
        tmp = stackTop + 1;
        while ((tmp < stackBottom) && (*tmp == OS_STACK_INIT)) {
            tmp++;
        }
⑵      size = (UINT32)((UINTPTR)stackBottom - (UINTPTR)tmp);
        *peakUsed = (size == 0) ? size : (size + sizeof(CHAR *));
        return LOS_OK;
    } else {
        *peakUsed = OS_INVALID_WATERLINE;
        return LOS_NOK;
    }
}UINT32 OsGetTaskWaterLine(UINT32 taskID)
{
    UINT32 *stackPtr = NULL;
    UINT32 peakUsed;

⑴  if (*(UINT32 *)(UINTPTR)OS_TCB_FROM_TID(taskID)->topOfStack == OS_TASK_MAGIC_WORD) {
        stackPtr = (UINT32 *)(UINTPTR)(OS_TCB_FROM_TID(taskID)->topOfStack + OS_TASK_STACK_TOP_OFFSET);
        while ((stackPtr < (UINT32 *)(OS_TCB_FROM_TID(taskID)->stackPointer)) && (*stackPtr == OS_TASK_STACK_INIT)) {
            stackPtr += 1;
        }
⑵      peakUsed = OS_TCB_FROM_TID(taskID)->stackSize -
            ((UINT32)(UINTPTR)stackPtr - OS_TCB_FROM_TID(taskID)->topOfStack);
    } else {
⑶      PRINT_ERR("CURRENT task %s stack overflow!\n", OS_TCB_FROM_TID(taskID)->taskName);
        peakUsed = OS_NULL_INT;
    }
    return peakUsed;
}

3. The task enters and exits the function

3.1, task exit function

When the context is initialized, the function (UINT32) (UINTPTR) HalSysExit is set in the link register, which is defined in the file kernel\src\los_task.c. Call LOS_IntLock() in the function code to turn off the interrupt, and then enter an endless loop. During the normal scheduling of tasks, this function theoretically will not be executed. When the system is abnormal, the function will also be called when the LOS_Panic()c is actively called to trigger an abnormality.

LITE_OS_SEC_TEXT_MINOR VOID HalSysExit(VOID)
{
    LOS_IntLock();
    while (1) {
    }
}

3.2, task entry function

When the context is initialized, the PC register sets the function VOID OsTaskEntry (UINT32 taskId), which is defined in the file kernel\base\los_task.c, let’s analyze the source code, ⑴ get taskCB from the code, and then execute ⑵ to call the task The entry function. After the task is completed, execute (3) to delete the task. Usually the task entry execution function is a while loop. When the task is not executed, it will be scheduled to other tasks or idle tasks, and will not be executed to the delete task stage.

LITE_OS_SEC_TEXT_INIT VOID OsTaskEntry(UINT32 taskID)
{
    UINT32 retVal;
⑴  LosTaskCB *taskCB = OS_TCB_FROM_TID(taskID);

⑵  (VOID)taskCB->taskEntry(taskCB->arg);

⑶  retVal = LOS_TaskDelete(taskCB->taskID);
    if (retVal != LOS_OK) {
        PRINT_ERR("Delete Task[TID: %d] Failed!\n", taskCB->taskID);
    }
}

summary

This article led everyone to learn the basic concepts of the task stack and task context of the Hongmeng Light Core, and analyzed the initialization code of the task stack. More sharing articles will be launched in the follow-up, so stay tuned, and welcome everyone to share your experience of learning and using the light kernel. If you have any questions or suggestions, you can leave a message to us: 160c97040c6c7f https://gitee.com/openharmony /kernel_liteos_m/issues . light kernel code warehouse easier, it is recommended to visit 160c97040c6c81 https://gitee.com/openharmony/kernel_liteos_m , follow Watch, like Star, and Fork to your account, thank you.

Click to follow, and get to know the fresh technology of


华为云开发者联盟
1.4k 声望1.8k 粉丝

生于云,长于云,让开发者成为决定性力量