SylixOS 中的软件定时器
1、基本原理
SylixOS 里的软件定时器是通过 普通定时器等待唤醒链表 和 高速定时器等待唤醒链表 来实现的,所有操作都是围绕这两个链表进行,不同的是普通定时器等待唤醒链表是在线程上下文中判定时间和回调的,而高速定时器等待唤醒链表是在系统 Tick 中断中进行的,他俩的最小有效定时器精度通常为一个 Tick。
struct sigevent;
typedef struct {LW_LIST_MONO TIMER_monoResrcList; /* 定时器资源表 */LW_CLASS_WAKEUP_NODE TIMER_wunTimer; /* 等待唤醒链表 */
#define TIMER_ulCounter TIMER_wunTimer.WUN_ulCounterUINT8 TIMER_ucType; /* 定时器类型 */ULONG TIMER_ulCounterSave; /* 定时器计数值保留值 */ULONG TIMER_ulOption; /* 定时器操作选项 */UINT8 TIMER_ucStatus; /* 定时器状态 */PTIMER_CALLBACK_ROUTINE TIMER_cbRoutine; /* 执行函数 */PVOID TIMER_pvArg; /* 定时器参数 */UINT16 TIMER_usIndex; /* 数组中的索引 */#if LW_CFG_PTIMER_AUTO_DEL_EN > 0LW_OBJECT_HANDLE TIMER_ulTimer;
#endif /* LW_CFG_PTIMER_AUTO_DEL_EN */LW_OBJECT_HANDLE TIMER_ulThreadId; /* 线程 ID */struct sigevent TIMER_sigevent; /* 定时器信号相关属性 *//* SIGEV_THREAD 必须使能 POSIX */UINT64 TIMER_u64Overrun; /* timer_getoverrun */clockid_t TIMER_clockid; /* 仅对 POSIX 定时器有效 */#if LW_CFG_TIMERFD_EN > 0PVOID TIMER_pvTimerfd; /* timerfd 结构 */
#endif /* LW_CFG_TIMERFD_EN > 0 */CHAR TIMER_cTmrName[LW_CFG_OBJECT_NAME_SIZE]; /* 定时器名 */LW_SPINLOCK_DEFINE (TIMER_slLock); /* 自旋锁 */
} LW_CLASS_TIMER;
typedef LW_CLASS_TIMER *PLW_CLASS_TIMER;
2、接口详解
LW_API
LW_OBJECT_HANDLE API_TimerCreate (CPCHAR pcName,ULONG ulOption,LW_OBJECT_ID *pulId)
{REGISTER PLW_CLASS_TIMER ptmr;....../* 根据 ulOption 选项控制,创建的是普通定时器还是高速定时器 */if (ulOption & LW_OPTION_ITIMER) { /* 应用级定时器 */ptmr->TIMER_ucType = LW_TYPE_TIMER_ITIMER; /* 定时器类型 */} else {ptmr->TIMER_ucType = LW_TYPE_TIMER_HTIMER;}......ulIdTemp = _MakeObjectId(_OBJECT_TIMER, LW_CFG_PROCESSOR_NUMBER, ptmr->TIMER_usIndex); /* 构建对象 id */....../* 返回创建好的定时器句柄 */return (ulIdTemp);
}
启动一个定时器
LW_API
ULONG API_TimerStartEx (LW_OBJECT_HANDLE ulId,ULONG ulInitCounter,ULONG ulCounter,ULONG ulOption,PTIMER_CALLBACK_ROUTINE cbTimerRoutine,PVOID pvArg)
{INTREG iregInterLevel;REGISTER UINT16 usIndex;REGISTER PLW_CLASS_TIMER ptmr;/* 根据入参 ulId 定时器句柄,找到定时器结构 */usIndex = _ObjectGetIndex(ulId);......ptmr = &_K_tmrBuffer[usIndex];....../* 这里就能看出区别,不同类型的定时器,加入的链表是不同的 */ if (ptmr->TIMER_ucType == LW_TYPE_TIMER_ITIMER) { /* 加入扫描队列 */_WakeupAdd(&_K_wuITmr, &ptmr->TIMER_wunTimer, LW_TRUE);} else {_WakeupAdd(&_K_wuHTmr, &ptmr->TIMER_wunTimer, LW_FALSE);}
......
}
_K_wuITmr
、_K_wuHTmr
是全局变量
/*********************************************************************************************************定时器
*********************************************************************************************************/
__KERNEL_EXT LW_CLASS_WAKEUP _K_wuHTmr; /* 高速定时器管理表 */
__KERNEL_EXT LW_CLASS_WAKEUP _K_wuITmr; /* 普通定时器管理表 */
同时,要注意到,_WakeupAdd
将节点加入链表的过程中,不是随意加的,是按照时间的长短进行排序:
VOID _WakeupAdd (PLW_CLASS_WAKEUP pwu, PLW_CLASS_WAKEUP_NODE pwun, BOOL bProcTime)
{
....../* 按照时间顺序进行链表插入,这里的 WUN_ulCounter 是相对等待时间,时间单位为 tick */while (plineTemp) {pwunTemp = _LIST_ENTRY(plineTemp, LW_CLASS_WAKEUP_NODE, WUN_lineManage);if (pwun->WUN_ulCounter >= pwunTemp->WUN_ulCounter) { /* 需要继续向后找 */pwun->WUN_ulCounter -= pwunTemp->WUN_ulCounter;plineTemp = _list_line_get_next(plineTemp);} else {if (plineTemp == pwu->WU_plineHeader) { /* 如果是链表头 */_List_Line_Add_Ahead(&pwun->WUN_lineManage, &pwu->WU_plineHeader);} else {_List_Line_Add_Left(&pwun->WUN_lineManage, plineTemp); /* 不是表头则插在左边 */}pwunTemp->WUN_ulCounter -= pwun->WUN_ulCounter; /* 右侧的点重新计算计数器 */break;}}
....../* * 如果是 _K_wuITmr 普通定时器,则这里会调用 WU_pfuncWakeup 函数,唤醒普通定时器线程* 注意,普通定时器线程被唤醒后,会从系统的 _K_wuDelay 链表删除*/if (bProcTime && pwu->WU_pfuncWakeup) {if (bSaveTime) {__KERNEL_TIME_GET(pwu->WU_i64LastTime, INT64);}pwu->WU_pfuncWakeup(pwu->WU_pvWakeupArg); /* 唤醒 */}
}
2.1 普通定时器
普通定时器 _K_wuITmr
是在线程中,去更新、维护、唤醒
PVOID _ITimerThread (PVOID pvArg)
{INTREG iregInterLevel;REGISTER PLW_CLASS_TIMER ptmr;PTIMER_CALLBACK_ROUTINE pfuncRoutine;PVOID pvRoutineArg;PLW_CLASS_TCB ptcbCur;PLW_CLASS_PCB ppcb;PLW_CLASS_WAKEUP_NODE pwun;INT64 i64CurTime;ULONG ulCounter;BOOL bNoTimer;(VOID)pvArg;LW_TCB_GET_CUR_SAFE(ptcbCur); /* 当前任务控制块 */for (;;) {iregInterLevel = __KERNEL_ENTER_IRQ(); /* 进入内核同时关闭中断 */__KERNEL_TIME_GET_IGNIRQ(_K_wuITmr.WU_i64LastTime, INT64); /* 原始时间 *//* 因为 _K_wuITmr 链表是经过排序的,链表第一个成员,是需要等待时间最短的 */__WAKEUP_GET_FIRST(&_K_wuITmr, pwun); /* 获得第一个节点 */if (pwun) {ulCounter = pwun->WUN_ulCounter; /* 已第一个节点等待时间 Sleep */bNoTimer = LW_FALSE;} else {ulCounter = LW_ITIMER_IDLE_TICK; /* 没有任何节点 (会被自动唤醒) */bNoTimer = LW_TRUE;}/* 将当前线程从就绪表中删除 */ppcb = _GetPcb(ptcbCur);__DEL_FROM_READY_RING(ptcbCur, ppcb); /* 从就绪表中删除 *//* * 将当前线程 TCP 的等待唤醒时间设置为 ulCounter,并加入到系统的 _K_wuDelay 超时唤醒链表* 为什么能多次对同一个 tcb 去 ADD 到 _K_wuDelay 链表?因为每一次被唤醒,该 tcb 都会被从 _K_wuDelay 链表中删除* 这里只是更新 TCB_ulDelay 时间,再重新将其加入 _K_wuDelay 链表*/ptcbCur->TCB_ulDelay = ulCounter;__ADD_TO_WAKEUP_LINE(ptcbCur); /* 加入等待扫描链 *//* 退出内核时,会进行调度,因为当前线程已经被从就绪表删除,这里会调度其他线程运行 */__KERNEL_EXIT_IRQ(iregInterLevel); /* 退出内核同时打开中断 *//* 当前线程被唤醒时,会从当前开始运行 */iregInterLevel = __KERNEL_ENTER_IRQ(); /* 进入内核同时关闭中断 */if (bNoTimer) {__KERNEL_EXIT_IRQ(iregInterLevel); /* 退出内核同时打开中断 */continue;}/* 计算真正的睡眠时间(因为可能会有抖动、误差) */__KERNEL_TIME_GET_IGNIRQ(i64CurTime, INT64); /* 获得 Sleep 后时间 */ulCounter = (ULONG)(i64CurTime - _K_wuITmr.WU_i64LastTime); /* 真正睡眠时间 */_K_wuITmr.WU_i64LastTime = i64CurTime;/* 针对 _K_wuITmr 链表上所有节点,都减去 ulCounter 值 */__WAKEUP_PASS_FIRST(&_K_wuITmr, pwun, ulCounter);ptmr = _LIST_ENTRY(pwun, LW_CLASS_TIMER, TIMER_wunTimer);/* 对于已经达到时间的节点,进行删除 */_WakeupDel(&_K_wuITmr, pwun, LW_FALSE);/* 这里会根据定时器模式(one shot 还是 continue 模式进行重载) */if (ptmr->TIMER_ulOption & LW_OPTION_AUTO_RESTART) {ptmr->TIMER_ulCounter = ptmr->TIMER_ulCounterSave;_WakeupAdd(&_K_wuITmr, pwun, LW_FALSE);} else {ptmr->TIMER_ucStatus = LW_TIMER_STATUS_STOP; /* 填写停止标志位 */}pfuncRoutine = ptmr->TIMER_cbRoutine;pvRoutineArg = ptmr->TIMER_pvArg;__KERNEL_EXIT_IRQ(iregInterLevel); /* 退出内核同时打开中断 */LW_SOFUNC_PREPARE(pfuncRoutine);pfuncRoutine(pvRoutineArg);iregInterLevel = __KERNEL_ENTER_IRQ(); /* 进入内核同时关闭中断 */__WAKEUP_PASS_SECOND();KN_INT_ENABLE(iregInterLevel); /* 这里允许响应中断 */iregInterLevel = KN_INT_DISABLE();__WAKEUP_PASS_END();__KERNEL_EXIT_IRQ(iregInterLevel); /* 退出内核同时打开中断 */}return (LW_NULL);
}
2.2 高速定时器
高速定时器 _K_wuHTmr
在 API_TimerHTicks
函数中去更新、维护、唤醒。API_TimerHTicks
函数在 Tick 中断中被调用。
LW_API
VOID API_TimerHTicks (VOID)
{INTREG iregInterLevel;REGISTER PLW_CLASS_TIMER ptmr;PLW_CLASS_WAKEUP_NODE pwun;PTIMER_CALLBACK_ROUTINE pfuncRoutine;PVOID pvRoutineArg;ULONG ulCounter = 1;iregInterLevel = __KERNEL_ENTER_IRQ(); /* 进入内核同时关闭中断 *//* 这里是个 for 循环,针对 _K_wuHTmr 链表上的所有节点,进行减一个 counter (一个 counter = 1个 tick)的操作 */__WAKEUP_PASS_FIRST(&_K_wuHTmr, pwun, ulCounter);ptmr = _LIST_ENTRY(pwun, LW_CLASS_TIMER, TIMER_wunTimer);/* 针对已经到达定时时间的节点,将其从链表中删除 */_WakeupDel(&_K_wuHTmr, pwun, LW_FALSE);if (ptmr->TIMER_ulOption & LW_OPTION_AUTO_RESTART) {ptmr->TIMER_ulCounter = ptmr->TIMER_ulCounterSave;_WakeupAdd(&_K_wuHTmr, pwun, LW_FALSE);} else {ptmr->TIMER_ucStatus = LW_TIMER_STATUS_STOP; /* 填写停止标志位 */}pfuncRoutine = ptmr->TIMER_cbRoutine;pvRoutineArg = ptmr->TIMER_pvArg;__KERNEL_EXIT_IRQ(iregInterLevel); /* 退出内核同时打开中断 *//* 执行定时器的执行函数 */LW_SOFUNC_PREPARE(pfuncRoutine);pfuncRoutine(pvRoutineArg);iregInterLevel = __KERNEL_ENTER_IRQ(); /* 进入内核同时关闭中断 */__WAKEUP_PASS_SECOND();KN_INT_ENABLE(iregInterLevel); /* 这里允许响应中断 */iregInterLevel = KN_INT_DISABLE();__WAKEUP_PASS_END();__KERNEL_EXIT_IRQ(iregInterLevel); /* 退出内核同时打开中断 */
}