Zephys OS nano 內核篇:棧 stack

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

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

棧是 nanokernel 提供的另一種用於在不同線程間傳遞數據的服務,它也是後進先出的,但是它與 lifo 的不同之處在於兩點:

  • 棧中的元素的大小是固定的,每個元素都是一個整型;lifo中的元素的數據的大小是不固定的
  • 棧的內存空間是在棧的初始化時就固定了,裏面保存的每個元素是實實在在的數據;lifo中保持的數據的內存空間是由向lifo中添加數據的線程分配的,所以lifo裏面保存的是數據的指針。

棧的類型定義

struct nano_stack {
    nano_thread_id_t fiber;
    uint32_t *base;
    uint32_t *next;
#ifdef CONFIG_DEBUG_TRACING_KERNEL_OBJECTS
    struct nano_stack *__next;
#endif
};

不考慮用於調試的 __next,一共包含3個成員:

  • fiber:一個棧允許一個線程在從棧中取數據時處於阻塞狀態。fiber這個成員就用來保存這個阻塞的線程。
  • base:指向棧底。
  • next:指向棧中數據頂部的下一個地址,即棧空閒空間的底部,如下圖所示。

棧結構體和棧在內存空間的存儲情況如下圖所示;

圖:棧結構體與棧在內存空間的存儲結構

棧的初始化

void nano_stack_init(struct nano_stack *stack, uint32_t *data)
{
    stack->next = stack->base = data;
    stack->fiber = (struct tcs *)0;
    SYS_TRACING_OBJ_INIT(nano_stack, stack);
}

初始化棧結構體中相關成員:將 base、next 指向棧底,將 fiber 指向 NULL。

出棧操作

int nano_stack_pop(struct nano_stack *stack, uint32_t *pData, int32_t timeout_in_ticks)
{
    static int (*func[3])(struct nano_stack *, uint32_t *, int32_t) = {
        nano_isr_stack_pop,
        nano_fiber_stack_pop,
        nano_task_stack_pop,
    };

    return func[sys_execution_context_type_get()](stack, pData, timeout_in_ticks);
}

先看看函數的入參:

  • stack:待取數據的棧
  • pData:用來保存從棧中取得的數據
  • timeout_in_ticks:出棧的超時等待時間,以滴答爲單位。函數內部會根據該變量的值來做相應的處理。

再看看函數的返回值:

  • 1 - 表示出棧成功,即成功從棧中取得數據。
  • 0 - 表示出棧失敗,即沒有從棧中取得數據。

nano_stack_pop會根據當前上下文的類型,調用對應的獲取信號的函數。其中,nano_isr_stack_pop() 和 nano_fiber_stack_pop() 是函數 _stack_pop() 的別名。

_stack_pop

int _stack_pop(struct nano_stack *stack, uint32_t *pData, int32_t timeout_in_ticks)
{
    unsigned int imask;

    imask = irq_lock();

    if (likely(stack->next > stack->base)) {
        // 如果棧中有數據,則直接從棧中取出:
        // 將 next 指針後移,指向數據的頂部
        // 然後取出數據,放入 pData 指向的內存處
        // 然後返回 1,表示出棧操作成功
        stack->next--;
        *pData = *(stack->next);
        irq_unlock(imask);
        return 1;
    }

    if (timeout_in_ticks != TICKS_NONE) {
        // 如果棧中沒有數據,將當前線程(即正在執行出棧操作的線程)
        // 用棧中的 fiber 成員保存起來
        // 然後進行上下文切換
        stack->fiber = _nanokernel.current;
        *pData = (uint32_t) _Swap(imask);
        // 指向完 _Swap() 函數後,將會切換到其它上下文
        // 如果代碼能走到這裏,說明有其它線程向該棧中壓入了數據,並喚醒了本線程
        // 將數據存放到 pData 指向的內存中
        // 然後返回 1,表示出棧操作成功
        return 1;
    }

    // 如果代碼走到這裏,說明執行操作操作的線程不希望進行延時等待
    // 此時出棧操作失敗,立即返回
    irq_unlock(imask);
    return 0;
}

當某線程嘗試從棧中彈出數據時,有兩種可能:

  • 棧中有數據,直接彈出數據頂部的數據
  • 棧中沒有數據,此時會根據入參 timeout_in_ticks 的值來做對應的處理:
    • 等於 TICKS_NONE,表示不進行超時等待,立即返回
    • 不等於 TICKS_NONE,將其陷入阻狀態,並等待其它需要入棧的線程喚醒本線程

