电子说
本文简单讨论RT-Thread在启动后,逐步进入到处于就绪态最高优先级main线程的全过程。部分内容涉及到汇编指令,但通俗易懂。通过简化工程,配合Debug过程,逐步观察寄存器的变化、绘制栈帧结构、绘制线程控制块和rt_interrupt_from_thread、rt_interrupt_to_thread等典型变量取值(指向,虽然是rt_uint32_t类型,但实际在汇编中是作为指针使用),能有效帮助理解RTOS的线程栈的恢复与启动过程。
通过本文对线程启动过程的了解,对于两个线程/多个线程之间的互相切换能奠定坚实的基础,化繁为简,结合论坛关于上下文切换的代码注释,能帮助快速抓住主线。
使用的软硬件环境如下:
IDE工具 - RT-Thread Studio 2.2.6
硬件 - STM32L431RCT6,Cortex M4内核
软件 - RT-Thread 4.0.5版本
配置 - 仅使能main线程和tidle0线程
一、工程设置
Step 1. 新建名称为EVBMX_RTThread405_Switch的4.0.5版本工程
Step 2. 不使能软件定时器,使能线程状态更改的调试
关闭软件定时器线程,避免干扰。
Step 3. 关闭msh shell,禁用Finsh
关闭tshell线程,避免干扰。仅仅保留main线程和tidle0线程。
Step 4. 修改main函数
修改main函数后,线程进入一次,休眠且切换1次,再次切回且return,然后彻底退出,只留下tidle0线程。
#include
#define DBG_TAG "main"
#define DBG_LVL DBG_LOG
#include
int main(void)
{
rt_thread_mdelay(1000);
return RT_EOK;
}
Step 5. 下载程序,观察输出结果
读完全文后,对下方输出结果的每一行语句所代表的含义和发生时刻,能有更深刻体会。
二、调试运行
Step 6. 在component.c中257行按F9设置断点;F5全速运行到此处后,再按F9关闭此处断点。
Step 7. 依次进入rt_thread_create, _thread_init, 停留在thread.c的164行。
将变量thread添加到表达式窗口,可以查看各个成员的值,其中,thread->stack_addr = 0x20001138, thread->stack_size = 0x800,分别表示栈底位置和栈空间大小。
164行的函数rt_hw_stack_init对于理解线程切换是一个相当重要的函数,其形参分别为:
线程入口函数:main_thread_entry
线程参数RT_NULL:
线程栈栈顶地址:thread->stack_addr + thread->stack_size - 4 = 0x20001138 + 0x800 - 4 = 0x20001934
Step 8. 单步进入到rt_hw_stack_init函数内部,开展分析
149行,由于传递进来的stack_addr = 0x20001934,执行完毕后,stk为0x20001938。从0x20001138(含)到0x20001934(含),合计是0x800 = 2048字节。STM32使用的满递减栈,所以此处的stk是0x20001938。
150行,此处设置8字节对齐。由于0x20001938 = (536877368)Decimal,该数据除8等于67109671,能被8整除,该语句执行栈对齐操作后,stk依然为0x20001938。
Step 9. 继续了解rt_hw_stack_init函数。
151行,更新stk的值,减去struct stack_frame结构体的大小。执行完毕后,stk = 0x200018F4。
153行,stack_frame指针指向0x200018F4。
156至159行,通过for循环将0x200018F4至0x20001938的所有内存变成0xdeadbeaf魔法字。
161行至168行,将stack_frame成员的exception_stack_frame中的r0~psr共8个寄存器分别设置为:线程参数,4个0,线程返回地址,线程入口地址,0x01000000。
175行,返回stk的值,此时变成0x200018F4。这个值在初始化线程时,将返回给thread->sp,即线程栈的临时栈顶指针。
依次将线程的形参、r1-r3, r12, 线程返回地址、线程入口地址,线程的xPSR写入异常栈帧结构中。
在初入门时,这里是难点。C语言中使用结构体定义的栈结构,如何和实际寄存器的顺序进行一一对应?,后文会通过逐步Debug揭示这个问题答案。
至此,main线程创建完毕后,线程结构体和线程栈空间如下所示。
Step 10. 继续单步到rt_system_scheduler_start函数处,并单独跟踪进入到该函数内部。
期间,RT-Thread会调用rt_thread_idle_init函数,在该函数中使用静态创建方式初始化tidle0线程。可以按照上述过程记录tidle0线程的栈空间。
全部0条评论
快来发表一下你的评论吧 !