转载出处:https://blog.csdn.net/qq_43332314/article/details/132261334?spm=1001.2014.3001.5502

转载作者:爱出名的狗腿子

rt_hw_stack_init rt-thread线程栈初始化参数分析

本文代码内采用的rtthread源码采用master分支分析,对应commit:dbf1463176921bed3310fbd9dd400897b64f501b

Github 源码地址链接!!!

Q:

RT-Thread 在线程初始化的代码内有一段初始化线程堆栈的代码,如下:

thread->sp = (void *)rt_hw_stack_init(thread->entry, thread->parameter,
                                     (rt_uint8_t *)((char *)thread->stack_addr + thread->stack_size - sizeof(rt_ubase_t)),
                                     (void *)_thread_exit);

在调用 rt_hw_stack_init() 初始化堆栈的时候传入线程栈起始地址进行了 -sizeof(rt-ubase_t) 操作,而在 rt_hw_stack_init() 函数内又进行 stk = stack_addr + sizeof(rt_uint32_t); 将其给加了回去,这操作的意义是什么呢?还是说是历史遗留问题?

在《野火 RT-Thread内核实现与应用开发指南》内也有说到此处的设计,但并未进行升入说明,仅简单的一笔带过,因此大多数读者和我一样都对此充满疑问。

A:

1. rt_hw_stack_init调用分析

分析此问题,首先我们需要结合完整版本的 rt-thread 内核代码进行阅读才能更好的充分理解。

在rt-thread内核代码中,初始化线程堆栈的时候其实是有一个宏声明进行选择的,具体代码如下:

#ifdef ARCH_CPU_STACK_GROWS_UPWARD
    thread->sp = (void *)rt_hw_stack_init(thread->entry, thread->parameter,
                                          (void *)((char *)thread->stack_addr),
                                          (void *)_thread_exit);
#else
    thread->sp = (void *)rt_hw_stack_init(thread->entry, thread->parameter,
                                          (rt_uint8_t *)((char *)thread->stack_addr + thread->stack_size - sizeof(rt_ubase_t)),
                                          (void *)_thread_exit);
#endif /* ARCH_CPU_STACK_GROWS_UPWARD */


也就是针对不同架构的CPU实际传入此函数的参数还存在着不一样的地方!

针对 栈是向下增长型 的CPU架构,传入的参数为:(rt_uint8_t *)((char *)thread->stack_addr + thread->stack_size - sizeof(rt_ubase_t))
针对 栈是向上增长型 的CPU架构,传入的参数为:(void *)((char *)thread->stack_addr)
而此参数的含义为栈的起始地址!

线程的栈也就是一块连续地址空间的数组,这个是理解栈的前提;针对向上增长型的栈,栈起始地址就是 thread->stack_addr 这很好理解,对于向下增长型的栈,就需要注意了,起始地址并不是,thread->stack_addr + thread->stack_size!!!

既然栈就是一块数组,那我们不妨用数组来理解,char table[100],数组table的最顶部的成员不是table[100],而是table[99],即table[100-1]。因此向下增长的栈从顶部往底部填充数据就类似于数组从尾部往头部填充数据,起始地址为: (rt_uint8_t *)((char *)thread->stack_addr + thread->stack_size - sizeof(rt_ubase_t))


同时此处的代码是放在 thread.c 内,thread.c是内核文件,是公共的文件,不管你是什么硬件平台,不管你是什么CPU架构,在内核的角度看,它只管给 rt_hw_stack_init 函数传入栈的起始地址即可,因此针对向下增长型的栈在这里 -sizeof(rt_ubase_t)) 并没有任何问题。

再往下层,具体到cpu上,每个cpu都会有对应的 cpuport.c 来实现对应的 rt_hw_stack_init 函数,并根据各自的cpu结构来实现具体的线程栈初始化。

2. rt_hw_stack_init 实现分析

2.1 向下增长型栈 rt_hw_stack_init 实现

针对向下增长型的栈,以 cortex-m4 内核为例:

rt_uint8_t *rt_hw_stack_init(void       *tentry,
                             void       *parameter,
                             rt_uint8_t *stack_addr,
                             void       *texit)
{
    struct stack_frame *stack_frame;
    rt_uint8_t         *stk;
    unsigned long       i;

    stk  = stack_addr + sizeof(rt_uint32_t);
    stk  = (rt_uint8_t *)RT_ALIGN_DOWN((rt_uint32_t)stk, 8);
    stk -= sizeof(struct stack_frame);

    stack_frame = (struct stack_frame *)stk;

    /* init all register */
    for (i = 0; i < sizeof(struct stack_frame) / sizeof(rt_uint32_t); i ++)
    {
        ((rt_uint32_t *)stack_frame)[i] = 0xdeadbeef;
    }

    stack_frame->exception_stack_frame.r0  = (unsigned long)parameter; /* r0 : argument */
    stack_frame->exception_stack_frame.r1  = 0;                        /* r1 */
    stack_frame->exception_stack_frame.r2  = 0;                        /* r2 */
    stack_frame->exception_stack_frame.r3  = 0;                        /* r3 */
    stack_frame->exception_stack_frame.r12 = 0;                        /* r12 */
    stack_frame->exception_stack_frame.lr  = (unsigned long)texit;     /* lr */
    stack_frame->exception_stack_frame.pc  = (unsigned long)tentry;    /* entry point, pc */
    stack_frame->exception_stack_frame.psr = 0x01000000L;              /* PSR */

#if USE_FPU
    stack_frame->flag = 0;
#endif /* USE_FPU */

    /* return task's current stack address */
    return stk;
}

继续以char table[100]作为栈举例:

  1. stk = stack_addr + sizeof(rt_uint32_t); 拿到栈的最顶端的值,也就是100,注意table[100]这个成员是不能写值的。


  1. stk = (rt_uint8_t *)RT_ALIGN_DOWN((rt_uint32_t)stk, 8); 之后按8字节 向下对齐,那就 stk 就变成了 96,table[97]、table[98]、table[99]由于字节对齐就保留了,后续也不会去使用,至于table[96]用没用还不知道,我们接着看。

  1. stk -= sizeof(struct stack_frame);,stk 减掉 struct stack_frame 结构大小存储 struct stack_frame 结构数据,假定 struct stack_frame 大小4字节, stk -= sizeof(struct stack_frame); 之后 stk 为92,之后写4字节数据,那么stk[92]、stk[93]、stk[94]、stk[95]填充了数据,stk[96]不会去访问。

  1. 因此无论字节对齐的时候有没有保留字节,第一步stk虽然切换到了栈最顶端,但是并不会访问最顶端的那个成员,所以是安全的!

2.1 向上增长型栈 rt_hw_stack_init 实现

注意向上增长型栈初始化代码就不是上面那一份了!上面我们说了针对不同的cpu,会有不同的cpuport.c文件来实现对应的 rt_hw_stack_init,因此我们需要找到向上增长型的cpu对应的cpuport.c来分析才行。

在rtthread内核中,目前仅TI的tms320f28379为向上增长型,对应的cpuport.c在 libcpu/ti-dsp/c28x/cpuport.c内,它实现的 rt_hw_stack_init 函数如下:
(不要问我怎么找到的,根据宏全局搜ARCH_CPU_STACK_GROWS_UPWARD=y能发现只有ti这颗用的向上增长型! T_T