Zephys OS nano內核篇:超時服務timeout

Zephyr OS 所有的學習筆記已託管到 Github,CSDN 博客裏的內容只是 Github 裏內容的拷貝,因此鏈接會有錯誤,請諒解。

最新的學習筆記請移步 GitHub:https://github.com/tidyjiang8/zephyr-inside

Zephys 的內核大總管 _nanokernel 掌握着一個超時鏈表(sys_dlist_t timeout_q),這個鏈表中的每個節點都綁定了一個超時時間、一個線程和/或一個回調函數。當超時時間到期時,會執行該節點所綁定的線程(將其設爲就緒態)或者回調函數。

滴答時鐘

要學習超時服務,必須需要先了解一下滴答時鐘的概念。一般而言,一顆 CPU 都至少有兩類時鐘:
- 高頻時鐘:系統時鐘(system clock),也叫做主時鐘(main clock)。CPU 在每個時鐘週期內取值、執行(有些指令可能需要多個時鐘週期)。
- 低頻時鐘:低頻時鐘可以用作系統的滴答時鐘(systick clock),或者看門狗時鐘(WDT clock)等多種用途。例如 TI 的 RF 芯片 CC2538,其滴答時鐘是 32 KHz,即在每秒之內將產生 32000 個滴答,每個滴答的週期是 1/32000 秒。

Zephyr OS 中定時器的實現就是利用滴答時鐘實現的。超時鏈表中的節點所綁定的超時時間就是以滴答爲單位的。系統每產生一次滴答,都會觸發一次滴答中斷,從而會調用滴答中斷的中斷服務例程(ISR)。在這個中斷例程中,會先將超時鏈表中各節點所綁定的超時時間減 1,然後處理該鏈表中所有超時時間減至 0 的節點(即調用線程或執行回調函數)。更多的知識請參考後續的文章《Zephyr OS 內核篇:定時器Timer》和《Zephyr OS 驅動篇:滴答時鐘 SysTick》。

類型定義

struct _nano_timeout {
    sys_dlist_t node;
    struct tcs *tcs;
    struct _nano_queue *wait_q;
    int32_t delta_ticks_from_prev;
    _nano_timeout_func_t func;
};

各成員含義如下:
- node: 描述該節點在超時鏈表中的位置,可以通過該變量找到本節點的前驅節點和後繼節點。
- tcs: 超時節點所綁定的線程。
- wait_q: 超時節點所綁定的等待隊列(暫時還不瞭解有什麼作用)。
- delta_ticks_from_prev:超時時間,以系統的滴答(tick)爲單位。
- func: 超時節點所綁定的回調函數。

timeout 支持如下 API :
- _nano_timeout_init: 初始化 timeout 中結構體中相關成員。
- _nano_timeout_add: 將某個 timeout 加入到 _nanokernel 的超時鏈表中。
- _nano_timeout_handle_timeouts: 處理 _nanokerenl 的超時鏈表中的所以已超時的節點。
- _nano_timeout_abort: 終止某個超時節點,即從超時鏈表中刪除。
- _nano_get_earliest_timeouts_deadline:返回即將超時時間即將到期的節點。

_nano_timeout_init

static inline void _nano_timeout_init(struct _nano_timeout *t,
                      _nano_timeout_func_t func)
{
    t->delta_ticks_from_prev = -1;
    t->wait_q = NULL;
    t->tcs = NULL;
    t->func = func;
}

初始化超時結構中的各變量。

_nano_timeout_add

static inline void _nano_timeout_add(struct tcs *tcs,
                     struct _nano_queue *wait_q,
                     int32_t timeout)
{
    _do_nano_timeout_add(tcs,  &tcs->nano_timeout, wait_q, timeout);
}

繼續追蹤 _do_nano_timeout_add:

void _do_nano_timeout_add(struct tcs *tcs,
                     struct _nano_timeout *t,
                     struct _nano_queue *wait_q,
                     int32_t timeout)
{
    // 獲取內核大總管 _nanokernel 的超時鏈表
    sys_dlist_t *timeout_q = &_nanokernel.timeout_q;

    t->tcs = tcs;   // 綁定本節點線程
    t->delta_ticks_from_prev = timeout; // 設置本節點的超時時間,以滴答(tick)爲單位
    t->wait_q = wait_q; // 綁定本節點等待隊列
    // 在超時鏈表中插入本節點,請看下面的具體分析
    sys_dlist_insert_at(timeout_q, (void *)t,
                        _nano_timeout_insert_point_test,
                        &t->delta_ticks_from_prev);
}

在《Zephyr OS 內核篇: 內核鏈表》已經講解了函數 sys_dlist_insert_at(),它會從超時鏈表的頭節點依次測試,直到該節點滿足條件 _nano_timeout_insert_point_test(),即該函數的返回值非 0。繼續追蹤 _nano_timeout_insert_point_test:

static int _nano_timeout_insert_point_test(sys_dnode_t *test, void *timeout)
{
    struct _nano_timeout *t = (void *)test;
    int32_t *timeout_to_insert = timeout;

    // 如果待插入的節點的超時時間大於測試節點的增量超時時間,timeout_to_insert 減去測試節點的超
    // 時時間,並返回0,測試下一個節點。
    if (*timeout_to_insert > t->delta_ticks_from_prev) {
        *timeout_to_insert -= t->delta_ticks_from_prev;
        return 0;
    }

    // 如果代碼走到這裏,表示待插入的節點的測試時間小於測試測點,則表示待插入的節點將插入到該
    // 測試節點之前,並將測試節點的超時時間嵌入待插入節點的增量超時時間。
    t->delta_ticks_from_prev -= *timeout_to_insert;
    return 1;
}

