本文参考自[野火EmbedFire]《RT-Thread内核实现与应用开发实战——基于STM32》,仅作为个人学习笔记。更详细的内容和步骤请查看原文(可到野火资料下载中心下载)
文章目录
当你拿到一个移植好的 RT-Thread 工程的时候,你去看 main 函数,只能在 main 函数里面看到创建线程和启动线程的代码,硬件初始化,系统初始化,启动调度器等信息都看不到。那是因为RT-Thread 拓展了 main 函数,在 main 函数之前把这些工作都做好了。
——原文
Reset_Handler 复位函数
系统上电后第一个函数是启动文件中的复位函数,这个启动文件由汇编语言编写,复位函数会调用C库函数__main
,该函数的主要工作时初始化系统的堆和栈,最后调用main()
函数,进入C语言的世界。
Reset_Handler PROC
EXPORT Reset_Handler [WEAK]
IMPORT __main
IMPORT SystemInit
LDR R0, =SystemInit
BLX R0
LDR R0, =__main
BX R0
ENDP
$Sub$$main 函数
原文说单步执行完
__main
之后,会跳转到component.c
中的$Sub$$main()
函数,但我单步到__main
后,直接到了main()
函数(这可能是我操作不到位吧)。
在Keil的编译器中,存在$Sub$$
和$Super$$
这样一对符号,它们可以扩展一个函数,比如使用$Sub$$main()
可以在执行main()
之前执行$Sub$$main()
,然后调用$Super$$main()
来切换到main()
函数中。
下面是component.c
中的部分函数(针对Keil编译器)
extern int $Super$$main(void);
/* re-define main function */
int $Sub$$main(void)
{
rtthread_startup();
return 0;
}
/* the system main thread */
void main_thread_entry(void *parameter)
{
extern int main(void);
extern int $Super$$main(void);
#ifdef RT_USING_COMPONENTS_INIT
/* RT-Thread components initialization */
rt_components_init();
#endif
/* invoke system main function */
$Super$$main(); /* for ARMCC. */
}
rtthread_startup() 函数
上一步$Sub$$main()
函数中只有rtthread_startup()
这么一个函数,RT-Thread的所有初始化都在这个函数里实现。
其中rt_hw_board_init()
这个函数我们已经接触到多次,定义在board.c
中,用来初始化底层固件。
int rtthread_startup(void)
{
rt_hw_interrupt_disable();
/* board level initialization
* NOTE: please initialize heap inside board initialization.
*/
rt_hw_board_init();
/* show RT-Thread version */
rt_show_version();
/* timer system initialization */
rt_system_timer_init();
/* scheduler system initialization */
rt_system_scheduler_init();
/* create init_thread */
rt_application_init();
/* timer thread initialization */
rt_system_timer_thread_init();
/* idle thread initialization */
rt_thread_idle_init();
/* start scheduler */
rt_system_scheduler_start();
/* never reach here */
return 0;
}
rt_application_init() 函数
rt_application_init()
函数里创建了一个线程,这是一个初始线程,当所有应用线程都成功创建好后,初始线程就把自己关闭。函数最后一行调用了rt_thread_startup()
函数,虽然我们知道这个函数的作用是启动线程,但这个时候线程并没有启动,因为在上层函数rtthread_startup()
最后一行(见上文)的rt_system_scheduler_start()
还未执行,系统的线程还未开始调度。
void rt_application_init(void)
{
rt_thread_t tid;
#ifdef RT_USING_HEAP
tid = rt_thread_create("main", main_thread_entry, RT_NULL,
RT_MAIN_THREAD_STACK_SIZE, RT_MAIN_THREAD_PRIORITY, 20);
RT_ASSERT(tid != RT_NULL);
#else
rt_err_t result;
tid = &main_thread;
result = rt_thread_init(tid, "main", main_thread_entry, RT_NULL,
main_stack, sizeof(main_stack), RT_MAIN_THREAD_PRIORITY, 20);
RT_ASSERT(result == RT_EOK);
/* if not define RT_USING_HEAP, using to eliminate the warning */
(void)result;
#endif
rt_thread_startup(tid);
}
main_thread_entry() 函数
下面是初始线程的入口函数,主要作用是进行组件初始化,最后调用$Super$main()
回到main()
函数中。
#ifndef RT_USING_HEAP
/* if there is not enable heap, we should use static thread and stack. */
ALIGN(8)
static rt_uint8_t main_stack[RT_MAIN_THREAD_STACK_SIZE];
struct rt_thread main_thread;
#endif
/* the system main thread */
void main_thread_entry(void *parameter)
{
extern int main(void);
extern int $Super$$main(void);
#ifdef RT_USING_COMPONENTS_INIT
/* RT-Thread components initialization */
rt_components_init();
#endif
/* invoke system main function */
#if defined(__CC_ARM) || defined(__CLANG_ARM)
$Super$$main(); /* for ARMCC. */
#elif defined(__ICCARM__) || defined(__GNUC__)
main();
#endif
}
main() 函数
main()
函数是通过初始线程调用的,前面提到初始线程在后面需要自己关闭,意思就是main()
函数运行完之后,整个系统就和main()
无关了。这里和裸机系统差别很大,裸机系统的main()
函数里都有一个while(1)
,所以单片机上电后main()
函数就没有停止运行过,而RT-Thread(以及一些其他的RTOS)的main()
是不包含死循环的,取而代之的是一堆线程的创建和初始化。
到这里RT-Thread的整个启动流程就基本介绍完了,关于main()
运行结束后系统是如何运作的,这里就不作论述。