Zephys OS nano內核篇:定時器 Timer

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;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章