鴻蒙輕內核定時器Swtmr:不受硬件和數量限制,滿足用戶需求

摘要:本文通過分析鴻蒙輕內核定時器模塊的源碼,掌握定時器使用上的差異。

本文分享自華爲雲社區《鴻蒙輕內核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到自己賬戶下,謝謝。

 

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章