一般了解一份代码大多从启动部分开始,同样这里也采用这种方式,先寻找启动的源头。以 MDK-ARM 为例,MDK-ARM 的用户程序入口为 main() 函数,位于 main.c 文件中。系统启动后先从汇编代码 startup_stm32f103xe.s 开始运行,然后跳转到 C 代码,进行 RT-Thread 系统功能初始化,最后进入用户程序入口 main()。
下面我们来看看在 components.c 中定义的这段代码:
- //components.c 中定义
- /* re-define main function */
- int $Sub$$main(void)
- {
- rt_hw_interrupt_disable();
- rtthread_startup();
- return 0;
- }
在这里 $Sub$$main
函数仅仅调用了 rtthread_startup()
函数。RT-Thread 支持多种平台和多种编译器,而 rtthread_startup()
函数是 RT-Thread 规定的统一入口点,所以 $Sub$$main
函数只需调用 rtthread_startup()
函数即可。例如采用 GNU
GCC
编译器编译的 RT-Thread
,就是直接从汇编启动代码部分跳转到 rtthread_startup()
函数中,并开始第一个 C 代码的执行的。在 components.c
的代码中找到 rtthread_startup()
函数,我们将可以看到 RT-Thread 的启动流程:
- int rtthread_startup(void)
- {
- rt_hw_interrupt_disable();
- /* board level initalization
- * 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();
- #ifdef RT_USING_SIGNALS
- /* signal system initialization */
- rt_system_signal_init();
- #endif
- /* 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;
- }
这部分启动代码,大致可以分为四个部分:
1. 初始化与系统相关的硬件;
2. 初始化系统内核对象,例如定时器,调度器;
3. 初始化系统设备,这个主要是为 RT-Thread 的设备框架做的初始化;
4. 初始化各个应用线程,并启动调度器。
在这里我要做几点说明, 因为官方文档在这里讲解的比较少,比较难理解:
1. 首先来说说$Sub$$main和$Super$$main
函数的用法
这是一种特殊模式:用于有一个已经存在且不能被改变的函数的情况(比如不能更改的库函数);使用这两个模式可以帮原函数打补丁,如存在一个函数foo();
$Sub$ $foo :定义的新功能函数,在foo()函数之前/后使用$Sub$ $foo 可以添加一些新的程序代码。
$Super$ $foo :就是原始的未修补的foo函数,使用这个$Super$ $foo函数将直接跳转到foo()函数。
下面以stm32f10xxx的int main(void)函数为例:
(1) 上电后,运行启动代码startup_stm32f10xxx.s
(2) 从系统初始化(SystemInit)开始执行,将函数地址赋给R0寄存器,跳转到R0地址执行并返回此处。
(3) 将__main函数地址给R0,将函数地址赋给R0,跳转到R0地址执行,不返回。
(4) 跳转到$Sub$$main(自己定义该函数)。
- #if defined (__CC_ARM)
- extern int $Super$$main(void);
- /* re-define main function */
- int $Sub$$main(void)
- {
- preWork() ; // do somthing before call main
- $Super$$main(); // 跳转到 main()
- return 0;
- }
- #endif
讲到这里我们来看看代码中关于这个得用法:
首先在components.c文件中
这里表示,这个函数在main函数之前运行,那么这个函数首先关闭了中断,然后再是rttread系统的启动函数。在该文件下还有
$Super$$main()函数表示将跳转到main函数运行。系统之所以这样的做法是,把用户的main函数和系统的main函数分开,系统的main函数比用户的main函数更前执行,这样就可以避免接触底层,注重应用层开发。
2.接着我们详细来介绍一下系统的运行过程:
(1)我们就不分析汇编代码了,直接从c文件入手,首先进入的是components.c文件中的int $Sub$$main(void)函数,该函数首先运行rt_hw_interrupt_disable()函数, 该函数失能中断
(2)接着运行rtthread_startup()函数,该函数对系统做了初始化,首先rt_hw_interrupt_disable()对失能了硬件中断,为啥要失能两次?? 有待思考, 接着rt_hw_board_init() 板级的初始化,rt_show_version()函数打印了系统的版本号 rt_system_timer_init()函数初始化了系统的定时器。rt_system_scheduler_init()初始化系统调度器。rt_system_signal_init()初始化信号量 ,rt_application_init() 函数是应用初始化,我们具体来看看这个函数
(3)rt_application_init(): 首先定义了一个rt的事件结构体变量,然后调用rt_thread_create创建了一个指向main_thread_entry函数的事件,最后启动了这个事件
(4)我们接着看看main_thread_entry函数,这个函数首先初始化了components, 然后调用了main函数,这里应该不能马上调用,因为系统还没有跑起来,只有等系统调用了,才能运行,分析到这我们就不分析了,接着就分析rt_thread_idle_init函数了, 这是初始化idle,至于这个是啥 我暂时也不知道, 然后再rt_system_scheduler_start 系统调度开始了,上面我们创建了一个main函数的事件, 这就可以运行了
上面的启动代码基本上可以说都是和 RT-Thread 系统相关的,那么用户如何加入自己的应用程序的初始化代码呢?RT-Thread 将 main 函数作为了用户代码入口,只需要在 main 函数里添加自己的代码即可。
- int main(void)
- {
- /* user app entry */
- return 0;
- }
提示
注:为了在进入 main 程序之前,完成系统功能初始化,可以使用 $sub$$
和 $super$$
函数标识符在进入主程序之前调用另外一个例程,这样可以让用户不用去管 main() 之前的系统初始化操作。详见 ARM® Compiler v5.06 for µVision® armlink User Guide。
联系客服