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

软件定时器(Software Timer)是基于系统Tick时钟中断且由软件来模拟的定时器。当经过设定的Tick数后,会触发用户自定义的回调函数。硬件定时器受硬件的限制,数量上不足以满足用户的实际需求。鸿蒙轻内核提供了软件定时器功能可以提供更多的定时器,满足用户需求。

本文通过分析鸿蒙轻内核定时器模块的源码,掌握定时器使用上的差异。本文中所涉及的源码,以OpenHarmony LiteOS-M内核为例,均可以在开源站点 gitee.com/openharmony... 获取。


接下来,我们看下定时器的结构体,定时器初始化,定时器常用操作的源代码。

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。其他结构体成员的解释见注释部分。

objectivec 复制代码
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,如下:

objectivec 复制代码
typedef struct {
    SWTMR_PROC_FUNC handler;    /**< 定时器超时回调函数    */
    UINTPTR arg;                /**< 定时器超时回调函数参数 */
} SwtmrHandlerItem;

1.2 定时器常用宏定义

定时器头文件kernel\include\los_swtmr.h中还提供了相关的枚举和宏,从定时器池里获取定时器控制块的宏定义OS_SWT_FROM_SID如下:

scss 复制代码
#define OS_SWT_FROM_SID(swtmrId)    ((SWTMR_CTRL_S *)g_swtmrCBArray + ((swtmrId) % LOSCFG_BASE_CORE_SWTMR_LIMIT))

头文件中定义的定时器几个枚举如下:

arduino 复制代码
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

ini 复制代码
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)。成功读取后,获取定时器回调函数及其参数,然后⑵处执行定时器回调函数。记录定时器回调函数的执行时间,⑶处判断执行时间是否超时,如果超时,打印警告信息。

ini 复制代码
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));
            }
        }
    }
}

DD一下: 欢迎大家关注公众号<程序猿百晓生>,可以了解到一下知识点。

erlang 复制代码
1.OpenHarmony开发基础
2.OpenHarmony北向开发环境搭建
3.鸿蒙南向开发环境的搭建
4.鸿蒙生态应用开发白皮书V2.0 & V3.0
5.鸿蒙开发面试真题(含参考答案) 
6.TypeScript入门学习手册
7.OpenHarmony 经典面试题(含参考答案)
8.OpenHarmony设备开发入门【最新版】
9.沉浸式剖析OpenHarmony源代码
10.系统定制指南
11.【OpenHarmony】Uboot 驱动加载流程
12.OpenHarmony构建系统--GN与子系统、部件、模块详解
13.ohos开机init启动流程
14.鸿蒙版性能优化指南
.......

3、定时器常用操作

3.1 定时器创建

我们分析下创建定时器函数LOS_SwtmrCreate()的代码。先不考虑定时器对齐LOSCFG_BASE_CORE_SWTMR_ALIGN的情况。先看下函数参数,interval是定时器执行时间间隔,mode是创建的定时器模式,handlerarg是定时器回调函数及其参数。swtmrId是定时器编号。

⑴处对传入参数定时器超时间隔、定时器模式、回调函数,定时器编号进行校验。⑵判断空闲定时器池是否为空,为空则返回错误,无法创建定时器。⑶处如果定时器不为空,则获取定时器控制块swtmr。⑷处对定时器控制块信息进行初始化。⑸处把该定时器排序链表节点的响应时间responseTime初始化为-1。

ini 复制代码
#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)

ini 复制代码
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头部,然后把定时器状态改为未使用状态就完成了删除。

ini 复制代码
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)

ini 复制代码
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)启动定时器。函数特别简单,⑴设置定时器的等待超时时间,并把定时器状态改为计时中。⑵处把该定时器插入超时排序链表中。如果已使能任务调度,则修改过期时间。

ini 复制代码
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)停止定时器。

ini 复制代码
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)停止定时器。函数特别简单,⑴处从排序链表中删除该定时器的排序链表节点,更改定时器的状态。⑵如果已使能任务调度,则修改过期时间。

ini 复制代码
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。⑺如果超时排序链表为空则终止循环。

ini 复制代码
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。⑶如果定时器属于周期性定时器,重新启动定时器。⑷如果是一次性定时器但不删除,则把定时器状态设置为创建状态。

ini 复制代码
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;
    }
}

小结

本文带领大家一起剖析了鸿蒙轻内核的定时器模块的源代码,包含定时器的结构体、定时器池初始化、定时器创建、删除、启动停止等。

相关推荐
前端菜鸟日常7 小时前
鸿蒙版(ArkTs) 贪吃蛇,包含无敌模式 最高分 暂停和继续功能
华为·harmonyos
鸿蒙布道师12 小时前
鸿蒙NEXT开发数值工具类(TS)
android·ios·华为·harmonyos·arkts·鸿蒙系统·huawei
塞尔维亚大汉17 小时前
鸿蒙南向开发 ——轻量系统芯片移植指南(二)
物联网·嵌入式·harmonyos
别说我什么都不会18 小时前
OpenHarmony内核系统调用hats测试用例编写指南
物联网·嵌入式·harmonyos
90后的晨仔19 小时前
鸿蒙ArkTS是如何实现并发的?
harmonyos
鸿蒙布道师21 小时前
鸿蒙NEXT开发日期工具类(ArkTs)
android·ios·华为·harmonyos·arkts·鸿蒙系统·huawei
HMSCore1 天前
在应用内购票、寄件时,如何一键填充所需信息?
harmonyos
HarmonyOS_SDK1 天前
在应用内购票、寄件时,如何一键填充所需信息?
harmonyos
IceMan1 天前
进程间通信(一)管道通信
操作系统
罗念笙1 天前
说下你常用的Linux命令?
linux·操作系统