鴻蒙輕核心定時器Swtmr:不受硬體和數量限制,滿足使用者需求

華為雲開發者社群發表於2021-07-29
摘要:本文通過分析鴻蒙輕核心定時器模組的原始碼,掌握定時器使用上的差異。

本文分享自華為雲社群《鴻蒙輕核心M核原始碼分析系列十四 軟體定時器Swtmr》,作者:zhushy 。

軟體定時器(Software Timer)是基於系統Tick時鐘中斷且由軟體來模擬的定時器。當經過設定的Tick數後,會觸發使用者自定義的回撥函式。硬體定時器受硬體的限制,數量上不足以滿足使用者的實際需求。鴻蒙輕核心提供了軟體定時器功能可以提供更多的定時器,滿足使用者需求。

本文通過分析鴻蒙輕核心定時器模組的原始碼,掌握定時器使用上的差異。本文中所涉及的原始碼,以OpenHarmony LiteOS-M核心為例,均可以在開源站點https://gitee.com/openharmony/kernel_liteos_m 獲取。

接下來,我們看下定時器的結構體,定時器初始化,定時器常用操作的原始碼。

1、定時器結構體定義和常用巨集定義

1.1 定時器結構體定義

在檔案kernel\include\los_swtmr.h定義的定時器控制塊結構體為SWTMR_CTRL_S,結構體原始碼如下。定時器狀態.ucState取值OS_SWTMR_STATUS_UNUSED、OS_SWTMR_STATUS_CREATED或OS_SWTMR_STATUS_TICKING,定時器模式.mode取值LOS_SWTMR_MODE_ONCE、LOS_SWTMR_MODE_PERIOD或LOS_SWTMR_MODE_NO_SELFDELETE。其他結構體成員的解釋見註釋部分。

typedef struct tagSwTmrCtrl {
    struct tagSwTmrCtrl *pstNext;       /* 指向下一個定時器結構體的指標       */
    UINT8               ucState;        /* 定時器狀態,取值列舉SwtmrState    */
    UINT8               ucMode;         /* 定時器模式,取值列舉enSwTmrType   */
#if (LOSCFG_BASE_CORE_SWTMR_ALIGN == 1)
    UINT8               ucRouses;       /* 喚醒開關                         */
    UINT8               ucSensitive;    /* 對齊開關                         */
#endif
    UINT32              usTimerID;      /* 定時器編號Id                     */
    UINT32              uwCount;        /* 定時器執行的次數                  */
    UINT32              uwInterval;     /* 週期定時器超時間隔 (單位: tick)   */
    UINT32              uwArg;          /* 定時器超時回撥函式引數            */
    SWTMR_PROC_FUNC     pfnHandler;     /* 定時器超時回撥函式                */
    SortLinkList        stSortList;     /* 定時器排序連結串列                    */
} SWTMR_CTRL_S;

另外,還對回撥函式及其引數單獨定義了一個結構體SwtmrHandlerItem,如下:

typedef struct {
    SWTMR_PROC_FUNC handler;    /**< 定時器超時回撥函式    */
    UINTPTR arg;                /**< 定時器超時回撥函式引數 */
} SwtmrHandlerItem;

1.2 定時器常用巨集定義

定時器標頭檔案kernel\include\los_swtmr.h中還提供了相關的列舉和巨集,從定時器池裡獲取定時器控制塊的巨集定義OS_SWT_FROM_SID如下:

#define OS_SWT_FROM_SID(swtmrId)    ((SWTMR_CTRL_S *)g_swtmrCBArray + ((swtmrId) % LOSCFG_BASE_CORE_SWTMR_LIMIT))
標頭檔案中定義的定時器幾個列舉如下:
enum SwtmrState {
    OS_SWTMR_STATUS_UNUSED,     /**< 定時器未使用    */
    OS_SWTMR_STATUS_CREATED,    /**< 定時器已建立     */
    OS_SWTMR_STATUS_TICKING     /**< 定時器計時中     */
};

#if (LOSCFG_BASE_CORE_SWTMR_ALIGN == 1)

enum enSwTmrRousesType {
    OS_SWTMR_ROUSES_IGNORE, /* 定時器不能喚醒系統 */
    OS_SWTMR_ROUSES_ALLOW,  /* 定時器能喚醒系統 */
};

enum enSwTmrAlignSensitive {
    OS_SWTMR_ALIGN_SENSITIVE,   /* 定時器不需要對齊 */
    OS_SWTMR_ALIGN_INSENSITIVE, /* 定時器需要對齊 */
};
#endif

