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

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

[[404515]]

想了解更多内容,请访问:

51CTO和华为官方合作共建的鸿蒙技术社区

https://harmonyos.51cto.com

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

本文通过分析鸿蒙轻内核定时器模块的源码,掌握定时器使用上的差异。

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

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

  1. typedef struct tagSwTmrCtrl { 
  2.     struct tagSwTmrCtrl *pstNext;       /* 指向下一个定时器结构体的指针       */ 
  3.     UINT8               ucState;        /* 定时器状态,取值枚举SwtmrState    */ 
  4.     UINT8               ucMode;         /* 定时器模式,取值枚举enSwTmrType   */ 
  5. #if (LOSCFG_BASE_CORE_SWTMR_ALIGN == 1) 
  6.     UINT8               ucRouses;       /* 唤醒开关                         */ 
  7.     UINT8               ucSensitive;    /* 对齐开关                         */ 
  8. #endif 
  9.     UINT32              usTimerID;      /* 定时器编号Id                     */ 
  10.     UINT32              uwCount;        /* 定时器运行的次数                  */ 
  11.     UINT32              uwInterval;     /* 周期定时器超时间隔 (单位: tick)   */ 
  12.     UINT32              uwArg;          /* 定时器超时回调函数参数            */ 
  13.     SWTMR_PROC_FUNC     pfnHandler;     /* 定时器超时回调函数                */ 
  14.     SortLinkList        stSortList;     /* 定时器排序链表                    */ 
  15. } SWTMR_CTRL_S; 

 另外,还对回调函数及其参数单独定义了一个结构体SwtmrHandlerItem,如下:

  1. typedef struct { 
  2.     SWTMR_PROC_FUNC handler;    /**< 定时器超时回调函数    */ 
  3.     UINTPTR arg;                /**< 定时器超时回调函数参数 */ 
  4. } SwtmrHandlerItem; 

 1.2 定时器常用宏定义

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

  1. #define OS_SWT_FROM_SID(swtmrId)    ((SWTMR_CTRL_S *)g_swtmrCBArray + ((swtmrId) % LOSCFG_BASE_CORE_SWTMR_LIMIT)) 

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

  1. enum SwtmrState { 
  2.     OS_SWTMR_STATUS_UNUSED,     /**< 定时器未使用    */ 
  3.     OS_SWTMR_STATUS_CREATED,    /**< 定时器已创建     */ 
  4.     OS_SWTMR_STATUS_TICKING     /**< 定时器计时中     */ 
  5. }; 
  6.  
  7. #if (LOSCFG_BASE_CORE_SWTMR_ALIGN == 1) 
  8.  
  9. enum enSwTmrRousesType { 
  10.     OS_SWTMR_ROUSES_IGNORE, /* 定时器不能唤醒系统 */ 
  11.     OS_SWTMR_ROUSES_ALLOW,  /* 定时器能唤醒系统 */ 
  12. }; 
  13.  
  14. enum enSwTmrAlignSensitive { 
  15.     OS_SWTMR_ALIGN_SENSITIVE,   /* 定时器不需要对齐 */ 
  16.     OS_SWTMR_ALIGN_INSENSITIVE, /* 定时器需要对齐 */ 
  17. }; 
  18. #endif 
  19.  
  20. enum EnSwTmrType { 
  21.     LOS_SWTMR_MODE_ONCE,            /* 一次性定时器, 值为0. */ 
  22.     LOS_SWTMR_MODE_PERIOD,          /* 周期定时器,值为 1. */ 
  23.     LOS_SWTMR_MODE_NO_SELFDELETE,   /* 一次性定时器,不会自删除,值为2 */ 
  24.     LOS_SWTMR_MODE_OPP,             /* 一次性定时器完成后,使能周期性定时器。该模式暂不支持。值为3 */ 
  25. }; 

 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。

  1. LITE_OS_SEC_TEXT_INIT UINT32 OsSwtmrInit(VOID) 
  2.     UINT32 size
  3.     UINT16 index
  4.     UINT32 ret; 
  5.  
  6. #if (LOSCFG_BASE_CORE_SWTMR_ALIGN == 1) 
  7.     // Ignore the return code when matching CSEC rule 6.6(1). 
  8. ⑴  (VOID)memset_s((VOID *)g_swtmrAlignID, sizeof(SwtmrAlignData) * LOSCFG_BASE_CORE_SWTMR_LIMIT, 
  9.                    0, sizeof(SwtmrAlignData) * LOSCFG_BASE_CORE_SWTMR_LIMIT); 
  10. #endif 
  11.  
  12. ⑵  size = sizeof(SWTMR_CTRL_S) * LOSCFG_BASE_CORE_SWTMR_LIMIT; 
  13.     SWTMR_CTRL_S *swtmr = (SWTMR_CTRL_S *)LOS_MemAlloc(m_aucSysMem0, size); 
  14.     if (swtmr == NULL) { 
  15.         return LOS_ERRNO_SWTMR_NO_MEMORY; 
  16.     } 
  17.     // Ignore the return code when matching CSEC rule 6.6(3). 
  18.     (VOID)memset_s((VOID *)swtmr, size, 0, size); 
  19.     g_swtmrCBArray = swtmr; 
  20. ⑶  g_swtmrFreeList = swtmr; 
  21.     swtmr->usTimerID = 0; 
  22.     SWTMR_CTRL_S *temp = swtmr; 
  23.     swtmr++; 
  24.     for (index = 1; index < LOSCFG_BASE_CORE_SWTMR_LIMIT; index++, swtmr++) { 
  25.         swtmr->usTimerID = index
  26.         temp->pstNext = swtmr; 
  27.         temp = swtmr; 
  28.     } 
  29.  
  30. ⑷  ret = LOS_QueueCreate((CHAR *)NULL, OS_SWTMR_HANDLE_QUEUE_SIZE, 
  31.                           &g_swtmrHandlerQueue, 0, sizeof(SwtmrHandlerItem)); 
  32.     if (ret != LOS_OK) { 
  33.         (VOID)LOS_MemFree(m_aucSysMem0, swtmr); 
  34.         return LOS_ERRNO_SWTMR_QUEUE_CREATE_FAILED; 
  35.     } 
  36.  
  37. ⑸  ret = OsSwtmrTaskCreate(); 
  38.     if (ret != LOS_OK) { 
  39.         (VOID)LOS_MemFree(m_aucSysMem0, swtmr); 
  40.         return LOS_ERRNO_SWTMR_TASK_CREATE_FAILED; 
  41.     } 
  42.  
  43. ⑹  g_swtmrSortLinkList = OsGetSortLinkAttribute(OS_SORT_LINK_SWTMR); 
  44.     if (g_swtmrSortLinkList == NULL) { 
  45.         (VOID)LOS_MemFree(m_aucSysMem0, swtmr); 
  46.         return LOS_NOK; 
  47.     } 
  48.  
  49.     ret = OsSortLinkInit(g_swtmrSortLinkList); 
  50.     if (ret != LOS_OK) { 
  51.         (VOID)LOS_MemFree(m_aucSysMem0, swtmr); 
  52.         return LOS_NOK; 
  53.     } 
  54.  
  55. ⑺  ret = OsSchedSwtmrScanRegister((SchedScan)OsSwtmrScan); 
  56.     if (ret != LOS_OK) { 
  57.         (VOID)LOS_MemFree(m_aucSysMem0, swtmr); 
  58.         return LOS_NOK; 
  59.     } 
  60.  
  61.     return LOS_OK; 

