LiteOS-任務篇-原始碼分析-任務排程函式

李柱明發表於2020-10-13


前言

  • 20201012
  • LiteOS 2018
  • 建議先瞄一眼 輔助參考程式碼 章節

筆錄草稿

核心原始碼分析

  • 這裡主要分析系統排程的彙編部分,也是排程的底層核心部分。

osTaskSchedule函式原始碼分析

  • osTaskSchedule 原始碼 ( 位於檔案 los_dispatch_keil.S
    • 往暫存器 OS_NVIC_INT_CTRL 中寫入 OS_NVIC_PENDSVSET

      • OS_NVIC_INT_CTRL 為 Interrupt Control State Register,該暫存器可配置內容如下
        • set a pending Non-Maskable Interrupt (NMI)
        • set or clear a pending SVC
        • set or clear a pending SysTick
        • check for pending exceptions
        • check the vector number of the highest priority pended exception
        • check the vector number of the active exception.
      • 設定如圖,觸發 PendSV 中斷
    • 退出 osTaskSchedule 函式,即是返回上層函式

osTaskSchedule
    LDR     R0, =OS_NVIC_INT_CTRL
    LDR     R1, =OS_NVIC_PENDSVSET
    STR     R1, [R0]
    BX      LR
  • OS_NVIC_INT_CTRL 定義 ( 位於檔案 los_hwi.h
/**
 * @ingroup los_hwi
 * Interrupt control and status register.
 */
#define OS_NVIC_INT_CTRL            0xE000ED04
  • OS_NVIC_INT_CTRL 定義 ( 位於檔案 los_dispatch_keil.S
OS_NVIC_PENDSVSET           EQU     0x10000000

osPendSV函式原始碼分析

  • PendSV 中斷的回撥函式就是 void osPendSV(void);
  • osPendSV 原始碼 ( 位於檔案 los_dispatch_keil.S
    • 讀取 PRIMASK 的值到 R12 中,即是儲存中斷狀態
    • 遮蔽全域性中斷
    • 判斷是否呼叫 TaskSwitch 函式
      • 如果巨集 LOSCFG_BASE_CORE_TSK_MONITORNO,則執行 TaskSwitch 函式
      • 如果巨集 LOSCFG_BASE_CORE_TSK_MONITORYES,則在下面執行 osTaskSwitchCheck 函式
        • 壓棧保護暫存器 R12LR
        • 執行 R2 函式,也就是 osTaskSwitchCheck 函式
          • 原始碼解析路徑:LOS_KernelInit() --> osTaskMonInit() --> g_pfnTskSwitchHook = osTaskSwitchCheck;
          • 讀者可以自己追蹤一下
        • 恢復 R12LR
osPendSV
    MRS     R12, PRIMASK
    CPSID   I

    LDR     R2, =g_pfnTskSwitchHook    ; C: R2 = &g_pfnTskSwitchHook;
    LDR     R2, [R2]    ; C: R2 = *R2; ==》 R2 = g_pfnTskSwitchHook;
    CBZ     R2, TaskSwitch    ; C: if(g_pfnTskSwitchHook == 0) TaskSwitch();
    PUSH    {R12, LR}    ; 將 R12 和 LR 暫存器壓棧
    BLX     R2    ; 跳到 R2
    POP     {R12, LR}    ; 出棧到暫存器 R12 和 LR
  • PRIMASK 說明
    • 這是個只有單一位元的暫存器
    • 被置 1 後,就關掉所有可遮蔽的異常,只剩下 NMI硬fault 可以響應
    • 預設值是0,表示沒有關中斷。
  • 指令 CBZ
    • 比較 為 0 則跳轉,如:CBZ x1,fun ; 表示如果 x10,則跳轉到 fun
  • 語句 PUSH {R12, LR}POP {R12, LR}個人理解,望指正
    • {} 內先排序,根據暫存器 PS 的走向排序,最終目標是,下面那點
    • 小端模式:低編號暫存器對應低地址
    • PUSH {R12, LR}
      • 順序:LR R12
      • 壓棧:先壓 LR,PS -= 4
      • 壓棧:再壓 R12,PS -= 4
    • PUSH {R12, LR}
      • 順序:R12 LR
      • 壓棧:先出 R12,PS += 4
      • 壓棧:再壓 LR,PS += 4

TaskSwitch函式原始碼分析

  • 如果使用者沒有開啟任務堆疊監測,即巨集 LOSCFG_BASE_CORE_TSK_MONITOR 配置為 NO,就執行本函式。
  • PSP 更新給 R0
  • 手動把 R4-R12 壓棧
    • R0-R3,R12,LR,PC,xPSR 這些暫存器已經自動壓棧了
  • 更新當前執行任務棧 g_stLosTask.pstRunTask.pStackPointer 指標
  • 更新當前任務狀態,取消 OS_TASK_STATUS_RUNNING 執行態
    • 先獲取當前任務的狀態暫存器
    • 再取消 OS_TASK_STATUS_RUNNING 執行態
    • 最後再賦值回 g_stLosTask.pstRunTask.usTaskStatus
  • 更新當前執行任務變數 *g_stLosTask.pstRunTask = *g_stLosTask.pstNewTask;
  • 更新準備執行的任務的狀態,更新執行態 OS_TASK_STATUS_RUNNING注:此時*g_stLosTask.pstRunTask *g_stLosTask.pstNewTask 是一樣的,指向同一個任務
  • 準備執行的任務手動出棧
  • 更新準備執行的任務的 PSP 值
  • 恢復原有的中斷狀態
  • 返回到上層函式中,如 osScheduleLOS_Schedule 函式中
TaskSwitch
    MRS     R0, PSP    ;// R0 = PSP;

    STMFD   R0!, {R4-R12}    ;// 手動壓棧,先減再壓,小端,且棧往下生長

    LDR     R5, =g_stLosTask    ;//  R5 = g_stLosTask; ==> R5 = g_stLosTask.pstRunTask;
    LDR     R6, [R5]     ;// R6 = *(g_stLosTask.pstRunTask); ==> R6 = g_stLosTask.pstRunTask.pStackPointer;
    STR     R0, [R6]    ;// *(g_stLosTask.pstRunTask.pStackPointer) = R0;


    LDRH    R7, [R6 , #4]    ;// R7 = *(&(g_stLosTask.pstRunTask.usTaskStatus)); ==> R7 = g_stLosTask.pstRunTask.usTaskStatus;
    MOV     R8,#OS_TASK_STATUS_RUNNING    ;// R8 = OS_TASK_STATUS_RUNNING;
    BIC     R7, R7, R8    ;// R7 &= ~R8;
    STRH    R7, [R6 , #4]    ;// g_stLosTask.pstRunTask.usTaskStatus = R7;


    LDR     R0, =g_stLosTask    ;// R0 = g_stLosTask; ==> R0 = g_stLosTask.pstRunTask;
    LDR     R0, [R0, #4]    ;// R0 = *(g_stLosTask.pstNewTask); ==> R0 = g_stLosTask.pstNewTask.pStackPointer;
    STR     R0, [R5]    ;// g_stLosTask.pstRunTask.pStackPointer = g_stLosTask.pstNewTask.pStackPointer; ==> *g_stLosTask.pstRunTask = *g_stLosTask.pstNewTask;


    LDRH    R7, [R0 , #4]    ;// R7 = *(&(g_stLosTask.pstNewTask.usTaskStatus)); ==> R7 = g_stLosTask.pstNewTask.usTaskStatus;
    MOV     R8,  #OS_TASK_STATUS_RUNNING    ;// R8 = OS_TASK_STATUS_RUNNING;
    ORR     R7, R7, R8    ;// R7 |= R8;
    STRH    R7,  [R0 , #4]    ;// g_stLosTask.pstNewTask.usTaskStatus = R7;

    LDR     R1,   [R0]    ;// R1 = *(g_stLosTask.pstNewTask.pStackPointer);
    LDMFD   R1!, {R4-R12}    ;// 手動出棧,先出棧後增,小端,且棧往上生長
    MSR     PSP,  R1    ;// PSP = R1; // 更新 PSP 值

    MSR     PRIMASK, R12    ;// 恢復原有的中斷狀態
    BX      LR    ;// 返回到上層函式中,如 `osSchedule` 或 `LOS_Schedule` 函式中

    ALIGN
    END

排程上層原始碼分析

osSchedule函式原始碼分析

  • osSchedule 函式多用於建立任務函式和刪除任務函式。
/*****************************************************************************
 Function    : osSchedule
 Description : task scheduling
 Input       : None
 Output      : None
 Return      : None
 *****************************************************************************/
LITE_OS_SEC_TEXT VOID osSchedule(VOID)
{
    osTaskSchedule();
}

LOS_Schedule函式原始碼分析

  • LOS_Schedule 函式為系統常用的排程函式。
  • 簡單流程
    • 鎖中斷
    • 從就緒列表中獲取最合適的任務,賦值給 g_stLosTask.pstNewTask ,為下一個執行的任務
    • 判斷當前執行的任務和就緒列表中最適合的任務是否為同一個任務
        • 判斷是否鎖任務排程
            • 解鎖中斷
            • 進行排程操作: osTaskSchedule();
            • return;
    • 解鎖中斷
/*****************************************************************************
 Function    : LOS_Schedule
 Description : Function to determine whether task scheduling is required
 Input       : None
 Output      : None
 Return      : None
 *****************************************************************************/
LITE_OS_SEC_TEXT VOID LOS_Schedule(VOID)
{
    UINTPTR uvIntSave;

    uvIntSave = LOS_IntLock(); // 鎖中斷

    /* Find the highest task */
    g_stLosTask.pstNewTask = LOS_DL_LIST_ENTRY(osPriqueueTop(), LOS_TASK_CB, stPendList); // 從就緒列表中獲取最合適的任務,賦值給 g_stLosTask.pstNewTask ,為下一個執行的任務

    /* In case that running is not highest then reschedule */
    if (g_stLosTask.pstRunTask != g_stLosTask.pstNewTask) // 不是同一個任務就進行排程準備
    {
        if ((!g_usLosTaskLock)) // 判斷是否鎖任務了
        {
            (VOID)LOS_IntRestore(uvIntSave); // 解鎖中斷

            osTaskSchedule(); // 排程操作

            return; // 返回
        }
    }

    (VOID)LOS_IntRestore(uvIntSave); // 解鎖中斷
}

輔助參考程式碼

任務控制塊 LOS_TASK_CB 原始碼參考

  • 上述程式碼分析理解時需要了解這個結構體佈局。
/**
 * @ingroup los_task
 * Define the task control block structure.
 */
typedef struct tagTaskCB
{
    VOID                        *pStackPointer;             /**< Task stack pointer          */
    UINT16                      usTaskStatus;
    UINT16                      usPriority;
    UINT32                      uwStackSize;                /**< Task stack size             */
    UINT32                      uwTopOfStack;               /**< Task stack top              */
    UINT32                      uwTaskID;                   /**< Task ID                     */
    TSK_ENTRY_FUNC              pfnTaskEntry;               /**< Task entrance function      */
    VOID                        *pTaskSem;                  /**< Task-held semaphore         */
    VOID                        *pTaskMux;                  /**< Task-held mutex             */
    UINT32                      uwArg;                      /**< Parameter                   */
    CHAR                        *pcTaskName;                /**< Task name                   */
    LOS_DL_LIST                 stPendList;
    LOS_DL_LIST                 stTimerList;
    UINT32                      uwIdxRollNum;
    EVENT_CB_S                  uwEvent;
    UINT32                      uwEventMask;                /**< Event mask                  */
    UINT32                      uwEventMode;                /**< Event mode                  */
    VOID                        *puwMsg;                    /**< Memory allocated to queues  */
} LOS_TASK_CB;

LiteOS中斷向量表(二次命名版)

  • 中斷向量表原始碼 (位於檔案 los_hwi.c
HWI_PROC_FUNC m_pstHwiForm[OS_VECTOR_CNT] =
{
    (HWI_PROC_FUNC)0,                    // [0] Top of Stack
    (HWI_PROC_FUNC)Reset_Handler,        // [1] reset
    (HWI_PROC_FUNC)osHwiDefaultHandler,  // [2] NMI Handler
    (HWI_PROC_FUNC)osHwiDefaultHandler,  // [3] Hard Fault Handler
    (HWI_PROC_FUNC)osHwiDefaultHandler,  // [4] MPU Fault Handler
    (HWI_PROC_FUNC)osHwiDefaultHandler,  // [5] Bus Fault Handler
    (HWI_PROC_FUNC)osHwiDefaultHandler,  // [6] Usage Fault Handler
    (HWI_PROC_FUNC)0,                    // [7] Reserved
    (HWI_PROC_FUNC)0,                    // [8] Reserved
    (HWI_PROC_FUNC)0,                    // [9] Reserved
    (HWI_PROC_FUNC)0,                    // [10] Reserved
    (HWI_PROC_FUNC)osHwiDefaultHandler,  // [11] SVCall Handler
    (HWI_PROC_FUNC)osHwiDefaultHandler,  // [12] Debug Monitor Handler
    (HWI_PROC_FUNC)0,                    // [13] Reserved
    (HWI_PROC_FUNC)osPendSV,             // [14] PendSV Handler
    (HWI_PROC_FUNC)osHwiDefaultHandler,  // [15] SysTick Handler
};

參考

連結

相關文章