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);
}