Abstract: light core task sorting list, used in business scenarios such as task delay expiration/overtime wake-up, is a very important and very basic data structure.

This article will continue to introduce readers to the important data structure in the Hongmeng light kernel source code: TaskSortLinkAttr. The task sorting list of Hongmeng Light Core is used in business scenarios such as task delay expiration/overtime wakeup. It is a very important and very basic data structure. The source code involved in this article, taking the OpenHarmony LiteOS-M kernel as an example, can be obtained from the open source site https://gitee.com/openharmony/kernel_liteos_m .

1 Task sorting list

Let's first look at the data structure of the task sorting link. The task sorting linked list is a circular doubly linked list array. The task sorting linked list attribute structure TaskSortLinkAttr, as the head node of the doubly linked list, points to the first element of the doubly linked list array. It also maintains cursor information and records the current position information. Let's first look at the definition of the structure of the sorted list attribute.

1.1 Definition of task sorting list attribute structure

In the kernel\include\los_task.h header file, the structure TaskSortLinkAttr of the sorting linked list attribute is defined. This structure defines the head node LOS_DL_LIST *sortLink of the sort linked list, the cursor UINT16 cursor, and a reserved field, which is not used for the time being.

The source code of

 typedef struct {
        LOS_DL_LIST *sortLink;
        UINT16      cursor;
        UINT16      reserved;
    } TaskSortLinkAttr;

In the file kernel\src\los_task.c, a global variable g_taskSortLink of the TaskSortLinkAttr type of the sorting linked list attribute structure is defined. The member variable sortLink of the global variable is used as the head node of the sorting linked list, pointing to a circular doubly linked list with a length of 32 Array, the member variable cursor is used as the cursor to record the current cursor position of the circular array. The source code is as follows.

​​​​​​​    LITE_OS_SEC_BSS  TaskSortLinkAttr                    g_taskSortLink;

Let's use a schematic diagram to describe it. The task sorting linked list is a circular doubly linked list array with a length of 32, each element is a doubly linked list, and the linked list node timerList of the task LosTaskCB is mounted. The member variable idxRollNum of the task LosTaskCB records the index and rolling number of the array. The member variable cursor of the global variable g_taskSortLink records the current cursor position. After each tick, the cursor points to the next position, and 32 ticks are required for one turn. When the array position is reached and the doubly linked list is not empty, the rolling number maintained by the first node is reduced by one. Such a data structure is similar to a clock dial and is also called a time wheel.

Let's take an example to illustrate how the task sequencing linked list based on the time wheel manages the task delay timeout. If the current cursor is 1, when a task needs to delay 72 ticks, 72=2*32+8, it means that the sort index is 8 and the scroll number rollNum is 2. The task will be inserted into the position of the doubly linked list with the array index of sortIndex+cursor=9, and the scroll of the doubly linked list maintenance node at 9 will be 2. As the Tick time progresses, it runs from the current cursor position to the array index position 9, which lasts 8 ticks. When running to 9, if the scrolling number is greater than 0, the scrolling number is reduced by 1. After running for 2 more rounds, a total of 72 ticks are required, and the task will expire later and can be removed from the sorted list. The scrolling number of the first linked list node of the doubly linked list corresponding to each array element indicates how many rounds need to be turned before the node task expires. The rolling number of the second linked list node needs to be added to the rolling number of the first node, indicating the number of rounds that the second node needs to turn. And so on.

The schematic diagram is as follows:
image.png

1.2 Task sorting linked list macro definition

In the OS_TSK_SORTLINK_LEN header file, some macro definitions related to the task sorting list are defined. The length of the doubly linked list of delayed tasks is defined as 32, the number of high-order bits is 5, and the number of low-order bits is 27. For the timeout period of the task, the high 27 bits are used as the scrolling number, and the low 5 bits are used as the array index.

The source code of

