鸿蒙轻内核M核源码分析系列十四 软件定时器Swtmr 原创 精华

zhushangyuan_
发布于 2021-6-3 08:45
浏览
3收藏

鸿蒙轻内核M核源码分析系列十四 软件定时器Swtmr

软件定时器(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_UNUSEDOS_SWTMR_STATUS_CREATEDOS_SWTMR_STATUS_TICKING,定时器模式.mode取值LOS_SWTMR_MODE_ONCELOS_SWTMR_MODE_PERIODLOS_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是创建的定时器模式,handlerarg是定时器回调函数及其参数。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;
        }
    }
#endif

⑶  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:
            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到自己账户下,谢谢。

©著作权归作者所有,如需转载,请注明出处,否则将追究法律责任
2
收藏 3
回复
举报
8条回复
按时间正序
/
按时间倒序
mb609898e2cfb86
mb609898e2cfb86

一直在追更楼主的文章,感谢楼主分享。

回复
2021-6-3 11:10:37
jinyi7016
jinyi7016

对于硬件定时器的使用,是怎么样的呢,是要用到Hwi么

回复
2021-12-28 13:43:25
zhushangyuan_
zhushangyuan_ 回复了 jinyi7016
对于硬件定时器的使用,是怎么样的呢,是要用到Hwi么

这里是软件定时器。Hwi是中断?

回复
2021-12-28 14:09:58
jinyi7016
jinyi7016 回复了 zhushangyuan_
这里是软件定时器。Hwi是中断?

你好,就是想知道硬件定时器用的中断,在liteos-m中怎么配置呢?

直接裸机配置使用,要不要与liteos-m的中断管理关联,

如:鸿蒙轻内核M核源码分析系列四 中断Hwi-鸿蒙HarmonyOS技术社区-鸿蒙官方合作伙伴-51CTO.COM

回复
2021-12-28 14:59:35
zhushangyuan_
zhushangyuan_ 回复了 jinyi7016
你好,就是想知道硬件定时器用的中断,在liteos-m中怎么配置呢? 直接裸机配置使用,要不要与liteos-m的中断管理关联, 如:鸿蒙轻内核M核源码分析系列四 中断Hwi-鸿蒙HarmonyOS技术社区-鸿蒙官方合作伙伴-51CTO.COM

说的就是tick中断吗?不知道这篇文档时间管理 对您是否有帮助

https://harmonyos.51cto.com/posts/4250

已于2021-12-28 23:21:08修改
回复
2021-12-28 23:18:45
jinyi7016
jinyi7016 回复了 zhushangyuan_
说的就是tick中断吗?不知道这篇文档时间管理 对您是否有帮助 https://harmonyos.51cto.com/posts/4250

tick是内核的时间管理,而我说的是通用的一些外设的中断,如GPIO,UART,DMA,Timer,这些外设的中断在Liteos-m中的应用方法。

回复
2021-12-29 09:27:40
zhushangyuan_
zhushangyuan_

这块 需要了解下OpenHarmony的驱动框架 提供些文档供你参考 https://device.harmonyos.com/cn/docs/documentation/guide/driver-platform-gpio-des-0000001050058978

已于2021-12-29 10:24:52修改
回复
2021-12-29 10:24:17
jinyi7016
jinyi7016 回复了 zhushangyuan_
这块 需要了解下OpenHarmony的驱动框架 提供些文档供你参考 https://device.harmonyos.com/cn/docs/documentation/guide/driver-platform-gpio-des-0000001050058978

好的,谢谢

回复
2021-12-29 15:27:42
回复
    相关推荐