通過分析函數 _nano_timeout_insert_point_test 內部的代碼,我們可以得出如下結論:內核維持的超時鏈表,其每個節點所綁定的超時時間是一個增量超時時間,即某節點的實際增量時間等於該節點前面所有節點(包括該節點自己)的超時時間之和。例如,如果超時鏈表中有 3 個節點,每個節點所綁定的超時時間分別爲 3 ticks、6 ticks、4 ticks,那麼每個節點實際將等待的超時時間分別爲 3 ticks、9 ticks、13 ticks。

還記得高中數學的 Δ 和高等數學中的 δ 這兩個符號嗎?它們是表示增量的符號。回憶一下它們是怎麼發音的,呃,,它們的英文就是 delta。

_nano_timeout_handle_timeouts

void _nano_timeout_handle_timeouts(void)
{
    sys_dlist_t *timeout_q = &_nanokernel.timeout_q;
    struct _nano_timeout *next;

    // 遍歷鏈表中所有超時時間爲 0 的所有節點,並依次處理
    next = (struct _nano_timeout *)sys_dlist_peek_head(timeout_q);
    while (next && next->delta_ticks_from_prev == 0) {
        next = _nano_timeout_handle_one_timeout(timeout_q);
    }
}

上面的代碼很簡單。系統在每次滴答時鐘到來的時候,會先將超時鏈表中的所有節點所綁定的超時時間減 1,然後判斷頭節點的超時時間是否爲 0。如果爲 0,則會調用本函數處理超時時間爲 0 的所有節點。下面繼續追蹤函數 _nano_timeout_handle_one_timeout()。

static struct _nano_timeout *_nano_timeout_handle_one_timeout(
    sys_dlist_t *timeout_q)
{
    // 獲取超時隊列中的頭節點,並將其從超時鏈表中刪除
    struct _nano_timeout *t = (void *)sys_dlist_get(timeout_q);
    // 獲取頭節點所綁定的線程結構體
    struct tcs *tcs = t->tcs;

    if (tcs != NULL) {
        // 如果線程結構體不爲空,即該節點確實綁定了線程,將該線程從等待隊列
        // 中刪除(如果該節點綁定了等待隊列)。
        _nano_timeout_object_dequeue(tcs, t);
        if (_IS_MICROKERNEL_TASK(tcs)) {
            // 如果該線程是 task,將其加入 task 的就緒鏈表中去
            _NANO_TASK_READY(tcs);
        } else {
            // 如果線程是 fiber,將其加入 fiber 的就緒鏈表中去
            _nano_fiber_ready(tcs);
        }
    } else if (t->func) {
        // 如果該節點爲綁定線程,但是綁定了回調函數,則執行回調函數
        t->func(t);
    }
    t->delta_ticks_from_prev = -1;

    // 返回下一個節點
    return (struct _nano_timeout *)sys_dlist_peek_head(timeout_q);
}

繼續追蹤函數 _nano_timeout_object_dequeue:

static void _nano_timeout_object_dequeue(
    struct tcs *tcs, struct _nano_timeout *t)
{
    if (t->wait_q) {
        // 如果節點綁定了等待隊列,將其從等待隊列中刪除
        _nano_timeout_remove_tcs_from_wait_q(tcs, t->wait_q);
        // 設置線程 tcs 的返回值爲 0。爲啥?不知道。。
        fiberRtnValueSet(tcs, 0);
    }
}

static void _nano_timeout_remove_tcs_from_wait_q(
    struct tcs *tcs, struct _nano_queue *wait_q)
{
    if (wait_q->head == tcs) {
        // 如果該線程是等待隊列的隊首元素,則直接刪除之
        if (wait_q->tail == wait_q->head) {
            _nano_wait_q_reset(wait_q);
        } else {
            wait_q->head = tcs->link;
        }
    } else {
        // 如果不是隊首元素,先查找,再刪除之
        struct tcs *prev = wait_q->head;

        while (prev->link != tcs) {
            prev = prev->link;
        }
        prev->link = tcs->link;
        if (wait_q->tail == tcs) {
            wait_q->tail = prev;
        }
    }

    tcs->nano_timeout.wait_q = NULL;
}

_nano_timeout_abort

static inline int _nano_timeout_abort(struct tcs *tcs)
{
    return _do_nano_timeout_abort(&tcs->nano_timeout);
}

追蹤 _do_nano_timeout_abort():

int _do_nano_timeout_abort(struct _nano_timeout *t)
{
    sys_dlist_t *timeout_q = &_nanokernel.timeout_q;

    if (-1 == t->delta_ticks_from_prev) {
        return -1;
    }

    if (!sys_dlist_is_tail(timeout_q, &t->node)) {
        struct _nano_timeout *next =
            (struct _nano_timeout *)sys_dlist_peek_next(timeout_q,
                                    &t->node);
        // 將該節點的超時時間補充給下一個節點,否則後續所有節點的超時時間都將產生錯誤
        next->delta_ticks_from_prev += t->delta_ticks_from_prev;
    }
    sys_dlist_remove(&t->node);
    t->delta_ticks_from_prev = -1;

    return 0;
}

函數很簡單,將該 timeout 從超時鏈表中刪除並做一些處理即可。

_nano_get_earliest_timeouts_deadline

uint32_t _nano_get_earliest_timeouts_deadline(void)
{
    sys_dlist_t *q = &_nanokernel.timeout_q;
    struct _nano_timeout *t =
        (struct _nano_timeout *)sys_dlist_peek_head(q);

    return t ? min((uint32_t)t->delta_ticks_from_prev,
                    (uint32_t)_nanokernel.task_timeout)
             : (uint32_t)_nanokernel.task_timeout;
}

返回即將超時的節點。

這個 task_timeout 目前還沒碰到過,今後再說

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章