我们再看一下定时器任务的入口函数为OsSwtmrTask()。⑴进行for永久循环,队列读取不到数据时会阻塞,因为优先级比较高,定时器队列有数据时该任务就会执行。从定时器队列中读取定时器处理函数地址放入指针地址&swtmrHandle,读取的长度为sizeof(SwtmrHandlerItem)。成功读取后,获取定时器回调函数及其参数,然后⑵处执行定时器回调函数。记录定时器回调函数的执行时间,⑶处判断执行时间是否超时,如果超时,打印警告信息。

  1. LITE_OS_SEC_TEXT VOID OsSwtmrTask(VOID) 
  2.     SwtmrHandlerItem swtmrHandle; 
  3.     UINT32 readSize; 
  4.     UINT32 ret; 
  5.     UINT64 tick; 
  6.     readSize = sizeof(SwtmrHandlerItem); 
  7.  
  8.     for (;;) { 
  9. ⑴      ret = LOS_QueueReadCopy(g_swtmrHandlerQueue, &swtmrHandle, &readSize, LOS_WAIT_FOREVER); 
  10.         if ((ret == LOS_OK) && (readSize == sizeof(SwtmrHandlerItem))) { 
  11.             if (swtmrHandle.handler == NULL) { 
  12.                 continue
  13.             } 
  14.  
  15.             tick = LOS_TickCountGet(); 
  16. ⑵          swtmrHandle.handler(swtmrHandle.arg); 
  17.             tick = LOS_TickCountGet() - tick; 
  18.  
  19. ⑶          if (tick >= SWTMR_MAX_RUNNING_TICKS) { 
  20.                 PRINT_WARN("timer_handler(%p) cost too many ms(%d)\n"
  21.                            swtmrHandle.handler, 
  22.                            (UINT32)((tick * OS_SYS_MS_PER_SECOND) / LOSCFG_BASE_CORE_TICK_PER_SECOND)); 
  23.             } 
  24.         } 
  25.     } 

 3、定时器常用操作

