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);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值