ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

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

2022-02-20 14:03:49  阅读:239  来源: 互联网

标签:RT rt Thread thread frame uint32 线程 stack


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宏用法参考:字节对齐宏
在这里插入图片描述

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

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

在这里插入图片描述

图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

标签:RT,rt,Thread,thread,frame,uint32,线程,stack
来源: https://blog.csdn.net/kouxi1/article/details/123026460

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有