[017] [RT-Thread学习笔记] 线程栈的初始化

RT-Thread
学习笔记
线程栈初始
化代码分析
打印线程信息

RT-Thread版本:4.0.5
MCU型号:STM32F103RCT6(ARM Cortex-M3 内核)

1 线程栈初始化代码分析

在初始化/创建线程时会调用_thread_init()函数,关键代码如下:

static rt_err_t _thread_init(struct rt_thread *thread,
                             const char       *name,
                             void (*entry)(void *parameter),
                             void             *parameter,
                             void             *stack_start,
                             rt_uint32_t       stack_size,
                             rt_uint8_t        priority,
                             rt_uint32_t       tick)
{
    thread->entry = (void *)entry;
    thread->parameter = parameter;

    /* stack init */
    thread->stack_addr = stack_start;
    thread->stack_size = stack_size;

    /* init thread stack */
    rt_memset(thread->stack_addr, '#', thread->stack_size);
#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 */
}

rt_memset(thread->stack_addr, '#', thread->stack_size);将线程栈每个字节初始化为magic数字#(0x23),这样做是为了计算栈的利用率,即未使用的栈空间内容为#

然后将以下参数传入到线程栈初始化函数rt_hw_stack_init()中:

  • thread->entry:线程入口函数地址
  • thread->parameter:线程入口函数形参地址
  • 线程栈顶指针sp地址stack_addr
    • ARCH_CPU为向上生长的栈,栈顶地址即为数组首地址((char *)thread->stack_addr),将其转为char*便于字节对齐;
    • 其他CPU为向下生长的栈(如ARM),栈顶地址(char *)thread->stack_addr + thread->stack_size,即数组首地址+数组大小,然后减去sizeof(rt_ubase_t)rt_ubase_t4字节)向下偏移4个字节(chra*指针),因为SP指针每次操作必须为4字节,偏移后指向栈顶第一个元素。
  • _thread_exit:退出线程函数地址

rt_hw_stack_init()函数返回值为当前线程的栈地址,下面开始分析。

异常发生时保存寄存器数据的结构体(cpuport.c中):

struct exception_stack_frame
{
    /* 异常发生时自动保存的寄存器*/
    rt_uint32_t r0;
    rt_uint32_t r1;
    rt_uint32_t r2;
    rt_uint32_t r3;
    rt_uint32_t r12;
    rt_uint32_t lr;
    rt_uint32_t pc;
    rt_uint32_t psr;
};

struct stack_frame
{
    /* r4 ~ r11 register */
    异常发生时需手动保存的寄存器*/
    rt_uint32_t r4;
    rt_uint32_t r5;
    rt_uint32_t r6;
    rt_uint32_t r7;
    rt_uint32_t r8;
    rt_uint32_t r9;
    rt_uint32_t r10;
    rt_uint32_t r11;

    struct exception_stack_frame exception_stack_frame;
};

rt_hw_stack_init()函数(cpuport.c中):

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;

    // stack_addr为栈顶地址-4, 再+4变为栈顶地址
    stk  = stack_addr + sizeof(rt_uint32_t);
    // 让栈顶指针向下8字节对齐
    stk  = (rt_uint8_t *)RT_ALIGN_DOWN((rt_uint32_t)stk, 8);
    // stk指针继续向下偏移sizeof(struct stack_frame)个字节,即16个字大小(16*4个字节)
    stk -= sizeof(struct stack_frame);
	// 将stk指针强制转化为stack_frame类型后存到stack_frame, stack_frame指向如下图1
    stack_frame = (struct stack_frame *)stk;

    // 以stack_frame为起始地址,将栈空间里面的sizeof(struct stack_frame) 个内存初始化为0xdeadbeef(图2)
    for (i = 0; i < sizeof(struct stack_frame) / sizeof(rt_uint32_t); i ++)
    {
        ((rt_uint32_t *)stack_frame)[i] = 0xdeadbeef;
    }

    // 初始化异常发生时自动保存的寄存器(图3)
    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 */

    // 返回线程的当前栈地址
    return stk;
}