enum EnSwTmrType {
    LOS_SWTMR_MODE_ONCE,            /* 一次性定時器, 值為0. */
    LOS_SWTMR_MODE_PERIOD,          /* 週期定時器,值為 1. */
    LOS_SWTMR_MODE_NO_SELFDELETE,   /* 一次性定時器,不會自刪除,值為2 */
    LOS_SWTMR_MODE_OPP,             /* 一次性定時器完成後,使能週期性定時器。該模式暫不支援。值為3 */
};

2、定時器初始化

定時器在核心中預設開啟,使用者可以通過巨集LOSCFG_BASE_CORE_SWTMR進行關閉。開啟定時器的情況下,在系統啟動時,在kernel\src\los_init.c中呼叫OsSwtmrInit()進行定時器模組初始化。下面,我們分析下定時器初始化的程式碼。

⑴處如果開啟定時器對齊巨集LOSCFG_BASE_CORE_SWTMR_ALIGN,清零g_swtmrAlignID陣列。定時器的數量由巨集LOSCFG_BASE_CORE_SWTMR_LIMIT定義,⑵處計算定時器池需要的記憶體大小,然後為定時器申請記憶體,如果申請失敗,則返回錯誤。⑶初始化空閒定時器連結串列g_swtmrFreeList,維護未使用的定時器。迴圈每一個定時器進行初始化,為每一個定時器節點指定索引timerId,定時器控制塊依次指向下一個定時器控制塊。

⑷處程式碼為定時器建立佇列,佇列的訊息大小OS_SWTMR_HANDLE_QUEUE_SIZE等於定時器的數量LOSCFG_BASE_CORE_SWTMR_LIMIT,訊息內容的最大大小sizeof(SwtmrHandlerItem)。後文分析定時器佇列讀取寫入訊息的時候具體來看是什麼訊息。⑸處呼叫函式OsSwtmrTaskCreate()建立定時器任務,定時器任務優先順序最高,任務的入口函式為OsSwtmrTask(),後文會分析該函式。⑹處初始化定時器排序連結串列,原始碼分析系列之前的文章分析過,可以閱讀下排序連結串列資料結構章節。⑺處註冊定時器掃描函式OsSwtmrScan。

LITE_OS_SEC_TEXT_INIT UINT32 OsSwtmrInit(VOID)
{
    UINT32 size;
    UINT16 index;
    UINT32 ret;

#if (LOSCFG_BASE_CORE_SWTMR_ALIGN == 1)
    // Ignore the return code when matching CSEC rule 6.6(1).
⑴  (VOID)memset_s((VOID *)g_swtmrAlignID, sizeof(SwtmrAlignData) * LOSCFG_BASE_CORE_SWTMR_LIMIT,
                   0, sizeof(SwtmrAlignData) * LOSCFG_BASE_CORE_SWTMR_LIMIT);
#endif

⑵  size = sizeof(SWTMR_CTRL_S) * LOSCFG_BASE_CORE_SWTMR_LIMIT;
    SWTMR_CTRL_S *swtmr = (SWTMR_CTRL_S *)LOS_MemAlloc(m_aucSysMem0, size);
    if (swtmr == NULL) {
        return LOS_ERRNO_SWTMR_NO_MEMORY;
    }
    // Ignore the return code when matching CSEC rule 6.6(3).
    (VOID)memset_s((VOID *)swtmr, size, 0, size);
    g_swtmrCBArray = swtmr;
⑶  g_swtmrFreeList = swtmr;
    swtmr->usTimerID = 0;
    SWTMR_CTRL_S *temp = swtmr;
    swtmr++;
    for (index = 1; index < LOSCFG_BASE_CORE_SWTMR_LIMIT; index++, swtmr++) {
        swtmr->usTimerID = index;
        temp->pstNext = swtmr;
        temp = swtmr;
    }

⑷  ret = LOS_QueueCreate((CHAR *)NULL, OS_SWTMR_HANDLE_QUEUE_SIZE,
                          &g_swtmrHandlerQueue, 0, sizeof(SwtmrHandlerItem));
    if (ret != LOS_OK) {
        (VOID)LOS_MemFree(m_aucSysMem0, swtmr);
        return LOS_ERRNO_SWTMR_QUEUE_CREATE_FAILED;
    }

⑸  ret = OsSwtmrTaskCreate();
    if (ret != LOS_OK) {
        (VOID)LOS_MemFree(m_aucSysMem0, swtmr);
        return LOS_ERRNO_SWTMR_TASK_CREATE_FAILED;
    }

⑹  g_swtmrSortLinkList = OsGetSortLinkAttribute(OS_SORT_LINK_SWTMR);
    if (g_swtmrSortLinkList == NULL) {
        (VOID)LOS_MemFree(m_aucSysMem0, swtmr);
        return LOS_NOK;
    }

    ret = OsSortLinkInit(g_swtmrSortLinkList);
    if (ret != LOS_OK) {
        (VOID)LOS_MemFree(m_aucSysMem0, swtmr);
        return LOS_NOK;
    }

⑺  ret = OsSchedSwtmrScanRegister((SchedScan)OsSwtmrScan);
    if (ret != LOS_OK) {
        (VOID)LOS_MemFree(m_aucSysMem0, swtmr);
        return LOS_NOK;
    }

    return LOS_OK;
}

