軟定時器
所謂軟定時器,是由一個線程運行維護的定時器列表。由線程調(diào)用定時器回調(diào)函數(shù)。
相對硬定時器,是由中斷(SysTick)維護的定時器列表,并在中斷中調(diào)用定時器回調(diào)函數(shù)。
另外,還有一種*硬件定時器*,這個和單片機里的定時器是一個概念,由外設定時器實現(xiàn)定時。和 rt-thread 提供的硬定時器是兩個不同概念。
對硬定時器回調(diào)函數(shù)有嚴格的執(zhí)行時間要求,而且不能調(diào)用任何在中斷中不能調(diào)用的函數(shù)??傊荒苡腥魏尾荒茉谥袛嘀袌?zhí)行的操作。
那么,軟定時器呢,要求需要這么嚴格嗎?
比如有個處理執(zhí)行時間 10ms,比如在定時器中斷函數(shù)里發(fā)送個等待 10ms 的消息...
無論是硬定時器還是軟定時器,它們各自有一個定時器列表。列表中的定時器根據(jù)定時時間長短排序,定時時間短的在前。掃描這個列表中所有定時器,直到結尾或者出現(xiàn)第一個定時時間未到的定時器節(jié)點。當判斷出定時時間到的時候,調(diào)用定時器回調(diào)函數(shù)。
假如,某次掃描這個列表中多于一個定時器到達定時時間,也就是需要執(zhí)行兩個以上的定時器回調(diào)函數(shù)。并且前一個掃描到的定時器的回調(diào)函數(shù)執(zhí)行時間比較長,出現(xiàn)上面設想的某一種使用情況。這時候會出現(xiàn)什么效果?
因為硬定時器的種種硬性要求,以下討論只針對軟定時器。
從對 `rt_soft_timer_check` 的幾個疑問講起
先擺出官方的 rt_soft_timer_check 函數(shù),實現(xiàn)。這個函數(shù)是來掃描定時器列表中到達定時時間的定時器,并調(diào)用定時器回調(diào)函數(shù)的。
```
void rt_soft_timer_check(void)
{
rt_tick_t current_tick;
struct rt_timer *t;
register rt_base_t level;
rt_list_t list;
rt_list_init(&list);
RT_DEBUG_LOG(RT_DEBUG_TIMER, ("software timer check enter\n"));
/* disable interrupt */
level = rt_hw_interrupt_disable();
while (!rt_list_isempty(&rt_soft_timer_list[RT_TIMER_SKIP_LIST_LEVEL - 1]))
{
t = rt_list_entry(rt_soft_timer_list[RT_TIMER_SKIP_LIST_LEVEL - 1].next,
struct rt_timer, row[RT_TIMER_SKIP_LIST_LEVEL - 1]);
current_tick = rt_tick_get();
/*
* It supposes that the new tick shall less than the half duration of
* tick max.
*/
if ((current_tick - t->timeout_tick) < RT_TICK_MAX / 2)
{
RT_OBJECT_HOOK_CALL(rt_timer_enter_hook, (t));
/* remove timer from timer list firstly */
_rt_timer_remove(t);
if (!(t->parent.flag & RT_TIMER_FLAG_PERIODIC))
{
t->parent.flag &= ~RT_TIMER_FLAG_ACTIVATED;
}
/* add timer to temporary list */
rt_list_insert_after(&list, &(t->row[RT_TIMER_SKIP_LIST_LEVEL - 1]));
soft_timer_status = RT_SOFT_TIMER_BUSY;
/* enable interrupt */
rt_hw_interrupt_enable(level);
/* call timeout function */
t->timeout_func(t->parameter);
RT_OBJECT_HOOK_CALL(rt_timer_exit_hook, (t));
RT_DEBUG_LOG(RT_DEBUG_TIMER, ("current tick: %d\n", current_tick));
/* disable interrupt */
level = rt_hw_interrupt_disable();
soft_timer_status = RT_SOFT_TIMER_IDLE;
/* Check whether the timer object is detached or started again */
if (rt_list_isempty(&list))
{
continue;
}
rt_list_remove(&(t->row[RT_TIMER_SKIP_LIST_LEVEL - 1]));
if ((t->parent.flag & RT_TIMER_FLAG_PERIODIC) &&
(t->parent.flag & RT_TIMER_FLAG_ACTIVATED))
{
/* start it */
t->parent.flag &= ~RT_TIMER_FLAG_ACTIVATED;
rt_timer_start(t);
}
}
else break; /* not check anymore */
}
/* enable interrupt */
rt_hw_interrupt_enable(level);
RT_DEBUG_LOG(RT_DEBUG_TIMER, ("software timer check leave\n"));
}
疑問一 `rt_list_t list` 這個臨時中間變量的作用是什么?
- 進入 `rt_soft_timer_check` 函數(shù)后,先初始化 list 變量。
- 掃描定時器列表,當有到時的定時器,把這個定時器從軟定時器列表 `rt_soft_timer_list` 移除,插入到這個 list 臨時存放。
- 開中斷,調(diào)用定時器回調(diào)函數(shù),關中斷
- 把 list 中存放的定時器移除
- 判斷定時器的周期定時器還是一次性定時器
- 繼續(xù)掃描定時器列表
list 是個局部變量,僅僅起臨時存放當前這個定時器的作用。看似是一種穩(wěn)妥的做法。
但是,這樣做的初衷是什么?
為什么這個定時器一定要放到某個列表里?
如果從軟定時器列表 `rt_soft_timer_list` 移除后,不插入任何列表會有什么影響?
因為退出 `rt_soft_timer_check` 函數(shù)后,list 列表不復存在了,應該不是退出 `rt_soft_timer_check` 函數(shù)后的需求,那么插入 list 和 從 list 取出之間有哪些情況需要我們注意,需要用一個臨時列表將軟定時器暫存?
定時器回調(diào)函數(shù)里可能發(fā)生哪些操作?
- 修改定時器設置
- 停止,重啟定時器
- 刪除定時器
- 發(fā)生中斷,在中斷里執(zhí)行上述三種操作
修改定時器設置,可能只涉及到定時器的定時時間間隔和定時周期特性。這兩個參數(shù)設置需要定時器必須在某個列表中嗎?
停止,重啟定時器,必然導致修改定時器所在列表指針,這里就涉及到雙向列表的操作了。
> 簡短介紹一下雙向列表,
- rt-thread 使用的雙向列表,每一個節(jié)點有一個 prev 和 一個 next 指針,分別指向雙向列表中的前一個節(jié)點和后一個節(jié)點。
- 一個空列表 l 僅有一個不含數(shù)據(jù)的節(jié)點,此節(jié)點的 prev next 指針均指向它自己。
- 任何一個帶數(shù)據(jù)的列表節(jié)點必須進行初始化,使得它的 prev next 分別指向它自己,這一點和空列表 l 完全雷同!換句話說,***任何一個雙向列表節(jié)點均有作為鏈表的潛質(zhì)***。從操作上講,你可以定義兩個定時器,然后這兩個定時器之間構建一個含有兩個節(jié)點的雙向列表,當然,這種做法沒有多少實用意義。
- 從鏈表中移除的節(jié)點,**必須**使得它的 prev next 指針指向它自己。
- **無論一個節(jié)點是否在某個雙向鏈表中,或者僅僅是一個獨立節(jié)點,對它進行刪除操作,效果是完全一樣的!**
- 更多的操作詳見 rtservice.c 文件中相關函數(shù),`rt_list_init, rt_list_insert_after, rt_list_insert_before, rt_list_remove` 等等。
停止定時器會把當前定時器從定時器列表刪除,無論這個定時器有沒有在某個定時器列表中,或者只是一個獨立的定時器節(jié)點,刪除操作的結果都是一樣的,使用 list 這個臨時列表可能不能保護它不被刪除。
重啟定時器會把它先從前一個列表中刪除,然后插入軟定時器列表 `rt_soft_timer_list` 。list 這個臨時列表也阻止不了重啟定時器操作。
**至此,可以看出,`rt_list_t list` 這個臨時列表無任何存在意義**
疑問二 `soft_timer_status` 指示的是什么狀態(tài)?
這是一個全局靜態(tài)變量,它的使用也很簡單,只在四個地方使用了,上面的源碼函數(shù)里有兩處,其它兩個地方分別是初始化聲明
/* soft timer status */
static rt_uint8_t soft_timer_status = RT_SOFT_TIMER_IDLE;
和——以下摘自 `rt_timer_start` 函數(shù)
#ifdef RT_USING_TIMER_SOFT
if (timer->parent.flag & RT_TIMER_FLAG_SOFT_TIMER)
{
/* check whether timer thread is ready */
if ((soft_timer_status == RT_SOFT_TIMER_IDLE) &&
((timer_thread.stat & RT_THREAD_STAT_MASK) == RT_THREAD_SUSPEND))
{
/* resume timer thread to check soft timer */
rt_thread_resume(&timer_thread);
rt_schedule();
}
}
#endif /* RT_USING_TIMER_SOFT */
將這四個地方聯(lián)系起來看,意思好像是調(diào)用定時器回調(diào)函數(shù)前修改軟定時器為 busy 狀態(tài),返回回調(diào)函數(shù)后恢復為 idle 狀態(tài),而如果是在定時器回調(diào)函數(shù)里調(diào)用 `rt_timer_start` ,可以達到不進行任務調(diào)度的目的。好像是起了雙保險作用,真是這樣嗎?
我們分析一下上面這段 `rt_timer_start` 函數(shù)片段。
首先判斷定時器是不是軟定時器,只有軟定時器啟動時才有進行任務調(diào)度的可能。
其次,判斷 `soft_timer_status` 是否空閑,以及軟定時器線程是否***掛起態(tài)***。
以上仨條件均滿足,進行任務調(diào)度。
我們重點關注“其次”,一個定時器線程調(diào)用的定時器回調(diào)函數(shù),這個線程會是掛起態(tài)嗎?答案是肯定不是。它在運行著,肯定是 `RT_THREAD_RUNNING` 的。那么這個 `soft_timer_status` “雙保險”了嗎?
疑問三 開篇提到的假想
開篇提到了一種假想,假想軟定時器回調(diào)函數(shù)占用 cpu 時間有點兒長,會產(chǎn)生什么影響,引起什么后果。
討論這個問題仍然離不開 `rt_soft_timer_check` 函數(shù)工作原理,我們再梳理一下 `rt_soft_timer_check` 函數(shù)的操作。(以下分析忽略 list 以及 soft_timer_status 相關操作)
- 關中斷
- 掃描列表是否有節(jié)點
- 取出第一個節(jié)點
- ***獲取當前系統(tǒng) tick***
- 檢查定時器是否定時時間到,如果到時
- 先從軟定時器列表 `rt_soft_timer_list` 移除定時器。非周期定時器,取消激活態(tài)
- 開中斷
- 執(zhí)行定時器回調(diào)函數(shù)。(這里可能存在長時間操作)
- 關中斷
- 對于周期性定時器,重啟定時器;非周期定時器,前面已經(jīng)做了取消激活態(tài)操作。
- 繼續(xù)掃描列表,取出第一個節(jié)點,***獲取當前系統(tǒng) tick*** ,檢查定時器是否定時時間到。。。
假設 RT_TICK_PER_SECOND = 1000,有兩個周期性定時器 t0 t1 ,定時間隔不同,同時啟動,各自的定時器回調(diào)函數(shù)執(zhí)行時間 t0 500us,t1 5 ms。
經(jīng)過一段時間以后,總是可能會出現(xiàn)定時間隔公倍數(shù)時刻 Tn ,它們倆同時達到定時時間。
如果 t1 先被處理,那么 t1 重啟的時候系統(tǒng) tick 已經(jīng)是 Tn + 5;t0 的重啟時間有 50% 的可能是 Tn + 5, 50%的可能是 Tn + 6。
如果 t0 先被處理,t0 的重啟時間有 50% 的可能是 Tn, 50%的可能是 Tn + 1;t1 重啟時間是 Tn + 5 或 Tn + 6。
即便不考慮 t0 不考慮它對外的影響,也不考慮它受到的影響,僅僅分析 t1 自己對自己的影響,也可以看出來,隨著時間的推移,它的定時間隔不是初始設定的 Inv ,而是 Inv + 5。
優(yōu)化后的 `rt_soft_timer_check` 流程
- ***獲取當前系統(tǒng) tick***
- 關中斷
- 掃描列表是否有節(jié)點
- 取出第一個節(jié)點
- 檢查定時器是否定時時間到,如果到時
- 先從軟定時器列表 `rt_soft_timer_list` 移除定時器。
- 對于周期性定時器,***重啟定時器***;非周期定時器,取消激活態(tài)。
- 開中斷
- 執(zhí)行定時器回調(diào)函數(shù)。(這里可能存在長時間操作)
- 關中斷
- 繼續(xù)掃描列表,取出第一個節(jié)點,檢查定時器是否定時時間到。。。
其中,優(yōu)化后的重啟定時器不能使用原來的接口。需要使用如下原型函數(shù)接口
static rt_err_t _rt_timer_start(rt_timer_t timer, rt_tick_t current_tick)
第二個參數(shù)是進入 `rt_soft_timer_check` 函數(shù),第一次關中斷前獲取的當前系統(tǒng) `tick` 值,無論下面掃描出多少個到達時間的定時器,啟動時間都是同一個 `tick` 值。
而且無論其中某個定時器回調(diào)函數(shù)執(zhí)行時間有多長,或者多個回調(diào)函數(shù)累積執(zhí)行時間有多長,它們的啟動時間都是相同的!
注:由此,引起的另一個弊端的,期間某個定時器定時時間到了,但是會被判定為未到,下次調(diào)用 `rt_soft_timer_check` 時才會被處理。
附測試程序
static void led1_timeout(void *parameter)
{
rt_tick_t current_tick;
int pin = rt_pin_read(LED1_PIN);
rt_pin_write(LED1_PIN, !pin);
current_tick = rt_tick_get();
rt_hw_us_delay(50000);
}
void led_tick_thread(void *parameter)
{
rt_timer_t led1_timer;
led1_timer = rt_timer_create("ledtim1", led1_timeout,
RT_NULL, 1000,
RT_TIMER_FLAG_PERIODIC | RT_TIMER_FLAG_SOFT_TIMER);
if (led1_timer != RT_NULL) {
rt_timer_start(led1_timer);
}
while (1) {
rt_pin_write(LED0_PIN, PIN_HIGH);
rt_thread_mdelay(500);
rt_pin_write(LED0_PIN, PIN_LOW);
rt_thread_mdelay(500);
}
}
作為對比,兩個 led 一個用軟定時器控制亮滅頻率,一個用 mdelay 延時控制亮滅頻率。
如果 timeout 沒有延遲,兩個燈一直是同步的;有延遲后,過一段時間兩個燈亮滅變不同步了。
總結
肯定有很多人反對說,定時器回調(diào)函數(shù)不要有長時間操作。發(fā)消息,信號,郵箱...交給其它線程操作云云。
軟定時器本身就是一個線程,通過某種技術手段,在這個線程中可以完成的工作,一定要使用消息機制交給另外一下線程完成嗎?
如何抉擇,請君深思
本優(yōu)化系列所有提到的更改已經(jīng)提交到 gitee ,歡迎大家測試
https://gitee.com/thewon/rt_thread_repo
相關文章:
rt-thread 優(yōu)化系列(0) SysTick 優(yōu)化分析
rt-thread 優(yōu)化系列(一) 之 過多關中斷
rt-thread 優(yōu)化系列(二) 之 同步和消息關中斷分析
審核編輯:湯梓紅
-
定時器
+關注
關注
23文章
3251瀏覽量
115002 -
函數(shù)
+關注
關注
3文章
4338瀏覽量
62736 -
RT-Thread
+關注
關注
31文章
1293瀏覽量
40229
發(fā)布評論請先 登錄
相關推薦
評論