3.1 定时器创建

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

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

  1. #if (LOSCFG_BASE_CORE_SWTMR_ALIGN == 1) 
  2. LITE_OS_SEC_TEXT_INIT UINT32 LOS_SwtmrCreate(UINT32 interval, 
  3.                                              UINT8 mode, 
  4.                                              SWTMR_PROC_FUNC handler, 
  5.                                              UINT32 *swtmrId, 
  6.                                              UINT32 arg, 
  7.                                              UINT8 rouses, 
  8.                                              UINT8 sensitive) 
  9. #else 
  10. LITE_OS_SEC_TEXT_INIT UINT32 LOS_SwtmrCreate(UINT32 interval, 
  11.                                              UINT8 mode, 
  12.                                              SWTMR_PROC_FUNC handler, 
  13.                                              UINT32 *swtmrId, 
  14.                                              UINT32 arg) 
  15. #endif 
  16.     SWTMR_CTRL_S  *swtmr = NULL
  17.     UINT32 intSave; 
  18.  
  19. ⑴  if (interval == 0) { 
  20.         return LOS_ERRNO_SWTMR_INTERVAL_NOT_SUITED; 
  21.     } 
  22.  
  23.     if ((mode != LOS_SWTMR_MODE_ONCE) && 
  24.         (mode != LOS_SWTMR_MODE_PERIOD) && 
  25.         (mode != LOS_SWTMR_MODE_NO_SELFDELETE)) { 
  26.         return LOS_ERRNO_SWTMR_MODE_INVALID; 
  27.     } 
  28.  
  29.     if (handler == NULL) { 
  30.         return LOS_ERRNO_SWTMR_PTR_NULL; 
  31.     } 
  32.  
  33.     if (swtmrId == NULL) { 
  34.         return LOS_ERRNO_SWTMR_RET_PTR_NULL; 
  35.     } 
  36.  
  37. #if (LOSCFG_BASE_CORE_SWTMR_ALIGN == 1) 
  38.     if ((rouses != OS_SWTMR_ROUSES_IGNORE) && (rouses != OS_SWTMR_ROUSES_ALLOW)) { 
  39.         return OS_ERRNO_SWTMR_ROUSES_INVALID; 
  40.     } 
  41.  
  42.     if ((sensitive != OS_SWTMR_ALIGN_INSENSITIVE) && (sensitive != OS_SWTMR_ALIGN_SENSITIVE)) { 
  43.         return OS_ERRNO_SWTMR_ALIGN_INVALID; 
  44.     } 
  45. #endif 
  46.  
  47.     intSave = LOS_IntLock(); 
  48. ⑵  if (g_swtmrFreeList == NULL) { 
  49.         LOS_IntRestore(intSave); 
  50.         return LOS_ERRNO_SWTMR_MAXSIZE; 
  51.     } 
  52.  
  53. ⑶  swtmr = g_swtmrFreeList; 
  54.     g_swtmrFreeList = swtmr->pstNext; 
  55.     LOS_IntRestore(intSave); 
  56. ⑷  swtmr->pfnHandler    = handler; 
  57.     swtmr->ucMode        = mode; 
  58.     swtmr->uwInterval    = interval; 
  59.     swtmr->pstNext       = (SWTMR_CTRL_S *)NULL
  60.     swtmr->uwCount       = 0; 
  61.     swtmr->uwArg         = arg; 
  62. #if (LOSCFG_BASE_CORE_SWTMR_ALIGN == 1) 
  63.     swtmr->ucRouses      = rouses; 
  64.     swtmr->ucSensitive   = sensitive; 
  65. #endif 
  66.     swtmr->ucState       = OS_SWTMR_STATUS_CREATED; 
  67.     *swtmrId = swtmr->usTimerID; 
  68. ⑸  SET_SORTLIST_VALUE(&swtmr->stSortList, OS_SORT_LINK_INVALID_TIME); 
  69.  
  70.     return LOS_OK; 

 3.2 定时器删除

