軟定時(shí)器
所謂軟定時(shí)器,是由一個(gè)線程運(yùn)行維護(hù)的定時(shí)器列表。由線程調(diào)用定時(shí)器回調(diào)函數(shù)。
相對硬定時(shí)器,是由中斷(SysTick)維護(hù)的定時(shí)器列表,并在中斷中調(diào)用定時(shí)器回調(diào)函數(shù)。
另外,還有一種*硬件定時(shí)器*,這個(gè)和單片機(jī)里的定時(shí)器是一個(gè)概念,由外設(shè)定時(shí)器實(shí)現(xiàn)定時(shí)。和 rt-thread 提供的硬定時(shí)器是兩個(gè)不同概念。
對硬定時(shí)器回調(diào)函數(shù)有嚴(yán)格的執(zhí)行時(shí)間要求,而且不能調(diào)用任何在中斷中不能調(diào)用的函數(shù)??傊荒苡腥魏尾荒茉谥袛嘀袌?zhí)行的操作。
那么,軟定時(shí)器呢,要求需要這么嚴(yán)格嗎?
比如有個(gè)處理執(zhí)行時(shí)間 10ms,比如在定時(shí)器中斷函數(shù)里發(fā)送個(gè)等待 10ms 的消息...
無論是硬定時(shí)器還是軟定時(shí)器,它們各自有一個(gè)定時(shí)器列表。列表中的定時(shí)器根據(jù)定時(shí)時(shí)間長短排序,定時(shí)時(shí)間短的在前。掃描這個(gè)列表中所有定時(shí)器,直到結(jié)尾或者出現(xiàn)第一個(gè)定時(shí)時(shí)間未到的定時(shí)器節(jié)點(diǎn)。當(dāng)判斷出定時(shí)時(shí)間到的時(shí)候,調(diào)用定時(shí)器回調(diào)函數(shù)。
假如,某次掃描這個(gè)列表中多于一個(gè)定時(shí)器到達(dá)定時(shí)時(shí)間,也就是需要執(zhí)行兩個(gè)以上的定時(shí)器回調(diào)函數(shù)。并且前一個(gè)掃描到的定時(shí)器的回調(diào)函數(shù)執(zhí)行時(shí)間比較長,出現(xiàn)上面設(shè)想的某一種使用情況。這時(shí)候會(huì)出現(xiàn)什么效果?
因?yàn)橛捕〞r(shí)器的種種硬性要求,以下討論只針對軟定時(shí)器。
從對 `rt_soft_timer_check` 的幾個(gè)疑問講起
先擺出官方的 rt_soft_timer_check 函數(shù),實(shí)現(xiàn)。這個(gè)函數(shù)是來掃描定時(shí)器列表中到達(dá)定時(shí)時(shí)間的定時(shí)器,并調(diào)用定時(shí)器回調(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` 這個(gè)臨時(shí)中間變量的作用是什么?
- 進(jìn)入 `rt_soft_timer_check` 函數(shù)后,先初始化 list 變量。
- 掃描定時(shí)器列表,當(dāng)有到時(shí)的定時(shí)器,把這個(gè)定時(shí)器從軟定時(shí)器列表 `rt_soft_timer_list` 移除,插入到這個(gè) list 臨時(shí)存放。
- 開中斷,調(diào)用定時(shí)器回調(diào)函數(shù),關(guān)中斷
- 把 list 中存放的定時(shí)器移除
- 判斷定時(shí)器的周期定時(shí)器還是一次性定時(shí)器
- 繼續(xù)掃描定時(shí)器列表
list 是個(gè)局部變量,僅僅起臨時(shí)存放當(dāng)前這個(gè)定時(shí)器的作用。看似是一種穩(wěn)妥的做法。
但是,這樣做的初衷是什么?
為什么這個(gè)定時(shí)器一定要放到某個(gè)列表里?
如果從軟定時(shí)器列表 `rt_soft_timer_list` 移除后,不插入任何列表會(huì)有什么影響?
因?yàn)橥顺?`rt_soft_timer_check` 函數(shù)后,list 列表不復(fù)存在了,應(yīng)該不是退出 `rt_soft_timer_check` 函數(shù)后的需求,那么插入 list 和 從 list 取出之間有哪些情況需要我們注意,需要用一個(gè)臨時(shí)列表將軟定時(shí)器暫存?
定時(shí)器回調(diào)函數(shù)里可能發(fā)生哪些操作?
- 修改定時(shí)器設(shè)置
- 停止,重啟定時(shí)器
- 刪除定時(shí)器
- 發(fā)生中斷,在中斷里執(zhí)行上述三種操作
修改定時(shí)器設(shè)置,可能只涉及到定時(shí)器的定時(shí)時(shí)間間隔和定時(shí)周期特性。這兩個(gè)參數(shù)設(shè)置需要定時(shí)器必須在某個(gè)列表中嗎?
停止,重啟定時(shí)器,必然導(dǎo)致修改定時(shí)器所在列表指針,這里就涉及到雙向列表的操作了。
> 簡短介紹一下雙向列表,
- rt-thread 使用的雙向列表,每一個(gè)節(jié)點(diǎn)有一個(gè) prev 和 一個(gè) next 指針,分別指向雙向列表中的前一個(gè)節(jié)點(diǎn)和后一個(gè)節(jié)點(diǎn)。
- 一個(gè)空列表 l 僅有一個(gè)不含數(shù)據(jù)的節(jié)點(diǎn),此節(jié)點(diǎn)的 prev next 指針均指向它自己。
- 任何一個(gè)帶數(shù)據(jù)的列表節(jié)點(diǎn)必須進(jìn)行初始化,使得它的 prev next 分別指向它自己,這一點(diǎn)和空列表 l 完全雷同!換句話說,***任何一個(gè)雙向列表節(jié)點(diǎn)均有作為鏈表的潛質(zhì)***。從操作上講,你可以定義兩個(gè)定時(shí)器,然后這兩個(gè)定時(shí)器之間構(gòu)建一個(gè)含有兩個(gè)節(jié)點(diǎn)的雙向列表,當(dāng)然,這種做法沒有多少實(shí)用意義。
- 從鏈表中移除的節(jié)點(diǎn),**必須**使得它的 prev next 指針指向它自己。
- **無論一個(gè)節(jié)點(diǎn)是否在某個(gè)雙向鏈表中,或者僅僅是一個(gè)獨(dú)立節(jié)點(diǎn),對它進(jìn)行刪除操作,效果是完全一樣的!**
- 更多的操作詳見 rtservice.c 文件中相關(guān)函數(shù),`rt_list_init, rt_list_insert_after, rt_list_insert_before, rt_list_remove` 等等。
停止定時(shí)器會(huì)把當(dāng)前定時(shí)器從定時(shí)器列表刪除,無論這個(gè)定時(shí)器有沒有在某個(gè)定時(shí)器列表中,或者只是一個(gè)獨(dú)立的定時(shí)器節(jié)點(diǎn),刪除操作的結(jié)果都是一樣的,使用 list 這個(gè)臨時(shí)列表可能不能保護(hù)它不被刪除。
重啟定時(shí)器會(huì)把它先從前一個(gè)列表中刪除,然后插入軟定時(shí)器列表 `rt_soft_timer_list` 。list 這個(gè)臨時(shí)列表也阻止不了重啟定時(shí)器操作。
**至此,可以看出,`rt_list_t list` 這個(gè)臨時(shí)列表無任何存在意義**
疑問二 `soft_timer_status` 指示的是什么狀態(tài)?
這是一個(gè)全局靜態(tài)變量,它的使用也很簡單,只在四個(gè)地方使用了,上面的源碼函數(shù)里有兩處,其它兩個(gè)地方分別是初始化聲明
/* 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 */
將這四個(gè)地方聯(lián)系起來看,意思好像是調(diào)用定時(shí)器回調(diào)函數(shù)前修改軟定時(shí)器為 busy 狀態(tài),返回回調(diào)函數(shù)后恢復(fù)為 idle 狀態(tài),而如果是在定時(shí)器回調(diào)函數(shù)里調(diào)用 `rt_timer_start` ,可以達(dá)到不進(jìn)行任務(wù)調(diào)度的目的。好像是起了雙保險(xiǎn)作用,真是這樣嗎?
我們分析一下上面這段 `rt_timer_start` 函數(shù)片段。
首先判斷定時(shí)器是不是軟定時(shí)器,只有軟定時(shí)器啟動(dòng)時(shí)才有進(jìn)行任務(wù)調(diào)度的可能。
其次,判斷 `soft_timer_status` 是否空閑,以及軟定時(shí)器線程是否***掛起態(tài)***。
以上仨?xiàng)l件均滿足,進(jìn)行任務(wù)調(diào)度。
我們重點(diǎn)關(guān)注“其次”,一個(gè)定時(shí)器線程調(diào)用的定時(shí)器回調(diào)函數(shù),這個(gè)線程會(huì)是掛起態(tài)嗎?答案是肯定不是。它在運(yùn)行著,肯定是 `RT_THREAD_RUNNING` 的。那么這個(gè) `soft_timer_status` “雙保險(xiǎn)”了嗎?
疑問三 開篇提到的假想
開篇提到了一種假想,假想軟定時(shí)器回調(diào)函數(shù)占用 cpu 時(shí)間有點(diǎn)兒長,會(huì)產(chǎn)生什么影響,引起什么后果。
討論這個(gè)問題仍然離不開 `rt_soft_timer_check` 函數(shù)工作原理,我們再梳理一下 `rt_soft_timer_check` 函數(shù)的操作。(以下分析忽略 list 以及 soft_timer_status 相關(guān)操作)
- 關(guān)中斷
- 掃描列表是否有節(jié)點(diǎn)
- 取出第一個(gè)節(jié)點(diǎn)
- ***獲取當(dāng)前系統(tǒng) tick***
- 檢查定時(shí)器是否定時(shí)時(shí)間到,如果到時(shí)
- 先從軟定時(shí)器列表 `rt_soft_timer_list` 移除定時(shí)器。非周期定時(shí)器,取消激活態(tài)
- 開中斷
- 執(zhí)行定時(shí)器回調(diào)函數(shù)。(這里可能存在長時(shí)間操作)
- 關(guān)中斷
- 對于周期性定時(shí)器,重啟定時(shí)器;非周期定時(shí)器,前面已經(jīng)做了取消激活態(tài)操作。
- 繼續(xù)掃描列表,取出第一個(gè)節(jié)點(diǎn),***獲取當(dāng)前系統(tǒng) tick*** ,檢查定時(shí)器是否定時(shí)時(shí)間到。。。
假設(shè) RT_TICK_PER_SECOND = 1000,有兩個(gè)周期性定時(shí)器 t0 t1 ,定時(shí)間隔不同,同時(shí)啟動(dòng),各自的定時(shí)器回調(diào)函數(shù)執(zhí)行時(shí)間 t0 500us,t1 5 ms。
經(jīng)過一段時(shí)間以后,總是可能會(huì)出現(xiàn)定時(shí)間隔公倍數(shù)時(shí)刻 Tn ,它們倆同時(shí)達(dá)到定時(shí)時(shí)間。
如果 t1 先被處理,那么 t1 重啟的時(shí)候系統(tǒng) tick 已經(jīng)是 Tn + 5;t0 的重啟時(shí)間有 50% 的可能是 Tn + 5, 50%的可能是 Tn + 6。
如果 t0 先被處理,t0 的重啟時(shí)間有 50% 的可能是 Tn, 50%的可能是 Tn + 1;t1 重啟時(shí)間是 Tn + 5 或 Tn + 6。
即便不考慮 t0 不考慮它對外的影響,也不考慮它受到的影響,僅僅分析 t1 自己對自己的影響,也可以看出來,隨著時(shí)間的推移,它的定時(shí)間隔不是初始設(shè)定的 Inv ,而是 Inv + 5。
優(yōu)化后的 `rt_soft_timer_check` 流程
- ***獲取當(dāng)前系統(tǒng) tick***
- 關(guān)中斷
- 掃描列表是否有節(jié)點(diǎn)
- 取出第一個(gè)節(jié)點(diǎn)
- 檢查定時(shí)器是否定時(shí)時(shí)間到,如果到時(shí)
- 先從軟定時(shí)器列表 `rt_soft_timer_list` 移除定時(shí)器。
- 對于周期性定時(shí)器,***重啟定時(shí)器***;非周期定時(shí)器,取消激活態(tài)。
- 開中斷
- 執(zhí)行定時(shí)器回調(diào)函數(shù)。(這里可能存在長時(shí)間操作)
- 關(guān)中斷
- 繼續(xù)掃描列表,取出第一個(gè)節(jié)點(diǎn),檢查定時(shí)器是否定時(shí)時(shí)間到。。。
其中,優(yōu)化后的重啟定時(shí)器不能使用原來的接口。需要使用如下原型函數(shù)接口
static rt_err_t _rt_timer_start(rt_timer_t timer, rt_tick_t current_tick)
第二個(gè)參數(shù)是進(jìn)入 `rt_soft_timer_check` 函數(shù),第一次關(guān)中斷前獲取的當(dāng)前系統(tǒng) `tick` 值,無論下面掃描出多少個(gè)到達(dá)時(shí)間的定時(shí)器,啟動(dòng)時(shí)間都是同一個(gè) `tick` 值。
而且無論其中某個(gè)定時(shí)器回調(diào)函數(shù)執(zhí)行時(shí)間有多長,或者多個(gè)回調(diào)函數(shù)累積執(zhí)行時(shí)間有多長,它們的啟動(dòng)時(shí)間都是相同的!
注:由此,引起的另一個(gè)弊端的,期間某個(gè)定時(shí)器定時(shí)時(shí)間到了,但是會(huì)被判定為未到,下次調(diào)用 `rt_soft_timer_check` 時(shí)才會(huì)被處理。
附測試程序
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);
}
}
作為對比,兩個(gè) led 一個(gè)用軟定時(shí)器控制亮滅頻率,一個(gè)用 mdelay 延時(shí)控制亮滅頻率。
如果 timeout 沒有延遲,兩個(gè)燈一直是同步的;有延遲后,過一段時(shí)間兩個(gè)燈亮滅變不同步了。
總結(jié)
肯定有很多人反對說,定時(shí)器回調(diào)函數(shù)不要有長時(shí)間操作。發(fā)消息,信號(hào),郵箱...交給其它線程操作云云。
軟定時(shí)器本身就是一個(gè)線程,通過某種技術(shù)手段,在這個(gè)線程中可以完成的工作,一定要使用消息機(jī)制交給另外一下線程完成嗎?
如何抉擇,請君深思
本優(yōu)化系列所有提到的更改已經(jīng)提交到 gitee ,歡迎大家測試
https://gitee.com/thewon/rt_thread_repo
相關(guān)文章:
rt-thread 優(yōu)化系列(0) SysTick 優(yōu)化分析
rt-thread 優(yōu)化系列(一) 之 過多關(guān)中斷
rt-thread 優(yōu)化系列(二) 之 同步和消息關(guān)中斷分析
審核編輯:湯梓紅
-
定時(shí)器
+關(guān)注
關(guān)注
23文章
3218瀏覽量
113693 -
函數(shù)
+關(guān)注
關(guān)注
3文章
4237瀏覽量
61971 -
RT-Thread
+關(guān)注
關(guān)注
31文章
1239瀏覽量
39442
發(fā)布評(píng)論請先 登錄
相關(guān)推薦
評(píng)論