我們再看一下定時器任務的入口函式為OsSwtmrTask()。⑴進行for永久迴圈,佇列讀取不到資料時會阻塞,因為優先順序比較高,定時器佇列有資料時該任務就會執行。從定時器佇列中讀取定時器處理函式地址放入指標地址&swtmrHandle,讀取的長度為sizeof(SwtmrHandlerItem)。成功讀取後,獲取定時器回撥函式及其引數,然後⑵處執行定時器回撥函式。記錄定時器回撥函式的執行時間,⑶處判斷執行時間是否超時,如果超時,列印警告資訊。

LITE_OS_SEC_TEXT VOID OsSwtmrTask(VOID)
{
    SwtmrHandlerItem swtmrHandle;
    UINT32 readSize;
    UINT32 ret;
    UINT64 tick;
    readSize = sizeof(SwtmrHandlerItem);

    for (;;) {
⑴      ret = LOS_QueueReadCopy(g_swtmrHandlerQueue, &swtmrHandle, &readSize, LOS_WAIT_FOREVER);
        if ((ret == LOS_OK) && (readSize == sizeof(SwtmrHandlerItem))) {
            if (swtmrHandle.handler == NULL) {
                continue;
            }

            tick = LOS_TickCountGet();
⑵          swtmrHandle.handler(swtmrHandle.arg);
            tick = LOS_TickCountGet() - tick;

⑶          if (tick >= SWTMR_MAX_RUNNING_TICKS) {
                PRINT_WARN("timer_handler(%p) cost too many ms(%d)\n",
                           swtmrHandle.handler,
                           (UINT32)((tick * OS_SYS_MS_PER_SECOND) / LOSCFG_BASE_CORE_TICK_PER_SECOND));
            }
        }
    }
}

3、定時器常用操作

3.1 定時器建立

我們分析下建立定時器函式LOS_SwtmrCreate()的程式碼。先不考慮定時器對齊LOSCFG_BASE_CORE_SWTMR_ALIGN的情況。先看下函式引數,interval是定時器執行時間間隔,mode是建立的定時器模式,handler、arg是定時器回撥函式及其引數。swtmrId是定時器編號。

⑴處對傳入引數定時器超時間隔、定時器模式、回撥函式,定時器編號進行校驗。⑵判斷空閒定時器池是否為空,為空則返回錯誤,無法建立定時器。⑶處如果定時器不為空,則獲取定時器控制塊swtmr。⑷處對定時器控制塊資訊進行初始化。⑸處把該定時器排序連結串列節點的響應時間responseTime初始化為-1。

#if (LOSCFG_BASE_CORE_SWTMR_ALIGN == 1)
LITE_OS_SEC_TEXT_INIT UINT32 LOS_SwtmrCreate(UINT32 interval,
                                             UINT8 mode,
                                             SWTMR_PROC_FUNC handler,
                                             UINT32 *swtmrId,
                                             UINT32 arg,
                                             UINT8 rouses,
                                             UINT8 sensitive)
#else
LITE_OS_SEC_TEXT_INIT UINT32 LOS_SwtmrCreate(UINT32 interval,
                                             UINT8 mode,
                                             SWTMR_PROC_FUNC handler,
                                             UINT32 *swtmrId,
                                             UINT32 arg)