​​​​​​​

  /**
        * 延迟任务双向链表数组的数量(桶的数量):32
        */
        #define OS_TSK_SORTLINK_LEN                         32
     
        /**
        * 高阶bit位数目:5
        */
        #define OS_TSK_HIGH_BITS                            5U
     
        /**
        * 低阶bit位数目:27
        */
        #define OS_TSK_LOW_BITS                             (32U - OS_TSK_HIGH_BITS)
     
        /**
        * 滚动数最大值:0xFFFF FFDF,1111 0111 1111 1111 1111 1111 1101 1111
        */
        #define OS_TSK_MAX_ROLLNUM                          (0xFFFFFFFFU - OS_TSK_SORTLINK_LEN)
     
        /**
        * 任务延迟时间数的位宽:5
        */
        #define OS_TSK_SORTLINK_LOGLEN                      5
     
        /**
        * 延迟任务的桶编号的掩码:31、0001 1111
        */
        #define OS_TSK_SORTLINK_MASK                        (OS_TSK_SORTLINK_LEN - 1U)
     
        /**
        * 滚动数的高阶掩码:1111 1000 0000 0000 0000 0000 0000 0000
        */
        #define OS_TSK_HIGH_BITS_MASK                       (OS_TSK_SORTLINK_MASK << OS_TSK_LOW_BITS)
     
        /**
        * 滚动数的低阶掩码:0000 0111 1111 1111 1111 1111 1111 1111
        */
        #define OS_TSK_LOW_BITS_MASK                        (~OS_TSK_HIGH_BITS_MASK)

2 Task sorting linked list operation

We analyze the operations of the task sorting list, including initialization, insertion, deletion, rolling number update, and obtaining the next expiration time.

2.1 Initialize the sorted list

In the initialization phase of the system kernel, the task sequence list is initialized in the function UINT32 OsTaskInit (VOID). The calling relationship of this function is as follows, main.c:main() --> kernel\src\los_init.c:LOS_KernelInit() --> kernel\src\los_task.c:OsTaskInit().

The source code of the initialization sort list function is as follows:

​​​​​​​

 LITE_OS_SEC_TEXT_INIT UINT32 OsTaskInit(VOID)
    {
        UINT32 size;
        UINT32 index;
        LOS_DL_LIST *listObject = NULL;
        ......
    ⑴  size = sizeof(LOS_DL_LIST) * OS_TSK_SORTLINK_LEN;
        listObject = (LOS_DL_LIST *)LOS_MemAlloc(m_aucSysMem0, size);
    ⑵  if (listObject == NULL) {
            (VOID)LOS_MemFree(m_aucSysMem0, g_taskCBArray);
            return LOS_ERRNO_TSK_NO_MEMORY;
        }
     
    ⑶  (VOID)memset_s((VOID *)listObject, size, 0, size);
    ⑷  g_taskSortLink.sortLink = listObject;
        g_taskSortLink.cursor = 0;
        for (index = 0; index < OS_TSK_SORTLINK_LEN; index++, listObject++) {
    ⑸      LOS_ListInit(listObject);
        }
     
        return LOS_OK;
    }
     

(1) The code calculates the memory size of the doubly linked list that needs to be applied for, OS_TSK_SORTLINK_LEN is 32, that is, it needs to apply for memory space for 32 doubly linked list nodes. Then apply for memory, (2) when the application for memory fails, the corresponding error code will be returned. (3) The memory area applied for initializing is 0, etc. (4) Assign the requested doubly linked list node to the linked list node.sortLink of g_taskSortLink, as the head node of the sorted linked list, and the cursor.cursor is initialized to 0. Then in the loop at ⑸, call the LOS_ListInit() function to initialize each element of the doubly linked list array as a doubly cyclically linked list.

2.2 Insert a sorted list

The function to insert the sorted linked list is OsTaskAdd2TimerList(). When a task is waiting for resources such as a mutex lock/semaphore, it is necessary to call this function to add the task to the corresponding sorted list. This function contains two input parameters, the first parameter LosTaskCB *taskCB is used to specify the task to be delayed, and the second parameter UINT32 timeout specifies the timeout waiting time.

The source code of