我们可以使用函数LOS_SwtmrDelete(UINT32 swtmrId)来删除定时器,下面通过分析源码看看如何删除定时器的。

⑴处判断定时器swtmrId是否超过OS_SWTMR_MAX_TIMERID,如果超过则返回错误码。如果定时器编号没有问题,获取定时器控制块LosSwtmrCB *swtmr。⑵处判断要删除的定时器swtmrId是否匹配,不匹配则返回错误码。⑶处判断定时器的状态,如果定时器定时器没有创建,不能删除。如果定时器计时中,需要先停止OsSwtmrStop(swtmr),然后再删除OsSwtmrDelete(swtmr)。

  1. LITE_OS_SEC_TEXT UINT32 LOS_SwtmrDelete(UINT32 swtmrId) 
  2.     SWTMR_CTRL_S *swtmr = NULL
  3.     UINT32 intSave; 
  4.     UINT32 ret = LOS_OK; 
  5.     UINT16 swtmrCbId; 
  6.  
  7. ⑴  if (swtmrId >= OS_SWTMR_MAX_TIMERID) { 
  8.         return LOS_ERRNO_SWTMR_ID_INVALID; 
  9.     } 
  10.     intSave = LOS_IntLock(); 
  11.     swtmrCbId = swtmrId % LOSCFG_BASE_CORE_SWTMR_LIMIT; 
  12.     swtmr = g_swtmrCBArray + swtmrCbId; 
  13. ⑵  if (swtmr->usTimerID != swtmrId) { 
  14.         LOS_IntRestore(intSave); 
  15.         return LOS_ERRNO_SWTMR_ID_INVALID; 
  16.     } 
  17.  
  18. ⑶  switch (swtmr->ucState) { 
  19.         case OS_SWTMR_STATUS_UNUSED: 
  20.             ret = LOS_ERRNO_SWTMR_NOT_CREATED; 
  21.             break; 
  22.         case OS_SWTMR_STATUS_TICKING: 
  23.             OsSwtmrStop(swtmr); 
  24.             /* fall through */ 
  25.         case OS_SWTMR_STATUS_CREATED: 
  26.             OsSwtmrDelete(swtmr); 
  27.             break; 
  28.         default
  29.             ret = LOS_ERRNO_SWTMR_STATUS_INVALID; 
  30.             break; 
  31.     } 
  32.  
  33.     LOS_IntRestore(intSave); 
  34.     return ret; 

 接下来,我们继续看看如何调用函数OsSwtmrDelete(swtmr)删除定时器。函数特别简单,把定时器放入空闲定时器链表g_swtmrFreeList头部,然后把定时器状态改为未使用状态就完成了删除。

  1. STATIC_INLINE VOID OsSwtmrDelete(SWTMR_CTRL_S *swtmr) 
  2.     /* insert to free list */ 
  3.     swtmr->pstNext = g_swtmrFreeList; 
  4.     g_swtmrFreeList = swtmr; 
  5.     swtmr->ucState = OS_SWTMR_STATUS_UNUSED; 
  6.  
  7. #if (LOSCFG_BASE_CORE_SWTMR_ALIGN == 1) 
  8.     (VOID)memset_s((VOID *)&g_swtmrAlignID[swtmr->usTimerID % LOSCFG_BASE_CORE_SWTMR_LIMIT], 
  9.                    sizeof(SwtmrAlignData), 0, sizeof(SwtmrAlignData)); 
  10. #endif 

 3.3 定时器启动

