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_t
4字节)向下偏移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
宏用法参考:字节对齐宏)
函数返回值即为线程当前栈地址,由
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);
可以用sp
或list_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 | 线程的错误码 |
下面分析计算方法(仅分析向下生长的栈):
-
sp
:thread->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
。
参考:
END