Zephyr OS nano 內核篇: 等待隊列 wait_q

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

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

本文先描述 Zephyr OS 中線程的狀態關係,然後分析線程的等待隊列的相關操作。

Zephyr OS 中的等待隊列相關的代碼很少,僅有幾十行,但是理解這些代碼卻不是那麼輕鬆,因此爲了便於大家理解,畫了若干圖。

線程的狀態切換

Zephyr OS 中線程的狀態可以分爲三類:
- 執行態:指當前正在佔用 CPU 執行任務的線程。由於物聯網設備的低成本、低速率的特性,Zephyr OS 只支持單 CPU,因此內核中只有一個線程處於執行態。
- 就緒態:指已經準備就緒、等待被調度執行的線程,這些線程以優先級的順序排列。當系統當前正在執行的線程執行完畢或進入等待隊列時,調度算法會調度就緒態鏈表中優先級最高的線程。
- 等待態:當內核中的一個線程正在執行時,可能由於某種原因無法再執行下去(比如等待某種資源——信號量),這時我們可以將該線程放入一個等待隊列中,然後將 CPU 的執行權轉交給其它線程,以達到最大效率使用 CPU 的目的。

線程的狀態切換如圖 1 所示。


圖 1. 線程狀態切換

等待隊列的應用場景

Zephyr OS 中,以下幾種場景將會使用等待隊列:

  • 信號量:當多個線程都需要使用信號量時,可能信號量被別的線程用盡(信號量減至0),另一個線程再去獲取該信號量時,得不到信號量,該線程就將自己加入到等待隊列中,並釋放 CPU。
  • fifo:fifo 是先進先出的隊列,使用的場景是這樣的:某個線程(生產者)向 fifo 中寫數據,另一個線程(消費者)從 fifo 中讀取數據。當讀線程(消費者)從 fifo 中取數據時是空的,該線程就將自己加入到等待隊列中,並釋放 CPU。
  • lifo:lifo 是後進先出的隊列,使用的場景與 fifo 類似。
  • timeout:超時服務是指線程在指定的超時時間後再運行。線程在使用超時服務時,會將自己加入到等待隊列中。當超時時間到期時,調度器會將該線程從等待隊列中取出。

以上內容都是推測出來的,其正確與否會在今後的學習過程中得到驗證。

等待隊列的定義

struct _nano_queue {
    void *head;
    void *tail;
};

該結構體包含兩個成員:
- head:用於指向等待隊列的隊首線程。當隊列爲空時,指向NULL。
- tail:用於指向等待隊列的隊尾線程。當隊列位空時,指向該隊列本身。

對於如下程序:

struct _nano_queue wait_q;

其在內存空間存儲情況如圖 2 所示。


圖 2. 等待隊列的定義

等待隊列的初始化

static inline void _nano_wait_q_reset(struct _nano_queue *wait_q)
{
    wait_q->head = (void *)0;
    wait_q->tail = (void *)&(wait_q->head);
}

static inline void _nano_wait_q_init(struct _nano_queue *wait_q)
{
    _nano_wait_q_reset(wait_q);
}

進行隊列結構體中成員的初始化:
- 將 head 成員指向 NULL。
- 將 tail 成員指向該隊列自身(參考後面的圖2)。

_nano_wait_q_init() 和 _nano_wait_q_reset() 是兩個完全等價的函數。

對於如下程序:

struct _nano_queue wait_q;
_nano_wait_q_init(&wait_q);

其在內存空間的存儲情況如圖 3 所示。


圖 3. 等待隊列的初始化

在隊尾插入線程

static inline void _nano_wait_q_put(struct _nano_queue *wait_q)
{
    ((struct tcs *)wait_q->tail)->link = _nanokernel.current;
    wait_q->tail = _nanokernel.current;
}

很簡單的兩句話,但是理解起來蠻喫力的。我們分兩種情況考慮:

當等待隊列爲空時插入線程

先回憶一下描述線程的結構體 struct cts 的定義:

struct tcs {
    struct tcs *link; 
    uint32_t flags;
    uint32_t basepri;
    int prio;