创建完毕定时器后,我们可以使用函数LOS_SwtmrStart(UINT32 swtmrId)来启动定时器,下面通过分析源码看看如何启动定时器的。

⑴处判断定时器swtmrId是否超过OS_SWTMR_MAX_TIMERID,如果超过则返回错误码。如果定时器编号没有问题,获取定时器控制块LosSwtmrCB *swtmr。⑵处判断要启动的定时器swtmrId是否匹配,不匹配则返回错误码。⑶处判断定时器的状态,如果定时器定时器没有创建,不能启动。如果定时器计时中,需要先停止OsSwtmrStop(swtmr),然后再启动OsSwtmrStart(swtmr)。

  1. LITE_OS_SEC_TEXT UINT32 LOS_SwtmrStart(UINT32 swtmrId) 
  2.     UINT32 intSave; 
  3.     UINT32 ret = LOS_OK; 
  4.  
  5. ⑴  if (swtmrId >= OS_SWTMR_MAX_TIMERID) { 
  6.         return LOS_ERRNO_SWTMR_ID_INVALID; 
  7.     } 
  8.  
  9.     intSave = LOS_IntLock(); 
  10.     SWTMR_CTRL_S *swtmr = g_swtmrCBArray + swtmrId % LOSCFG_BASE_CORE_SWTMR_LIMIT; 
  11. ⑵  if (swtmr->usTimerID != swtmrId) { 
  12.         LOS_IntRestore(intSave); 
  13.         return LOS_ERRNO_SWTMR_ID_INVALID; 
  14.     } 
  15.  
  16. #if (LOSCFG_BASE_CORE_SWTMR_ALIGN == 1) 
  17.     if ((swtmr->ucSensitive == OS_SWTMR_ALIGN_INSENSITIVE) && (swtmr->ucMode == LOS_SWTMR_MODE_PERIOD)) { 
  18.         UINT32 swtmrAlignIdIndex = swtmr->usTimerID % LOSCFG_BASE_CORE_SWTMR_LIMIT; 
  19.         g_swtmrAlignID[swtmrAlignIdIndex].canAlign = 1; 
  20.         if ((swtmr->uwInterval % LOS_COMMON_DIVISOR) == 0) { 
  21.             g_swtmrAlignID[swtmrAlignIdIndex].canMultiple = 1; 
  22.             g_swtmrAlignID[swtmrAlignIdIndex].times = swtmr->uwInterval / LOS_COMMON_DIVISOR; 
  23.         } 
  24.     } 
  25. #endif 
  26.  
  27. ⑶  switch (swtmr->ucState) { 
  28.         case OS_SWTMR_STATUS_UNUSED: 
  29.             ret = LOS_ERRNO_SWTMR_NOT_CREATED; 
  30.             break; 
  31.         case OS_SWTMR_STATUS_TICKING: 
  32.             OsSwtmrStop(swtmr); 
  33.             /* fall through */ 
  34.         case OS_SWTMR_STATUS_CREATED: 
  35.             OsSwtmrStart(swtmr); 
  36.             break; 
  37.         default
  38.             ret = LOS_ERRNO_SWTMR_STATUS_INVALID; 
  39.             break; 
  40.     } 
  41.  
  42.     LOS_IntRestore(intSave); 
  43.     return ret; 

 接下来,我们继续看看如何调用函数OsSwtmrStart(swtmr)启动定时器。函数特别简单,⑴设置定时器的等待超时时间,并把定时器状态改为计时中。⑵处把该定时器插入超时排序链表中。如果已使能任务调度,则修改过期时间。

  1. LITE_OS_SEC_TEXT VOID OsSwtmrStart(SWTMR_CTRL_S *swtmr) 
  2.     UINT64 currTime = OsGetCurrSchedTimeCycle(); 
  3.  
  4. ⑴  swtmr->uwCount = swtmr->uwInterval; 
  5.     swtmr->ucState = OS_SWTMR_STATUS_TICKING; 
  6.  
  7. #if (LOSCFG_BASE_CORE_SWTMR_ALIGN == 1) 
  8.     if ((g_swtmrAlignID[swtmr->usTimerID % LOSCFG_BASE_CORE_SWTMR_LIMIT].canAlign == 1) && 
  9.         (g_swtmrAlignID[swtmr->usTimerID % LOSCFG_BASE_CORE_SWTMR_LIMIT].isAligned == 0)) { 
  10.         g_swtmrAlignID[swtmr->usTimerID % LOSCFG_BASE_CORE_SWTMR_LIMIT].isAligned = 1; 
  11.         OsSwtmrFindAlignPos(currTime, swtmr); 
  12.     } 
  13. #endif 
  14. ⑵  OsAdd2SortLink(&swtmr->stSortList, currTime, swtmr->uwCount, OS_SORT_LINK_SWTMR); 
  15.     if (LOS_TaskIsRunning()) { 
  16. ⑶      OsSchedUpdateExpireTime(currTime); 
  17.     } 

 3.4 定时器停止