與前面所學的信號量、fifo、lifo都不同的是:

  • 棧中沒有設置等待隊列,只用了一個fiber成員,最多隻能保存一個阻塞線程,因此當多個線程都嘗試向一個空棧中彈出數據時,只有最後一個線程能被保存,其它的線程將被替換掉,永遠也無法獲取數據(而且最奇怪的是,這個線程好像也不處於就緒隊列,將永遠不會在被執行)。爲什麼要這樣設計?可能與棧的具體應用有關係,目前還不清楚。
  • 當從棧中彈出數據失敗時,沒有將線程放入超時鏈表中,因此傳入的參數 timeout_in_ticks 只要不等於 TICKS_NONE,將一直陷入阻塞,直到有其它線程需要向棧中壓入數據

nano_task_stack_pop

int nano_task_stack_pop(struct nano_stack *stack, uint32_t *pData, int32_t timeout_in_ticks)
{
    unsigned int imask;

    imask = irq_lock();

    while (1) {
        if (likely(stack->next > stack->base)) {
            // 如果棧中有數據,則直接從棧中取出:
            // 將 next 指針後移,指向數據的頂部
            // 然後取出數據,放入 pData 指向的內存處
            // 然後返回 1,表示出棧操作成功
            stack->next--;
            *pData = *(stack->next);
            irq_unlock(imask);
            return 1;
        }

        if (timeout_in_ticks == TICKS_NONE) {
            // 如果 timeout_in_ticks 等於 TICKS_NONE,跳出循環,立即返回
            break;
        }

        // nano_cpu_atomic_idle() 函數已在《Zephyr OS nano 內核篇:信號量》中詳
        // 細分析過,它會讓cpu進入睡眠模式,如果發生外部中斷,cpu會被喚醒。
        nano_cpu_atomic_idle(imask);
        imask = irq_lock();
    }

    irq_unlock(imask);
    return 0;
}

入棧操作

void nano_stack_push(struct nano_stack *stack, uint32_t data)
{
    static void (*func[3])(struct nano_stack *, uint32_t) = {
        nano_isr_stack_push,
        nano_fiber_stack_push,
        nano_task_stack_push
    };

    func[sys_execution_context_type_get()](stack, data);
}

先看看函數的入參:

  • stack:待壓入數據的棧。
  • data:待壓如棧中的數據,其數據類型是無符號整型。

nano_stack_push會根據當前上下文的類型,調用對應的獲取信號的函數。其中,nano_isr_stack_push() 和 nano_fiber_stack_push() 是函數 nano_stack_push() 的別名。

_stack_push_non_preemptible

void _stack_push_non_preemptible(struct nano_stack *stack, uint32_t data)
{
    struct tcs *tcs;
    unsigned int imask;

    imask = irq_lock();

    tcs = stack->fiber;
    if (tcs) {
        // 如果之前已有線程在等待從棧中取數據,直接設置將數據設置爲該線程的返回值,
        // 然後將其從阻塞態變爲就緒態,加入就緒鏈表
        stack->fiber = 0;
        fiberRtnValueSet(tcs, data);
        _nano_fiber_ready(tcs);
    } else {
        // 將數據壓棧
        *(stack->next) = data;
        // next 指針上移
        stack->next++;
    }

    irq_unlock(imask);
}

注意,由於在入棧時沒有檢查棧是否已滿,所以在使用時必須小心,否則棧溢出了,後果很嚴重。這算不算stack中的一個bug?

nano_task_stack_push

void nano_task_stack_push(struct nano_stack *stack, uint32_t data)
{
    struct tcs *tcs;
    unsigned int imask;

    imask = irq_lock();

    tcs = stack->fiber;
    if (tcs) {
        // 如果之前已有線程在等待從棧中取數據,直接設置將數據設置爲該線程的返回值,
        // 然後將其從阻塞態變爲就緒態,加入就緒鏈表
        stack->fiber = 0;
        fiberRtnValueSet(tcs, data);
        _nano_fiber_ready(tcs);
        // 由於當前上下文是task,而就緒鏈表中存在fiber,所以就緒上下文切換
        _Swap(imask);
        return;
    }

    // 代碼走到這裏,說明之前沒有程序在等待從棧中取數據,直接將數據入棧
    *(stack->next) = data;
    stack->next++;

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