#endif
{
    SWTMR_CTRL_S  *swtmr = NULL;
    UINT32 intSave;

⑴  if (interval == 0) {
        return LOS_ERRNO_SWTMR_INTERVAL_NOT_SUITED;
    }

    if ((mode != LOS_SWTMR_MODE_ONCE) &&
        (mode != LOS_SWTMR_MODE_PERIOD) &&
        (mode != LOS_SWTMR_MODE_NO_SELFDELETE)) {
        return LOS_ERRNO_SWTMR_MODE_INVALID;
    }

    if (handler == NULL) {
        return LOS_ERRNO_SWTMR_PTR_NULL;
    }

    if (swtmrId == NULL) {
        return LOS_ERRNO_SWTMR_RET_PTR_NULL;
    }

#if (LOSCFG_BASE_CORE_SWTMR_ALIGN == 1)
    if ((rouses != OS_SWTMR_ROUSES_IGNORE) && (rouses != OS_SWTMR_ROUSES_ALLOW)) {
        return OS_ERRNO_SWTMR_ROUSES_INVALID;
    }

    if ((sensitive != OS_SWTMR_ALIGN_INSENSITIVE) && (sensitive != OS_SWTMR_ALIGN_SENSITIVE)) {
        return OS_ERRNO_SWTMR_ALIGN_INVALID;
    }
#endif

    intSave = LOS_IntLock();
⑵  if (g_swtmrFreeList == NULL) {
        LOS_IntRestore(intSave);
        return LOS_ERRNO_SWTMR_MAXSIZE;
    }

⑶  swtmr = g_swtmrFreeList;
    g_swtmrFreeList = swtmr->pstNext;
    LOS_IntRestore(intSave);
⑷  swtmr->pfnHandler    = handler;
    swtmr->ucMode        = mode;
    swtmr->uwInterval    = interval;
    swtmr->pstNext       = (SWTMR_CTRL_S *)NULL;
    swtmr->uwCount       = 0;
    swtmr->uwArg         = arg;
#if (LOSCFG_BASE_CORE_SWTMR_ALIGN == 1)
    swtmr->ucRouses      = rouses;
    swtmr->ucSensitive   = sensitive;
#endif
    swtmr->ucState       = OS_SWTMR_STATUS_CREATED;
    *swtmrId = swtmr->usTimerID;
⑸  SET_SORTLIST_VALUE(&swtmr->stSortList, OS_SORT_LINK_INVALID_TIME);

    return LOS_OK;
}

3.2 定時器刪除

我們可以使用函式LOS_SwtmrDelete(UINT32 swtmrId)來刪除定時器,下面通過分析原始碼看看如何刪除定時器的。

⑴處判斷定時器swtmrId是否超過OS_SWTMR_MAX_TIMERID,如果超過則返回錯誤碼。如果定時器編號沒有問題,獲取定時器控制塊LosSwtmrCB *swtmr。⑵處判斷要刪除的定時器swtmrId是否匹配,不匹配則返回錯誤碼。⑶處判斷定時器的狀態,如果定時器定時器沒有建立,不能刪除。如果定時器計時中,需要先停止OsSwtmrStop(swtmr),然後再刪除OsSwtmrDelete(swtmr)。

LITE_OS_SEC_TEXT UINT32 LOS_SwtmrDelete(UINT32 swtmrId)
{
    SWTMR_CTRL_S *swtmr = NULL;
    UINT32 intSave;
    UINT32 ret = LOS_OK;
    UINT16 swtmrCbId;

⑴  if (swtmrId >= OS_SWTMR_MAX_TIMERID) {
        return LOS_ERRNO_SWTMR_ID_INVALID;
    }
    intSave = LOS_IntLock();
    swtmrCbId = swtmrId % LOSCFG_BASE_CORE_SWTMR_LIMIT;
    swtmr = g_swtmrCBArray + swtmrCbId;
⑵  if (swtmr->usTimerID != swtmrId) {
        LOS_IntRestore(intSave);
        return LOS_ERRNO_SWTMR_ID_INVALID;
    }

⑶  switch (swtmr->ucState) {
        case OS_SWTMR_STATUS_UNUSED:
            ret = LOS_ERRNO_SWTMR_NOT_CREATED;
            break;
        case OS_SWTMR_STATUS_TICKING:
            OsSwtmrStop(swtmr);
            /* fall through */
        case OS_SWTMR_STATUS_CREATED:
            OsSwtmrDelete(swtmr);
            break;
        default:
            ret = LOS_ERRNO_SWTMR_STATUS_INVALID;
            break;
    }

    LOS_IntRestore(intSave);
    return ret;
}

