rt-thread线程栈初始化
1. RT Thread线程管理
thread对象是通过rt_thread_create函数动态创建的,在创建thread的栈空间是通过分配堆内存的形式,所以动态实际上也指的是栈的分配是动态的。
rt_thread_create会调用_thread_init对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);
可以看到rt_hw_stack_init函数第二个入参是栈顶指针,栈顶指针根据栈的生长方式有所不同,分为向上增长和向下增长。

ARCH_CPU_STACK_GROWS_UPWARD表示是向上增长,从低地址往高地址增长,所以传入rt_hw_stack_init的第二个入参是申请到的栈空间的首地址:
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传入的第二个入参就是(rt_uint8_t *)((char *)thread->stack_addr + thread->stack_size - sizeof(rt_ubase_t) ,大家应该会好奇为什么需要减去sizeof(rt_ubase_t)。在一开始看到这部分的代码的时候,我也是很疑惑的。最近再重新看这部分的时候才真正理解为什么要减sizeof(rt_ubase_t)。
我们首先从rt_hw_stack_init的源码分析,然后再写的例子反汇编验证下我们的猜想。这个问题我找了好多资料都没有详细解释这部分的代码。因为我使用的是qemu的vexpress-a9代码,所以这里是以vexpress-a9的代码为例。
rt_uint8_t *rt_hw_stack_init(void *tentry, void *parameter,
rt_uint8_t *stack_addr, void *texit)
{
rt_uint32_t *stk;
stack_addr += sizeof(rt_uint32_t);
stack_addr = (rt_uint8_t *)RT_ALIGN_DOWN((rt_uint32_t)stack_addr, 8);
stk = (rt_uint32_t *)stack_addr;
*(--stk) = (rt_uint32_t)tentry; /* entry point */
*(--stk) = (rt_uint32_t)texit; /* lr */
*(--stk) = 0xdeadbeef; /* r12 */
*(--stk) = 0xdeadbeef; /* r11 */
*(--stk) = 0xdeadbeef; /* r10 */
*(--stk) = 0xdeadbeef; /* r9 */
*(--stk) = 0xdeadbeef; /* r8 */
*(--stk) = 0xdeadbeef; /* r7 */
*(--stk) = 0xdeadbeef; /* r6 */
*(--stk) = 0xdeadbeef; /* r5 */
*(--stk) = 0xdeadbeef; /* r4 */
*(--stk) = 0xdeadbeef; /* r3 */
*(--stk) = 0xdeadbeef; /* r2 */
*(--stk) = 0xdeadbeef; /* r1 */
*(--stk) = (rt_uint32_t)parameter; /* r0 : argument */
/* cpsr */
if ((rt_uint32_t)tentry & 0x01)
*(--stk) = SVCMODE | 0x20; /* thumb mode */
else
*(--stk) = SVCMODE; /* arm mode */
/* return task's current stack address */
return (rt_uint8_t *)stk;
}
rt_hw_stack_init的代码逻辑很简单,实际上就是要把一些参数,包括栈的起始地址、线程的参数、线程的退出处理函数等等,存放栈上。现在arm、riscv、x86架构的芯片都是要求栈是8地址对齐的。所以这里减去sizeof(rt_ubase_t)是为了能够使得栈地址是8字节对齐的。
假设thread->stack_addr的地址是0x6007D140,那么传给rt_hw_stack_init的第二参数则是0x6007D13C,那么在初始化栈的时候,会加上4字节,然后再向下进行8字节对齐。但是由于要存储这些寄存器和参数等,还需要在保存玩这些寄存器和参数之后,确保stk也是8字节对齐。上面代码中需要保存14项数据,每项数据占用4个字节,所以要确保:0x6007D140 - 14 x 8 (结果是0x6007D0F8) (其中14是十进制数)是8字节对齐的。
这是对于有的芯片架构保存的数据刚好是能够保证相减之后的结果是8字节对齐的。而有的芯片是需要减去4字节的,例如nois-ii的芯片,这款芯片是需要4字节对齐的。

rt_uint8_t *rt_hw_stack_init(void *tentry, void *parameter,
rt_uint8_t *stack_addr, void *texit)
{
unsigned long *stk;
stk = (unsigned long *)stack_addr;
*(stk) = 0x01; /* status */
*(--stk) = (unsigned long)texit; /* ra */
*(--stk) = 0xdeadbeef; /* fp */
*(--stk) = 0xdeadbeef; /* r23 */
*(--stk) = 0xdeadbeef; /* r22 */
*(--stk) = 0xdeadbeef; /* r21 */
*(--stk) = 0xdeadbeef; /* r20 */
*(--stk) = 0xdeadbeef; /* r19 */
*(--stk) = 0xdeadbeef; /* r18 */
*(--stk) = 0xdeadbeef; /* r17 */
*(--stk) = 0xdeadbeef; /* r16 */
*(--stk) = 0xdeadbeef; /* r7 */
*(--stk) = 0xdeadbeef; /* r6 */
*(--stk) = 0xdeadbeef; /* r5 */
*(--stk) = (unsigned long)parameter; /* r4 argument */
*(--stk) = 0xdeadbeef; /* r3 */
*(--stk) = 0xdeadbeef; /* r2 */
*(--stk) = (unsigned long)tentry; /* pc */
/* return task's current stack address */
return (rt_uint8_t *)stk;
}
上面是nois-ii芯片的rt_hw_stack_init函数实现。
还是假设thread->stack_addr的地址是0x6007D140,那么传给rt_hw_stack_init的第二参数则是0x6007D13C。上面需要保存的是17项数据,那么最后的计算结果和上面是一样的,能够保证栈地址是4字节对齐的。

浙公网安备 33010602011771号