​​​​​​​

 LITE_OS_SEC_TEXT VOID OsTaskAdd2TimerList(LosTaskCB *taskCB, UINT32 timeout)
    {
        LosTaskCB *taskDelay = NULL;
        LOS_DL_LIST *listObject = NULL;
        UINT32 sortIndex;
        UINT32 rollNum;
     
    ⑴  sortIndex = timeout & OS_TSK_SORTLINK_MASK;
        rollNum = (timeout >> OS_TSK_SORTLINK_LOGLEN);
    ⑵  (sortIndex > 0) ? 0 : (rollNum--);
    ⑶  EVALUATE_L(taskCB->idxRollNum, rollNum);
    ⑷  sortIndex = (sortIndex + g_taskSortLink.cursor);
        sortIndex = sortIndex & OS_TSK_SORTLINK_MASK;
    ⑸  EVALUATE_H(taskCB->idxRollNum, sortIndex);
    ⑹  listObject = g_taskSortLink.sortLink + sortIndex;
    ⑺  if (listObject->pstNext == listObject) {
            LOS_ListTailInsert(listObject, &taskCB->timerList);
        } else {
    ⑻      taskDelay = LOS_DL_LIST_ENTRY((listObject)->pstNext, LosTaskCB, timerList);
            do {
    ⑼          if (UWROLLNUM(taskDelay->idxRollNum) <= UWROLLNUM(taskCB->idxRollNum)) {
                    UWROLLNUMSUB(taskCB->idxRollNum, taskDelay->idxRollNum);
                } else {
    ⑽              UWROLLNUMSUB(taskDelay->idxRollNum, taskCB->idxRollNum);
                    break;
                }
     
    ⑾          taskDelay = LOS_DL_LIST_ENTRY(taskDelay->timerList.pstNext, LosTaskCB, timerList);
            } while (&taskDelay->timerList != (listObject));
     
    ⑿      LOS_ListTailInsert(&taskDelay->timerList, &taskCB->timerList);
        }
    }

⑴ The code calculates the low 5 bits of the waiting time timeout as the array index, and the high 27 bits as the rolling number rollNum. The mathematical meaning of these two lines of code is to use the quotient obtained when the waiting time is at 32 as the rolling number, and the remainder as the array index. ⑵ In the code, if the remainder is 0, when it can be divided evenly, the rolling number is subtracted by 1. The reason for the minus 1 design is that in the function VOID OsTaskScan (VOID), when each tick comes, if the scroll number is greater than 0, the scroll number is reduced by 1, and the scroll continues for one lap. The function VOID OsTaskScan(VOID) will be analyzed later.

(3) The code assigns the rolling number to the lower 27 bits of the task taskCB->idxRollNum. ⑷ Add the index of the array to the cursor, and then execute ⑸ Assign value to the high 5 bits of task CB->idxRollNum. ⑹Acquire the head node of the doubly-linked list according to the array index, ⑺If the doubly-linked list here is empty, insert it directly into the list. If the linked list is not empty, execute ⑻ to obtain the task taskDelay corresponding to the first linked list node, and then traverse the circular doubly linked list to insert the task into the appropriate position. (9) If the scroll number of taskCB of the task to be inserted is greater than or equal to the scroll number of the task corresponding to the current linked list node, subtract the scroll number of the task corresponding to the current linked list node from the scroll number of the task to be inserted taskCB, and then execute ⑾ to obtain the next node to continue Traverse. ⑽ If the scroll number of taskCB of the task to be inserted is less than the scroll number of the task corresponding to the current linked list node, the scroll number of the task to be inserted taskCB is subtracted from the scroll number of the task corresponding to the current linked list node, and then the loop is out of the loop. Execute ⑿ to complete the task insertion. The insertion process can be understood in conjunction with the schematic diagram above.

2.3 Delete from the sorted list

The function deleted from the sorted linked list is VOID OsTimerListDelete (LosTaskCB taskCB). In scenarios such as task recovery/delete, you need to call this function to delete the task from the task list. This function contains a parameter LosTaskCB taskCB, which is used to specify the task to be deleted from the sorted list.

The source code of

​​​​​​​

 LITE_OS_SEC_TEXT VOID OsTimerListDelete(LosTaskCB *taskCB)
    {
        LOS_DL_LIST  *listObject = NULL;
        LosTaskCB  *nextTask = NULL;
        UINT32 sortIndex;
     
    ⑴  sortIndex = UWSORTINDEX(taskCB->idxRollNum);
    ⑵  listObject = g_taskSortLink.sortLink + sortIndex;
     
    ⑶  if (listObject != taskCB->timerList.pstNext) {
    ⑷      nextTask = LOS_DL_LIST_ENTRY(taskCB->timerList.pstNext, LosTaskCB, timerList);
            UWROLLNUMADD(nextTask->idxRollNum, taskCB->idxRollNum);
        }
     
    ⑸  LOS_ListDelete(&taskCB->timerList);
    }