我们可以使用函数LOS_SwtmrStop(UINT32 swtmrId)来停止定时器,下面通过分析源码看看如何停止定时器的。

⑴处判断定时器swtmrId是否超过OS_SWTMR_MAX_TIMERID,如果超过则返回错误码。如果定时器编号没有问题,获取定时器控制块LosSwtmrCB *swtmr。⑵处判断要启动的定时器swtmrId是否匹配,不匹配则返回错误码。⑶处判断定时器的状态,如果定时器定时器没有创建,没有启动,不能停止。如果定时器计时中,会继续调用OsSwtmrStop(swtmr)停止定时器。

  1. LITE_OS_SEC_TEXT UINT32 LOS_SwtmrStop(UINT32 swtmrId) 
  2.     SWTMR_CTRL_S *swtmr = NULL
  3.     UINT32 intSave; 
  4.     UINT16 swtmrCbId; 
  5.     UINT32 ret = LOS_OK; 
  6.  
  7. ⑴  if (swtmrId >= OS_SWTMR_MAX_TIMERID) { 
  8.         return LOS_ERRNO_SWTMR_ID_INVALID; 
  9.     } 
  10.     intSave = LOS_IntLock(); 
  11.     swtmrCbId = swtmrId % LOSCFG_BASE_CORE_SWTMR_LIMIT; 
  12.     swtmr = g_swtmrCBArray + swtmrCbId; 
  13. ⑵  if (swtmr->usTimerID != swtmrId) { 
  14.         LOS_IntRestore(intSave); 
  15.         return LOS_ERRNO_SWTMR_ID_INVALID; 
  16.     } 
  17.  
  18. ⑶  switch (swtmr->ucState) { 
  19.         case OS_SWTMR_STATUS_UNUSED: 
  20.             ret = LOS_ERRNO_SWTMR_NOT_CREATED; 
  21.             break; 
  22.         case OS_SWTMR_STATUS_CREATED: 
  23.             ret = LOS_ERRNO_SWTMR_NOT_STARTED; 
  24.             break; 
  25.         case OS_SWTMR_STATUS_TICKING: 
  26.             OsSwtmrStop(swtmr); 
  27.             break; 
  28.         default
  29.             ret = LOS_ERRNO_SWTMR_STATUS_INVALID; 
  30.             break; 
  31.     } 
  32.  
  33.     LOS_IntRestore(intSave); 
  34.     return ret; 

