rt-thread内核启动分析

本文深入解析RT-Thread实时操作系统内核的启动流程,从stm32硬件环境搭建到内核单步调试,详细解读关键函数作用及rt-thread官网内核分析。涵盖HAL库初始化、时钟配置、内存堆初始化等核心步骤。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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循环来进行遍历.
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

myenjoy_1

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值