(1) The code obtains the numerical index corresponding to the task to be deleted from the sorted list. (2) The code gets the head node listObject of the sorted linked list. The code at ⑶ judges whether the node to be deleted is the last node, if it is not the last node, execute the code at ⑷ to obtain the next task corresponding to the next node of the node to be deleted, and add the scroll of the node to be deleted to the scroll number of the next node. Count, and then execute the code at ⑸ to execute the delete operation. If it is the last node, simply execute the code at ⑸ to delete the node.

2.4 Get the next timeout expiration time

The function to get the next timeout expiration time is OsTaskNextSwitchTimeGet(), we analyze its code.

The source code of

​​​​​​​

    UINT32 OsTaskNextSwitchTimeGet(VOID)
    {
        LosTaskCB *taskCB = NULL;
        UINT32 taskSortLinkTick = LOS_WAIT_FOREVER;
        LOS_DL_LIST *listObject = NULL;
        UINT32 tempTicks;
        UINT32 index;
     
    ⑴  for (index = 0; index < OS_TSK_SORTLINK_LEN; index++) {
    ⑵      listObject = g_taskSortLink.sortLink + ((g_taskSortLink.cursor + index) % OS_TSK_SORTLINK_LEN);
    ⑶      if (!LOS_ListEmpty(listObject)) {
    ⑷          taskCB = LOS_DL_LIST_ENTRY((listObject)->pstNext, LosTaskCB, timerList);
    ⑸          tempTicks = (index == 0) ? OS_TSK_SORTLINK_LEN : index;
    ⑹          tempTicks += (UINT32)(UWROLLNUM((UINT32)taskCB->idxRollNum) * OS_TSK_SORTLINK_LEN);
    ⑺          if (taskSortLinkTick > tempTicks) {
                    taskSortLinkTick = tempTicks;
                }
            }
        }
        return taskSortLinkTick;
    }

(1) The code loops through the doubly linked list array, and (2) the code starts from the current cursor position to get the head node listObject of the sorted linked list. (3) The code judges whether the sorted list is empty, and if the sorted list is empty, it continues to traverse the next array. If the linked list is not empty, the code at ⑷ gets the task corresponding to the first linked list node of the sorted linked list. (5) If the number index to be traversed is 0, the number of ticks is 32, otherwise, the specific number index is used. ⑹ Get the scroll number of the task, calculate the waiting time required, and add the time calculated at ⑸ that is less than one lap. ⑺ Calculate the minimum time to wait, that is, the next fastest expiration time.

3 The relationship between sorted linked list and Tick time

After a task is added to the sorted list, and one tick of time elapses, how should the scrolling number in the sorted list be updated?

Every time a tick passes, the system will call the Tick interrupt processing function OsTickHandler(), which is implemented in the kernel\src\los_tick.c file. The following is a code snippet of the function, (1) the timeout and expiration of the respective tasks in the code.

​​​​​​​

   LITE_OS_SEC_TEXT VOID OsTickHandler(VOID)
    {
    #if (LOSCFG_BASE_CORE_TICK_HW_TIME == 1)
        platform_tick_handler();
    #endif
     
        g_ullTickCount++;
     
    #if (LOSCFG_BASE_CORE_TIMESLICE == 1)
        OsTimesliceCheck();
    #endif
     
    ⑴  OsTaskScan();  // task timeout scan
     
    #if (LOSCFG_BASE_CORE_SWTMR == 1)
        (VOID)OsSwtmrScan();
    #endif
    }

Analyze the function OsTaskScan() in detail to understand the relationship between the sorted list and the tick time. The function is implemented in the kernel\base\los_task.c file, the code snippet is as follows:

​​​​​​​

    LITE_OS_SEC_TEXT VOID OsTaskScan(VOID)
    {
        LosTaskCB *taskCB = NULL;
        BOOL needSchedule = FALSE;
        LOS_DL_LIST *listObject = NULL;
        UINT16 tempStatus;
        UINTPTR intSave;
        intSave = LOS_IntLock();
     
    ⑴  g_taskSortLink.cursor = (g_taskSortLink.cursor + 1) % OS_TSK_SORTLINK_LEN;
        listObject = g_taskSortLink.sortLink + g_taskSortLink.cursor;
    ⑵  if (listObject->pstNext == listObject) {
            LOS_IntRestore(intSave);
            return;
        }
     
    ⑶  for (taskCB = LOS_DL_LIST_ENTRY((listObject)->pstNext, LosTaskCB, timerList);
             &taskCB->timerList != (listObject);) {
            tempStatus = taskCB->taskStatus;
    ⑷      if (UWROLLNUM(taskCB->idxRollNum) > 0) {
                UWROLLNUMDEC(taskCB->idxRollNum);
                break;
            }
     
    ⑸      LOS_ListDelete(&taskCB->timerList);
    ⑹      if (tempStatus & OS_TASK_STATUS_PEND) {
                taskCB->taskStatus &= ~(OS_TASK_STATUS_PEND);
                LOS_ListDelete(&taskCB->pendList);
                taskCB->taskSem = NULL;
                taskCB->taskMux = NULL;
            }
    ⑺      else if (tempStatus & OS_TASK_STATUS_EVENT) {
                taskCB->taskStatus &= ~(OS_TASK_STATUS_EVENT);
            }
    ⑻      else if (tempStatus & OS_TASK_STATUS_PEND_QUEUE) {
                LOS_ListDelete(&taskCB->pendList);
                taskCB->taskStatus &= ~(OS_TASK_STATUS_PEND_QUEUE);
            } else {
                taskCB->taskStatus &= ~(OS_TASK_STATUS_DELAY);
            }
     
    ⑼      if (!(tempStatus & OS_TASK_STATUS_SUSPEND)) {
                taskCB->taskStatus |= OS_TASK_STATUS_READY;
                OsHookCall(LOS_HOOK_TYPE_MOVEDTASKTOREADYSTATE, taskCB);
                OsPriqueueEnqueue(&taskCB->pendList, taskCB->priority);
                needSchedule = TRUE;
            }
     
            if (listObject->pstNext == listObject) {
                break;
            }
     
            taskCB = LOS_DL_LIST_ENTRY(listObject->pstNext, LosTaskCB, timerList);
        }
     
        LOS_IntRestore(intSave);
     
    ⑽  if (needSchedule) {
            LOS_Schedule();
        }
    }

(1) The code updates the cursor of the global variable g_taskSortLink to point to the next position of the doubly linked list array, and then obtains the doubly linked list head node listObject at that position. ⑵ If the linked list is empty, return. If the doubly linked list is not empty, execute (3) loop to traverse each linked list node. (4) If the scrolling number of the linked list node is greater than 0, the scrolling number is reduced by 1, indicating that the task needs to wait for another round. If the scroll number of the linked list node is equal to 0, it means that the task has expired over time, and execute ⑸ to delete from the sorted linked list. Next, it needs to be processed separately according to the task status. If the code is in a blocked state, cancel the blocked state and delete it from the blocked list. ⑺ If the task is blocked in the event, cancel the blocking state. ⑻If the task is blocked in the queue, delete it from the blocked list and cancel the blocking state. If it is not in the above state, cancel the delayed state OS_TASK_STATUS_DELAY. ⑼ If the code is in the suspended state, set the task to the ready state, join the task ready queue, and set the flag that requires rescheduling. ⑽If the settings need to be rescheduled, call the scheduling function to trigger task scheduling.

summary

Mastering the important data structure of the Hongmeng Light Kernel Sorting Link List TaskSortLinkAttr will lay the foundation for further study and analysis of the Hongmeng Light Kernel source code, making subsequent learning easier. There will be more sharing articles in the follow-up, so stay tuned. You are also welcome 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: 160b9a54a57368 https://gitee.com/openharmony /kernel_liteos_m/issues . light kernel code warehouse easier, it is recommended to visit 160b9a54a5736b 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 粉丝

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