1.项目准备
上一节的基本环境,如rh-thread 基本环境的搭建,硬件材料stm32f103C8T6 以及st-link
rt-thread 内核启动官网分析
在分析rt-thread代码的时候,由于rt-thread的代码是十分优秀的,你完全不需要看每个函数实现的细节,就根据每个函数名字,可以分析出这个函数是干什么用的.
2.内核单步调试
如图程序从stm32 汇编入口开始启动,单步执行代码,会跳转到rt-thread内核启动的方法里面,为int
S
u
b
Sub
Sub$main(void)函数里面代码如下:
int $Sub$$main(void)
{
rt_hw_interrupt_disable();
rtthread_startup();
return 0;
}
rt_hw_interrupt_disable()读名字,为禁止硬件中断
rtthread_startup();读名字,为rt-thread开始启动
在继续单步调试,rtthread_startup()函数其代码如下:
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;
}
可以发现每个函数上面都有注释,而且见函数名,我们大致都知道每个函数的作用了,具体分析可以见rt-thread官网内核分析
这里对 rt_hw_board_init();进行单步调试分析,单步进入 rt_hw_board_init()函数里面,可以看见起代码如下:
void rt_hw_board_init(void)
{
HAL_Init();
SystemClock_Config();
#ifdef RT_USING_HEAP
rt_system_heap_init((void *)HEAP_BEGIN, (void *)HEAP_END);
#endif
#ifdef RT_USING_COMPONENTS_INIT
rt_components_board_init();
#endif
#ifdef RT_USING_CONSOLE
rt_console_set_device(RT_CONSOLE_DEVICE_NAME);
#endif
}
还是看函数名字,我们就知道每个函数是什么用的
函数 | 作用 |
---|
HAL_Init() | HAL库的初始化 |
SystemClock_Config() | systick时钟初始化 |
rt_system_heap_init() | 系统内存堆的初始化 |
rt_components_board_init(); | board级组件的初始化 |
rt_console_set_device() | 为console设置一个设备(此处命令行用输入输出通过串口) |
分析 rt_components_board_init(); 函数代码如下:
void rt_components_board_init(void)
{
#if RT_DEBUG_INIT
int result;
const struct rt_init_desc *desc;
for (desc = &__rt_init_desc_rti_board_start;
desc < &__rt_init_desc_rti_board_end; desc ++)
{
rt_kprintf("initialize %s", desc->fn_name);
result = desc->fn();
rt_kprintf(":%d done\n", result);
}
#else
const init_fn_t *fn_ptr;
for (fn_ptr = &__rt_init_rti_board_start;
fn_ptr < &__rt_init_rti_board_end; fn_ptr++)
{
(*fn_ptr)();
}
#endif
}
此处没有定义宏RT_DEBUG_INIT,因此代码执行
const init_fn_t *fn_ptr;
for (fn_ptr = &__rt_init_rti_board_start;
fn_ptr < &__rt_init_rti_board_end; fn_ptr++)
{
(*fn_ptr)();
}
个人认为此处是rt-thread代码写的精彩的地方之一.
此处可以发现__rt_init_rti_board_start,以及__rt_init_rti_board_end从来没有定义过,但是可以编译通过,甚至调试,这里面肯定内有乾坤
const init_fn_t *fn_ptr;此处定义了一个指针 fn_ptr,在for循环里面,有(fn_ptr)();这行代码典型的函数调用代码,因此fn_ptr指针指向的是一个函数,而&__rt_init_rti_board_start肯定是一个函数的地址.继续单步调试,看栈里面fn_ptr值如图所示:
可以发现fn_ptr指向函数,rti_board_start();这个函数在这里有定义了,其定义如下:
static int rti_board_start(void)
{
return 0;
}
INIT_EXPORT(rti_board_start, "0.end");
观察这个几行代码,INIT_EXPORT(rti_board_start, “0.end”);这个有点像函数的调用,但是在此处,有不是在函数里面,这么可能进行函数调用?打开这个INIT_EXPORT();go to definition 去定义这个函数的地方,其定义如下:
#define INIT_EXPORT(fn, level) \
RT_USED const init_fn_t __rt_init_##fn SECTION(".rti_fn."level) = fn
其中RT_USED定义为
#define RT_USED __attribute__((used))
#define SECTION(x) __attribute__((section(x)))
因此这句宏定义代码的意思为:
定义一个变量为 const init_fn_t rt_init_rti_board_start,这个变量的位置通过__attribute((section(x)))来指定,即放在程序段".rti_fn.0.end"里面,并且指向rti_board_start()这个函数.而且此处在编译阶段就完成的过程.具体解释可以查看 attribute((section(x)))的用法
因此for循环里面的变量,__rt_init_rti_board_start变量实际上通过 INIT_EXPORT(fn, level) 宏来定义的,继续单步,你会发现,只要通过这个宏定义的内容,将在编译阶段形成一个地址段,这里面的指针指向的就是一个函数,如下此处分别指向如下函数:
rti_board_start();
rt_hw_pin_init();
rt_hw_usart_init();
rti_board_end();
以上这4个函数,在编译的时候,就按照这个顺序进行地址的分配,也就是说这个四个函数的地址是连续的,因此可以用for循环来进行遍历.
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)