    ...
};

其在內存空間的存儲情況如圖 4 右邊部分所示。我們需要注意其 link 成員,它是 struct tcs 的第一個成員,其類型是一個指向線程的指針。

當隊列爲空時,wait_q 的 tail 成員指向該隊列自身的 head 成員,其結構如圖 3 所示。((struct tcs )wait_q->tail) 將 tail 強制轉換爲一個線程結構體指針,也就是說,tail 所指向的地址用於存放一個線程,而由於 link 是線程結構體 struct tcs 的第一個成員,且是一個指針,所以 ((struct tcs )wait_q->tail)->link 的含義就是有一個指針變量,該變量自身的地址等於 &wait_q->tail(即&wait_q->head), 該變量指向了一個線程的結構體

所以,((struct tcs *)wait_q->tail)->link = _nanokernel.current; 的含義就是將 head 指向當前正在執行的線程的結構體。

wait_q->tail = _nanokernel.current; 將 tail 也指向當前正在指向的線程的結構體。

對於如下程序:

struct _nano_queue wait_q;
_nano_wait_q_init(&wait_q);

_nano_wait_q_put(&wait_q);

其在內存空間的存儲情況如圖 4 所示。


圖 4. 隊列爲空時插入線程

通常,當前正在執行的線程將自己加入到等待線程後,會調用函數 _swip() 釋放 CPU,此時處於就緒狀態的線程中的優先級最高的線程會佔用 CPU 進行執行。

當等待隊列不爲空時插入線程

當等待隊列不爲空時,((struct tcs )wait_q->tail) 表示等待隊列的隊尾線程,((struct tcs )wait_q->tail)->link 表示對待隊列的隊尾線程的 link 成員。

((struct tcs *)wait_q->tail)->link = _nanokernel.current; 表示將隊尾線程的 link 指向當前正在執行的線程的結構體。
wait_q->tail = _nanokernel.current; 表示將 tail 也指向當前正在執行的線程的結構體。

例子

對於如下代碼:

struct _nano_queue wait_q;
_nano_wait_q_init(&wait_q);

//***********************************************************************************//
/* 線程 1 正在執行 */
...
_nano_wait_q_put(&wait_q); // 如圖 4
...
/* 線程 1 釋放 CPU */
//***********************************************************************************//
/* 線程 2 正在執行, 線程 1 已處於等待隊列中 */
...
_nano_wait_q_put(&wait_q); // 如圖 5
...
/* 線程 2 釋放 CPU */
//***********************************************************************************//
/* 線程 3 正在執行, 線程 1/2 已處於等待隊列中 */
...
_nano_wait_q_put(&wait_q); // 如圖 6
...
/* 線程 3 釋放 CPU */
//***********************************************************************************//

...

//***********************************************************************************//
/* 線程 6 正在執行, 線程 1/2/3/4/5 已處於等待隊列中 */
...
_nano_wait_q_put(&wait_q); // 如圖 7
...
/* 線程 6 釋放 CPU */


圖 4.


圖 5.


圖 6.


圖 7.

在隊首取出線程

struct tcs *_nano_wait_q_remove(struct _nano_queue *wait_q)
{
    return wait_q->head ? _nano_wait_q_remove_no_check(wait_q) : NULL;
}

先判斷等待隊列是否爲空,如果爲空,返回 NULL,如果不爲空,調用函數_nano_wait_q_remove_no_check().

static struct tcs *_nano_wait_q_remove_no_check(struct _nano_queue *wait_q)
{
    // 獲取等待隊列的隊首線程
    struct tcs *tcs = wait_q->head;

    if (wait_q->tail == wait_q->head) {
        // 如果等待隊列中只有一個線程了,對隊列進行復位
        _nano_wait_q_reset(wait_q);
    } else {
        // 如果隊列中不止一個線程,將 head 指向第二個線程
        wait_q->head = tcs->link;
    }
    tcs->link = 0;

    // 將該線程加入就緒隊列
    _nano_fiber_ready(tcs);
    return tcs;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章