您的位置:首页 > 其它

Zephys OS nano 内核篇:栈 stack

2016-10-07 17:20 302 查看
Zephyr OS 所有的学习笔记已托管到 Github,CSDN 博客里的内容只是 Github 里内容的拷贝,因此链接会有错误,请谅解。

最新的学习笔记请移步 GitHub:https://github.com/tidyjiang8/zephyr-inside

栈的类型定义

栈的初始化

出栈操作

_stack_pop

nano_task_stack_pop

入栈操作

_stack_push_non_preemptible

nano_task_stack_push

栈是 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);
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  Zephyr Project Kernel Stack