接下來,我們繼續看看如何呼叫函式OsSwtmrDelete(swtmr)刪除定時器。函式特別簡單,把定時器放入空閒定時器連結串列g_swtmrFreeList頭部,然後把定時器狀態改為未使用狀態就完成了刪除。

STATIC_INLINE VOID OsSwtmrDelete(SWTMR_CTRL_S *swtmr)
{
    /* insert to free list */
    swtmr->pstNext = g_swtmrFreeList;
    g_swtmrFreeList = swtmr;
    swtmr->ucState = OS_SWTMR_STATUS_UNUSED;

#if (LOSCFG_BASE_CORE_SWTMR_ALIGN == 1)
    (VOID)memset_s((VOID *)&g_swtmrAlignID[swtmr->usTimerID % LOSCFG_BASE_CORE_SWTMR_LIMIT],
                   sizeof(SwtmrAlignData), 0, sizeof(SwtmrAlignData));
#endif
}

3.3 定時器啟動

建立完畢定時器後,我們可以使用函式LOS_SwtmrStart(UINT32 swtmrId)來啟動定時器,下面通過分析原始碼看看如何啟動定時器的。

⑴處判斷定時器swtmrId是否超過OS_SWTMR_MAX_TIMERID,如果超過則返回錯誤碼。如果定時器編號沒有問題,獲取定時器控制塊LosSwtmrCB *swtmr。⑵處判斷要啟動的定時器swtmrId是否匹配,不匹配則返回錯誤碼。⑶處判斷定時器的狀態,如果定時器定時器沒有建立,不能啟動。如果定時器計時中,需要先停止OsSwtmrStop(swtmr),然後再啟動OsSwtmrStart(swtmr)。

LITE_OS_SEC_TEXT UINT32 LOS_SwtmrStart(UINT32 swtmrId)
{
    UINT32 intSave;
    UINT32 ret = LOS_OK;

⑴  if (swtmrId >= OS_SWTMR_MAX_TIMERID) {
        return LOS_ERRNO_SWTMR_ID_INVALID;
    }

    intSave = LOS_IntLock();
    SWTMR_CTRL_S *swtmr = g_swtmrCBArray + swtmrId % LOSCFG_BASE_CORE_SWTMR_LIMIT;
⑵  if (swtmr->usTimerID != swtmrId) {
        LOS_IntRestore(intSave);
        return LOS_ERRNO_SWTMR_ID_INVALID;
    }

#if (LOSCFG_BASE_CORE_SWTMR_ALIGN == 1)
    if ((swtmr->ucSensitive == OS_SWTMR_ALIGN_INSENSITIVE) && (swtmr->ucMode == LOS_SWTMR_MODE_PERIOD)) {
        UINT32 swtmrAlignIdIndex = swtmr->usTimerID % LOSCFG_BASE_CORE_SWTMR_LIMIT;
        g_swtmrAlignID[swtmrAlignIdIndex].canAlign = 1;
        if ((swtmr->uwInterval % LOS_COMMON_DIVISOR) == 0) {
            g_swtmrAlignID[swtmrAlignIdIndex].canMultiple = 1;
            g_swtmrAlignID[swtmrAlignIdIndex].times = swtmr->uwInterval / LOS_COMMON_DIVISOR;
        }
    }
#endifswitch (swtmr->ucState) {
        case OS_SWTMR_STATUS_UNUSED:
            ret = LOS_ERRNO_SWTMR_NOT_CREATED;
            break;
        case OS_SWTMR_STATUS_TICKING:
            OsSwtmrStop(swtmr);
            /* fall through */
        case OS_SWTMR_STATUS_CREATED:
            OsSwtmrStart(swtmr);
            break;
        default:
            ret = LOS_ERRNO_SWTMR_STATUS_INVALID;
            break;
    }

    LOS_IntRestore(intSave);
    return ret;
}

接下來,我們繼續看看如何呼叫函式OsSwtmrStart(swtmr)啟動定時器。函式特別簡單,⑴設定定時器的等待超時時間,並把定時器狀態改為計時中。⑵處把該定時器插入超時排序連結串列中。如果已使能任務排程,則修改過期時間。