接下来,我们继续看看如何调用函数OsSwtmrStop(swtmr)停止定时器。函数特别简单,⑴处从排序链表中删除该定时器的排序链表节点,更改定时器的状态。⑵如果已使能任务调度,则修改过期时间。

  1. LITE_OS_SEC_TEXT VOID OsSwtmrStop(SWTMR_CTRL_S *swtmr) 
  2. ⑴  OsDeleteSortLink(&swtmr->stSortList, OS_SORT_LINK_SWTMR); 
  3.     swtmr->ucState = OS_SWTMR_STATUS_CREATED; 
  4.  
  5.     if (LOS_TaskIsRunning()) { 
  6. ⑵       OsSchedUpdateExpireTime(OsGetCurrSchedTimeCycle()); 
  7. #if (LOSCFG_BASE_CORE_SWTMR_ALIGN == 1) 
  8.         g_swtmrAlignID[swtmr->usTimerID % LOSCFG_BASE_CORE_SWTMR_LIMIT].isAligned = 0; 
  9. #endif 
  10.     } 

 4、定时器和Tick时间关系

定时器加入到超时排序链表后,随时时间一个tick一个tick的逝去,需要不断的检查定时器是否超时到期。从之前的文章,已经知道系统每走过一个tick,系统就会调用一次Tick中断的处理函数OsTickHandler(),该函数会调用定时器扫描函数OsSwtmrScan()来扫描、更新定时器时间。我们看下OsSwtmrScan()的代码。

