Zephyr OS 所有的學習筆記已託管到 Github,CSDN 博客裏的內容只是 Github 裏內容的拷貝,因此鏈接會有錯誤,請諒解。
最新的學習筆記請移步 GitHub:https://github.com/tidyjiang8/zephyr-inside
概念
nanokernel 利用系統的時鐘實現了一個定時器服務。線程可以利用定時器服務來等待一段指定的時間間隔(以滴答爲單位),且在等待的期間可以異步執行其它工作。一個線程可以使用多個定時器同時監控多個時間間隔。
線程在使用定時器時,可以爲定時器綁定一個用戶數據(使用一個指向用戶數據結構體的指針)。當定時器的時間到期時,它會返回這個用戶數據。
Timer 的定義
struct nano_timer {
struct _nano_timeout timeout_data;
void *user_data;
void *user_data_backup;
#ifdef CONFIG_DEBUG_TRACING_KERNEL_OBJECTS
struct nano_timer *__next;
#endif
};
不考慮用於 debug 的 __next,一共包含 3 個成員:
- timeout_data: 可以看出來,定時器內部使用了前一篇文章所講的超時服務。
- user_data: 用戶數據,定時器到期後返回給線程。
- user_data_backup: 用戶數據的備份。
爲什麼需要備份用戶數據?表面上看去,這個備份完全是多餘的,但是深入分析,其實定時器同時還使用 user_data 來作爲一個標誌,來表示定時器當前的狀態。當定時器處於工作狀態(即定時器已啓動,並在等待到期)時,user_data 指向一段用戶自定義數據。當定時器處於非工作狀態(即定時器啓動前、定時器到期後或者定時器被終止後)時,user_data 指向 NULL。
Q:爲什麼不單獨使用一個 flag 成員來表示當前的狀態,那樣更容易理解
Timer 的 API
Timer 服務對內核其它服務暴露了如下 API 如下:
- nano_timer_init():初始化定時器
- nano_timer_start():啓動定時器。內核會根據當前的上下文調用對於的啓動函數:
- nano_task_timer_start()
- nano_fiber_timer_start()
- nano_isr_timer_start()
- nano_timer_test():測試定時器是否到期。內核會根據當前的上下文調用對應的測試函數:
- nano_task_timer_test()
- nano_fiber_timer_test()
- nano_isr_timer_test()
- nano_timer_stop():強行停止定時器,將其從超時鏈表中刪除,並將綁定的線程加入就緒隊列,因此也可以將其理解爲將定時器強制到期。內核會根據當前的上下文調用對於的停止函數:
- nano_task_timer_stop()
- nano_fiber_timer_stop()
- nano_isr_timer_stop()
- nano_timer_ticks_remain():判斷定時器還有多久將到期,單位是滴答。
nano_timer_init
void nano_timer_init(struct nano_timer *timer, void *data)
{
// 初始化 timeout_data
_nano_timeout_init(&timer->timeout_data, NULL);
timer->user_data = NULL;
timer->user_data_backup = data;
SYS_TRACING_OBJ_INIT(nano_timer, timer); // 用於debug
}
在啓動定時器前,需要調用該函數初始化定時器結構體中的各成員。需要注意到一點,user_data 沒有指向用戶數據,user_data_backup 指向了用戶數據。
nano_timer_start
nano_timer_start(),nano_task_timer_start(),nano_fiber_timer_start(),nano_isr_timer_start()都是函數_timer_start() 的別名:
FUNC_ALIAS(_timer_start, nano_isr_timer_start, void);
FUNC_ALIAS(_timer_start, nano_fiber_timer_start, void);
FUNC_ALIAS(_timer_start, nano_task_timer_start, void);
FUNC_ALIAS(_timer_start, nano_timer_start, void);
void _timer_start(struct nano_timer *timer, int ticks)
{
int key = irq_lock();
timer->user_data = timer->user_data_backup;
_nano_timer_timeout_add(&timer->timeout_data,
NULL, ticks);
irq_unlock(key);
}
啓動定時器,並設定定時器到期的時間間隔(以滴答爲單位)。主要做了兩件事:
- 將user_data 也指向用戶數據。
- 將定時器結構體中的超時服務加入到內核的超時鏈表中。
nano_timer_test
void *nano_timer_test(struct nano_timer *timer, int32_t timeout_in_ticks)
{
static void *(*func[3])(struct nano_timer *, int32_t) = {
nano_isr_timer_test,
nano_fiber_timer_test,
nano_task_timer_test,
};
return func[sys_execution_context_type_get()](timer, timeout_in_ticks);
}
根據當前的上下文,調用對應的測試函數。第二個比較難以理解,它其實是一個標誌 flag,用來指示該函數的具體行爲:
- 如果 flag 的取值爲 TICKS_NONE,表示如果待測試的定時器如果沒到期,該函數直接返回。
- 如果 flag 的取值爲 TICKS_UNLIMITED,表示如果待測試的定時器如果沒到期,會讓出 CPU 進行上下文切換或忙進行忙等待(如果當前上下文是中斷,不會切換)。
nano_fiber_timer_test
void *nano_fiber_timer_test(struct nano_timer *timer, int32_t timeout_in_ticks)
{
int key = irq_lock();
struct _nano_timeout *t = &timer->timeout_data;
void *user_data;
// 根據 _nano_timer_expire_wait 的返回值判讀是直接返回還是進行線程切換。
if (_nano_timer_expire_wait(timer, timeout_in_ticks, &user_data)) {
t->tcs = _nanokernel.current;
_Swap(key);
key = irq_lock();
user_data = timer->user_data;
timer->user_data = NULL;
}
irq_unlock(key);
return user_data;
}
再看看 _nano_timer_expire_wait() 如何實現:
tatic int _nano_timer_expire_wait(struct nano_timer *timer,
int32_t timeout_in_ticks,
void **user_data_ptr)
{
struct _nano_timeout *t = &timer->timeout_data;
/* check if the timer has expired */
if (t->delta_ticks_from_prev == -1) {
// 將定時器的用戶數據“取”給 user_data_ptr
*user_data_ptr = timer->user_data;
// 用戶數據被“取”走後,將 user_data 置爲 NULL
timer->user_data = NULL;
/* if the thread should not wait, return immediately */
} else if (timeout_in_ticks == TICKS_NONE) {
// 由於定時器沒到期,調用函數“取”不到任何數據
*user_data_ptr = NULL;
} else {
// 由於調用函數將進行上下文切換,此時不關心 user_data_ptr
return 1;
}
return 0;
}
該函數先檢查定時器是否已經到期了。
- 如果到期了,返回 0 ,“取”出用戶數據,其調用函數不會進行線程切換。
- 如果沒到期,再根據傳入的標誌 timeout_in_ticks 來判斷具體的行爲。
- 如果 timeout_in_ticks 爲 TICKS_NONE,返回 0,其調用函數不會進行上下文切換。
- 如果 timeout_in_ticks 爲 TICKS_UNLIMITED,返回 1,其調用函數將上下文切換。
我們現在可以回頭看看爲什麼用戶數據需要備份了。在初始化時,
nano_task_timer_test
void *nano_task_timer_test(struct nano_timer *timer, int32_t timeout_in_ticks)
{
int key = irq_lock();
struct _nano_timeout *t = &timer->timeout_data;
void *user_data;
if (_nano_timer_expire_wait(timer, timeout_in_ticks, &user_data)) {
// 進行忙等待操作,知道定時器到期。
while (t->delta_ticks_from_prev != -1) {
NANO_TASK_TIMER_PEND(timer, key);
}
user_data = timer->user_data;
timer->user_data = NULL;
}
irq_unlock(key);
return user_data;
}
NANO_TASK_TIMER_PEND 會判斷當前 task 是否是 idea task 來做相應的動作,如果是 idea task,直接進行忙等待。如果不是 idea task,將當前 task 掛起到一個定時器上。
nano_isr_timer_test
void *nano_isr_timer_test(struct nano_timer *timer, int32_t timeout_in_ticks)
{
int key = irq_lock();
void *user_data;
if (_nano_timer_expire_wait(timer, timeout_in_ticks, &user_data)) {
// 由於中斷上下文的需要需要快速處於終端,所以即使 _nano_timer_expire_wait 返回 1,
// 本函數也直接返回
user_data = NULL;
}
irq_unlock(key);
return user_data;
}
nano_timer_stop
void nano_timer_stop(struct nano_timer *timer)
{
static void (*func[3])(struct nano_timer *) = {
nano_isr_timer_stop,
nano_fiber_timer_stop,
nano_task_timer_stop,
};
func[sys_execution_context_type_get()](timer);
}
根據當前上下文,調用對應的定時器停止函數。
nano_fiber_timer_stop
nano_isr_timer_stop
nano_fiber_timer_stop()和nano_isr_timer_stop()都是函數 _timer_stop_non_preemptible() 的別名:
FUNC_ALIAS(_timer_stop_non_preemptible, nano_isr_timer_stop, void);
FUNC_ALIAS(_timer_stop_non_preemptible, nano_fiber_timer_stop, void);
所以我們具體來看 _timer_stop_non_preemptible() 函數:
void _timer_stop_non_preemptible(struct nano_timer *timer)
{
struct _nano_timeout *t = &timer->timeout_data;
struct tcs *tcs = t->tcs;
int key = irq_lock();
// 1. 先判斷定時器所綁定的線程是否處於一個等待隊列上。如果是,讓其繼續等待,當等待條
// 件被滿足時會有相應的程序將其加入到就緒隊列中,我們不用做任何處理。
// 2. 如果不在等待隊列上,將 timeout_data 從超時鏈表中刪除。
// 3. 再根據定時器綁定的線程的上下文類型,將其加入到對應的就緒鏈表中。
if (!t->wait_q && (_nano_timer_timeout_abort(t) == 0) &&
tcs != NULL) {
if (_IS_MICROKERNEL_TASK(tcs)) {
_NANO_TIMER_TASK_READY(tcs);
} else {
_nano_fiber_ready(tcs);
}
}
// 標誌定時器處於非工作狀態
timer->user_data = NULL;
irq_unlock(key);
}
nano_task_timer_stop
void nano_task_timer_stop(struct nano_timer *timer)
{
struct _nano_timeout *t = &timer->timeout_data;
struct tcs *tcs = t->tcs;
int key = irq_lock();
timer->user_data = NULL;
if (!t->wait_q && (_nano_timer_timeout_abort(t) == 0) &&
tcs != NULL) {
if (!_IS_MICROKERNEL_TASK(tcs)) {
_nano_fiber_ready(tcs);
_Swap(key);
return;
}
_TASK_NANO_TIMER_TASK_READY(tcs);
}
irq_unlock(key);
}
該函數與 _timer_stop_non_preemptible 極其相似,主要的區別是:
由於調用該函數時,當前上下文是 task,所以如果判斷出定時器所綁定的線程的上下文是 fiber 或者 isr 時,就進行上下文切換。這是因爲 task 的優先級比 fiber 和 isr 的優先級低。
nano_timer_ticks_remain
int32_t nano_timer_ticks_remain(struct nano_timer *timer)
{
int key = irq_lock();
int32_t remaining_ticks;
struct _nano_timeout *t = &timer->timeout_data;
sys_dlist_t *timeout_q = &_nanokernel.timeout_q;
struct _nano_timeout *iterator;
if (t->delta_ticks_from_prev == -1) {
// 如果定時器已到期,直接返回 0
remaining_ticks = 0;
} else {
// 如果定時器沒到期,從鏈表頭開始查找,直到找到該節點,並將各節點綁定的增量超
// 時時間 delta_ticks_from_prev 累加
iterator = (struct _nano_timeout *)sys_dlist_peek_head(timeout_q);
remaining_ticks = iterator->delta_ticks_from_prev;
while (iterator != t) {
iterator = (struct _nano_timeout *)sys_dlist_peek_next(timeout_q, &iterator->node);
remaining_ticks += iterator->delta_ticks_from_prev;
}
}
irq_unlock(key);
return remaining_ticks;
}