LITE_OS_SEC_TEXT VOID OsSwtmrStart(SWTMR_CTRL_S *swtmr)
{
    UINT64 currTime = OsGetCurrSchedTimeCycle();

⑴  swtmr->uwCount = swtmr->uwInterval;
    swtmr->ucState = OS_SWTMR_STATUS_TICKING;

#if (LOSCFG_BASE_CORE_SWTMR_ALIGN == 1)
    if ((g_swtmrAlignID[swtmr->usTimerID % LOSCFG_BASE_CORE_SWTMR_LIMIT].canAlign == 1) &&
        (g_swtmrAlignID[swtmr->usTimerID % LOSCFG_BASE_CORE_SWTMR_LIMIT].isAligned == 0)) {
        g_swtmrAlignID[swtmr->usTimerID % LOSCFG_BASE_CORE_SWTMR_LIMIT].isAligned = 1;
        OsSwtmrFindAlignPos(currTime, swtmr);
    }
#endif
⑵  OsAdd2SortLink(&swtmr->stSortList, currTime, swtmr->uwCount, OS_SORT_LINK_SWTMR);
    if (LOS_TaskIsRunning()) {
⑶      OsSchedUpdateExpireTime(currTime);
    }
}

3.4 定時器停止

我們可以使用函式LOS_SwtmrStop(UINT32 swtmrId)來停止定時器,下面通過分析原始碼看看如何停止定時器的。

⑴處判斷定時器swtmrId是否超過OS_SWTMR_MAX_TIMERID,如果超過則返回錯誤碼。如果定時器編號沒有問題,獲取定時器控制塊LosSwtmrCB *swtmr。⑵處判斷要啟動的定時器swtmrId是否匹配,不匹配則返回錯誤碼。⑶處判斷定時器的狀態,如果定時器定時器沒有建立,沒有啟動,不能停止。如果定時器計時中,會繼續呼叫OsSwtmrStop(swtmr)停止定時器。

LITE_OS_SEC_TEXT UINT32 LOS_SwtmrStop(UINT32 swtmrId)
{
    SWTMR_CTRL_S *swtmr = NULL;
    UINT32 intSave;
    UINT16 swtmrCbId;
    UINT32 ret = LOS_OK;

⑴  if (swtmrId >= OS_SWTMR_MAX_TIMERID) {
        return LOS_ERRNO_SWTMR_ID_INVALID;
    }
    intSave = LOS_IntLock();
    swtmrCbId = swtmrId % LOSCFG_BASE_CORE_SWTMR_LIMIT;
    swtmr = g_swtmrCBArray + swtmrCbId;
⑵  if (swtmr->usTimerID != swtmrId) {
        LOS_IntRestore(intSave);
        return LOS_ERRNO_SWTMR_ID_INVALID;
    }

⑶  switch (swtmr->ucState) {
        case OS_SWTMR_STATUS_UNUSED:
            ret = LOS_ERRNO_SWTMR_NOT_CREATED;
            break;
        case OS_SWTMR_STATUS_CREATED:
            ret = LOS_ERRNO_SWTMR_NOT_STARTED;
            break;
        case OS_SWTMR_STATUS_TICKING:
            OsSwtmrStop(swtmr);
            break;
        default:
            ret = LOS_ERRNO_SWTMR_STATUS_INVALID;
            break;
    }

    LOS_IntRestore(intSave);
    return ret;
}

接下來,我們繼續看看如何呼叫函式OsSwtmrStop(swtmr)停止定時器。函式特別簡單,⑴處從排序連結串列中刪除該定時器的排序連結串列節點,更改定時器的狀態。⑵如果已使能任務排程,則修改過期時間。

LITE_OS_SEC_TEXT VOID OsSwtmrStop(SWTMR_CTRL_S *swtmr)
{
⑴  OsDeleteSortLink(&swtmr->stSortList, OS_SORT_LINK_SWTMR);
    swtmr->ucState = OS_SWTMR_STATUS_CREATED;

    if (LOS_TaskIsRunning()) {
⑵       OsSchedUpdateExpireTime(OsGetCurrSchedTimeCycle());
#if (LOSCFG_BASE_CORE_SWTMR_ALIGN == 1)
        g_swtmrAlignID[swtmr->usTimerID % LOSCFG_BASE_CORE_SWTMR_LIMIT].isAligned = 0;
#endif
    }
}

4、定時器和Tick時間關係