⑴处获取超时排序链表的链表节点listObject,⑵判断排序链表是否为空,为空则返回。⑶获取排序链表的下一个链表节点sortList。⑷循环遍历超时排序链表上响应时间小于等于当前时间的链表节点,意味着定时器到期,需要处理定时器的回调函数。⑸从超时排序链表中删除超时的节点,⑹获取定时器控制块SWTMR_CTRL_S *swtmr,调用函数OsSwtmrTimeoutHandle(swtmr)执行定时器回调函数,并设置需要调度的标记needSchedule。⑺如果超时排序链表为空则终止循环。

  1. STATIC BOOL OsSwtmrScan(VOID) 
  2.     BOOL needSchedule = FALSE
  3. ⑴  LOS_DL_LIST *listObject = &g_swtmrSortLinkList->sortLink; 
  4.  
  5. ⑵  if (LOS_ListEmpty(listObject)) { 
  6.         return needSchedule; 
  7.     } 
  8.  
  9. ⑶  SortLinkList *sortList = LOS_DL_LIST_ENTRY(listObject->pstNext, SortLinkList, sortLinkNode); 
  10.     UINT64 currTime = OsGetCurrSchedTimeCycle(); 
  11. ⑷  while (sortList->responseTime <= currTime) { 
  12. ⑸      OsDeleteNodeSortLink(g_swtmrSortLinkList, sortList); 
  13.  
  14. ⑹      SWTMR_CTRL_S *swtmr = LOS_DL_LIST_ENTRY(sortList, SWTMR_CTRL_S, stSortList); 
  15.         OsSwtmrTimeoutHandle(swtmr); 
  16.  
  17.         needSchedule = TRUE
  18. ⑺      if (LOS_ListEmpty(listObject)) { 
  19.             break; 
  20.         } 
  21.  
  22.         sortList = LOS_DL_LIST_ENTRY(listObject->pstNext, SortLinkList, sortLinkNode); 
  23.     } 
  24.  
  25.     return needSchedule; 

 我们最后看下函数OsSwtmrTimeoutHandle()。⑴处把定时器回调函数写入定时器队列。⑵如果是一次性定时器,会把这个定时器删除,回收到空闲定时器链表,状态设置为未使用状态,然后更新定时器的编号timerId。⑶如果定时器属于周期性定时器,重新启动定时器。⑷如果是一次性定时器但不删除,则把定时器状态设置为创建状态。

  1. STATIC VOID OsSwtmrTimeoutHandle(SWTMR_CTRL_S *swtmr) 
  2.     SwtmrHandlerItem swtmrHandler; 
  3.  
  4.     swtmrHandler.handler = swtmr->pfnHandler; 
  5.     swtmrHandler.arg = swtmr->uwArg; 
  6.  
  7. ⑴  (VOID)LOS_QueueWriteCopy(g_swtmrHandlerQueue, &swtmrHandler, sizeof(SwtmrHandlerItem), LOS_NO_WAIT); 
  8. ⑵  if (swtmr->ucMode == LOS_SWTMR_MODE_ONCE) { 
  9.         OsSwtmrDelete(swtmr); 
  10.         if (swtmr->usTimerID < (OS_SWTMR_MAX_TIMERID - LOSCFG_BASE_CORE_SWTMR_LIMIT)) { 
  11.             swtmr->usTimerID += LOSCFG_BASE_CORE_SWTMR_LIMIT; 
  12.         } else { 
  13.             swtmr->usTimerID %= LOSCFG_BASE_CORE_SWTMR_LIMIT; 
  14.         } 
  15. ⑶  } else if (swtmr->ucMode == LOS_SWTMR_MODE_PERIOD) { 
  16.         OsSwtmrStart(swtmr); 
  17. ⑷  } else if (swtmr->ucMode == LOS_SWTMR_MODE_NO_SELFDELETE) { 
  18.         swtmr->ucState = OS_SWTMR_STATUS_CREATED; 
  19.     } 

 小结

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

想了解更多内容,请访问:

51CTO和华为官方合作共建的鸿蒙技术社区

https://harmonyos.51cto.com

 

责任编辑:jianghua 来源: 鸿蒙社区
相关推荐

2021-05-27 09:43:56

鸿蒙HarmonyOS应用

2021-06-04 09:57:49

鸿蒙HarmonyOS应用

2022-01-12 10:50:23

鸿蒙HarmonyOS应用

2022-01-10 15:31:44

鸿蒙HarmonyOS应用

2021-05-25 09:28:34

鸿蒙HarmonyOS应用

2021-10-20 16:08:57

鸿蒙HarmonyOS应用

2021-06-04 14:15:10

鸿蒙HarmonyOS应用

2021-05-17 09:28:59

鸿蒙HarmonyOS应用

2021-05-08 15:14:50

鸿蒙HarmonyOS应用

2021-05-31 20:30:55

鸿蒙HarmonyOS应用

2022-04-13 11:02:12

鸿蒙事件模块事件Event

2022-03-03 18:28:28

Harmony进程任务管理模块

2021-05-21 09:25:11

鸿蒙HarmonyOS应用

2021-05-11 09:54:55

鸿蒙HarmonyOS应用

2022-03-11 20:23:14

鸿蒙源码分析进程管理

2021-09-22 14:36:32

鸿蒙HarmonyOS应用

2021-07-06 09:45:03

鸿蒙HarmonyOS应用

2021-05-12 09:45:20

鸿蒙HarmonyOS应用

2021-05-10 15:05:56

鸿蒙HarmonyOS应用

2021-04-30 15:06:34

鸿蒙HarmonyOS应用
点赞
收藏

51CTO技术栈公众号