关于字节对齐:CM3总线宽度的32位的,一般栈保持4字节对齐即可,但是浮点运算需要8字节对齐,如果是4字节对齐会导致浮点数有4字节的错位。需注意的是每次CPU弹栈和压栈依然是4字节。(RT_ALIGN_DOWN宏用法参考:字节对齐宏
[017] [RT-Thread学习笔记] 线程栈的初始化

图1 stack_frame 在线程栈里面的指向

[017] [RT-Thread学习笔记] 线程栈的初始化

图2 sizeof(struct stack_frame) 个内存初始化为0xdeadbeef

[017] [RT-Thread学习笔记] 线程栈的初始化

图3 初始化异常发生时自动保存的8个寄存器

函数返回值即为线程当前栈地址,由thread->sp保存。关于寄存器参考:ARM内部寄存器

  • xPSR:位24表示Thumb状态,总为1,清除此位会引起错误异常
  • pc:即r15,保存线程的入口函数地址
  • lr:即r14,连接寄存器,保存线程退出函数的地址
  • r1~r3、r12:通用寄存器,初始化为0
  • r0:根据 ARM APCS调用标准, 将第一个参数保存在r0寄存器,这里为线程入口函数的形参

2 打印线程信息

list_thread()函数通过MSH_CMD_EXPORT命令导入到FinSH命令列表中(cmd.c中):

long list_thread(void)
{
    rt_ubase_t level;
    list_get_next_t find_arg;
    rt_list_t *obj_list[LIST_FIND_OBJ_NR];
    rt_list_t *next = (rt_list_t *)RT_NULL;
    const char *item_title = "thread";
    int maxlen;

    list_find_init(&find_arg, RT_Object_Class_Thread, obj_list, sizeof(obj_list) / sizeof(obj_list[0]));

    maxlen = RT_NAME_MAX;

    rt_kprintf("%-*.s pri  status      sp     stack size max used left tick  error\n", maxlen, item_title);
    object_split(maxlen);
    rt_kprintf(" ---  ------- ---------- ----------  ------  ---------- ---\n");

    do
    {
        next = list_get_next(next, &find_arg);
        {
            int i;
            for (i = 0; i < find_arg.nr_out; i++)
            {
                struct rt_object *obj;
                struct rt_thread thread_info, *thread;

                obj = rt_list_entry(obj_list[i], struct rt_object, list);
                level = rt_hw_interrupt_disable();

                if ((obj->type & ~RT_Object_Class_Static) != find_arg.type)
                {
                    rt_hw_interrupt_enable(level);
                    continue;
                }
                /* copy info */
                rt_memcpy(&thread_info, obj, sizeof thread_info);
                rt_hw_interrupt_enable(level);

                thread = (struct rt_thread *)obj;
                {
                    rt_uint8_t stat;
                    rt_uint8_t *ptr;

                    rt_kprintf("%-*.*s %3d ", maxlen, RT_NAME_MAX, thread->name, thread->current_priority);

                    stat = (thread->stat & RT_THREAD_STAT_MASK);
                    if (stat == RT_THREAD_READY)        rt_kprintf(" ready  ");
                    else if (stat == RT_THREAD_SUSPEND) rt_kprintf(" suspend");
                    else if (stat == RT_THREAD_INIT)    rt_kprintf(" init   ");
                    else if (stat == RT_THREAD_CLOSE)   rt_kprintf(" close  ");
                    else if (stat == RT_THREAD_RUNNING) rt_kprintf(" running");

#if defined(ARCH_CPU_STACK_GROWS_UPWARD)
                    ptr = (rt_uint8_t *)thread->stack_addr + thread->stack_size - 1;
                    while (*ptr == '#')ptr --;

                    rt_kprintf(" 0x%08x 0x%08x    %02d%%   0x%08x %03d\n",
                               ((rt_ubase_t)thread->sp - (rt_ubase_t)thread->stack_addr),
                               thread->stack_size,
                               ((rt_ubase_t)ptr - (rt_ubase_t)thread->stack_addr) * 100 / thread->stack_size,
                               thread->remaining_tick,
                               thread->error);
#else
                    ptr = (rt_uint8_t *)thread->stack_addr;
                    while (*ptr == '#')ptr ++;

                    rt_kprintf(" 0x%08x 0x%08x    %02d%%   0x%08x %03d\n",
                               thread->stack_size + ((rt_ubase_t)thread->stack_addr - (rt_ubase_t)thread->sp),
                               thread->stack_size,
                               (thread->stack_size - ((rt_ubase_t) ptr - (rt_ubase_t) thread->stack_addr)) * 100
                               / thread->stack_size,
                               thread->remaining_tick,
                               thread->error);
#endif
                }
            }
        }
    }
    while (next != (rt_list_t *)RT_NULL);

    return 0;
}
MSH_CMD_EXPORT(list_thread, list thread);

可以用splist_thread命令查看线程所有信息:

msh />list_thread
thread   pri  status      sp     stack size max used left tick  error
-------- ---  ------- ---------- ----------  ------  ---------- ---
tshell    20  ready   0x00000118 0x00001000    29%   0x00000009 000
tidle     31  ready   0x0000005c 0x00000200    28%   0x00000005 000
timer      4  suspend 0x00000078 0x00000400    11%   0x00000009 000
字段 描述
thread 线程的名称
pri 线程的优先级
status 线程当前的状态
sp 线程当前的栈位置
stack size 线程的栈大小
max used 线程历史中使用的最大栈位置
left tick 线程剩余的运行节拍数
error 线程的错误码

下面分析计算方法(仅分析向下生长的栈):

  • spthread->stack_size + ((rt_ubase_t)thread->stack_addr为栈顶地址(数组尾地址),减去当前线程栈指针地址(rt_ubase_t)thread->sp,得到当前线程在栈中的偏移位置(从后往前,注意不是地址)

  • stack size:即初始化时栈的大小thread->stack_size

  • max used:利用线程栈在初始化时被赋值的magic数字#,将其ptr指向栈基地址(数组首地址)(rt_uint8_t *)thread->stack_addr(必须转换成rt_uint8_t *指针,这样指针++才是偏移1个字节),然后执行while (*ptr == '#')ptr ++;,指向最后一块未使用的内存地址,再将ptr减去基地址thread->stack_addr,得到未使用的字节数,最后根据栈的总大小计算出线程使用时消耗的最大栈空间百分比。

关于-*.*s:第一个*为字符输出所占位宽,不足用空格补齐;第二个*为字符个数,为RT_NAME_MAX(默认为8),s即要打印的字符串thread->name


参考:

  1. FinSH 控制台
  2. C语言中%*.*s

END

上一篇:Go语言数据结构与算法-栈


下一篇:日复一日,c复一c