定時器加入到超時排序連結串列後,隨時時間一個tick一個tick的逝去,需要不斷的檢查定時器是否超時到期。從之前的文章,已經知道系統每走過一個tick,系統就會呼叫一次Tick中斷的處理函式OsTickHandler(),該函式會呼叫定時器掃描函式OsSwtmrScan()來掃描、更新定時器時間。我們看下OsSwtmrScan()的程式碼。

⑴處獲取超時排序連結串列的連結串列節點listObject,⑵判斷排序連結串列是否為空,為空則返回。⑶獲取排序連結串列的下一個連結串列節點sortList。⑷迴圈遍歷超時排序連結串列上響應時間小於等於當前時間的連結串列節點,意味著定時器到期,需要處理定時器的回撥函式。⑸從超時排序連結串列中刪除超時的節點,⑹獲取定時器控制塊SWTMR_CTRL_S *swtmr,呼叫函式OsSwtmrTimeoutHandle(swtmr)執行定時器回撥函式,並設定需要排程的標記needSchedule。⑺如果超時排序連結串列為空則終止迴圈。

STATIC BOOL OsSwtmrScan(VOID)
{
    BOOL needSchedule = FALSE;
⑴  LOS_DL_LIST *listObject = &g_swtmrSortLinkList->sortLink;

⑵  if (LOS_ListEmpty(listObject)) {
        return needSchedule;
    }

⑶  SortLinkList *sortList = LOS_DL_LIST_ENTRY(listObject->pstNext, SortLinkList, sortLinkNode);
    UINT64 currTime = OsGetCurrSchedTimeCycle();
⑷  while (sortList->responseTime <= currTime) {
⑸      OsDeleteNodeSortLink(g_swtmrSortLinkList, sortList);

⑹      SWTMR_CTRL_S *swtmr = LOS_DL_LIST_ENTRY(sortList, SWTMR_CTRL_S, stSortList);
        OsSwtmrTimeoutHandle(swtmr);

        needSchedule = TRUE;
⑺      if (LOS_ListEmpty(listObject)) {
            break;
        }

        sortList = LOS_DL_LIST_ENTRY(listObject->pstNext, SortLinkList, sortLinkNode);
    }

    return needSchedule;
}

我們最後看下函式OsSwtmrTimeoutHandle()。⑴處把定時器回撥函式寫入定時器佇列。⑵如果是一次性定時器,會把這個定時器刪除,回收到空閒定時器連結串列,狀態設定為未使用狀態,然後更新定時器的編號timerId。⑶如果定時器屬於週期性定時器,重新啟動定時器。⑷如果是一次性定時器但不刪除,則把定時器狀態設定為建立狀態。

STATIC VOID OsSwtmrTimeoutHandle(SWTMR_CTRL_S *swtmr)
{
    SwtmrHandlerItem swtmrHandler;

    swtmrHandler.handler = swtmr->pfnHandler;
    swtmrHandler.arg = swtmr->uwArg;

⑴  (VOID)LOS_QueueWriteCopy(g_swtmrHandlerQueue, &swtmrHandler, sizeof(SwtmrHandlerItem), LOS_NO_WAIT);
⑵  if (swtmr->ucMode == LOS_SWTMR_MODE_ONCE) {
        OsSwtmrDelete(swtmr);
        if (swtmr->usTimerID < (OS_SWTMR_MAX_TIMERID - LOSCFG_BASE_CORE_SWTMR_LIMIT)) {
            swtmr->usTimerID += LOSCFG_BASE_CORE_SWTMR_LIMIT;
        } else {
            swtmr->usTimerID %= LOSCFG_BASE_CORE_SWTMR_LIMIT;
        }
⑶  } else if (swtmr->ucMode == LOS_SWTMR_MODE_PERIOD) {
        OsSwtmrStart(swtmr);
⑷  } else if (swtmr->ucMode == LOS_SWTMR_MODE_NO_SELFDELETE) {
        swtmr->ucState = OS_SWTMR_STATUS_CREATED;
    }
}

小結

本文帶領大家一起剖析了鴻蒙輕核心的定時器模組的原始碼,包含定時器的結構體、定時器池初始化、定時器建立、刪除、啟動停止等。感謝閱讀,如有任何問題、建議,都可以留言給我們: https://gitee.com/openharmony/kernel_liteos_m/issues 。為了更容易找到鴻蒙輕核心程式碼倉,建議訪問 https://gitee.com/openharmony/kernel_liteos_m ,關注Watch、點贊Star、並Fork到自己賬戶下,謝謝。

 

點選關注,第一時間瞭解華為雲新鮮技術~

相關文章