rt-thread启动流程(最详细教程)

资料下载

RT-Thread Simulator 例程

操作流程

  1. 将上面的仿真例程下载并解压,通过MDK打开,编译,调试,并打开串口点击运行,就可以看到如下输出了:
    在这里插入图片描述
  2. 添加自己的 thread:在main()函数中添加即可,如下图:
    在这里插入图片描述

启动流程

  1. 首先是通过startup_stm32f103xe.s启动文件调用SystemInit(),系统初始化完成后,调用C库函数__main(),然后由__main()调用用户的main()函数。但是,由于ARMCC编译器的特性,可以在调用main()函数前插入一个$Sub$$main()函数(其他编译器也有类似特性)。rt-thread就是利用了这个特性,使所有的硬件、系统初始化都在$Sub$$main()函数完成,而不需要用户在main()中调用。
    在这里插入图片描述
  2. 接下来看看 rtthread_startup() 函数:
    在这里插入图片描述
  3. 我们进入到 rt_application_init() 函数去看一下:
    在这里插入图片描述
    在这里插入图片描述
  4. 以上就是系统的启动即初始化流程,以及实现自己的 thread 的操作流程。但是我们明明还有其他的 thread 在运行啊,比如上面图一中的tshelltidle线程,这两个线程又是从哪里启动的呢?
    在这里插入图片描述
  5. 首先,我们通过全局搜索tshell,看下这个这个thread是在哪里创建的:
#define FINSH_THREAD_NAME "tshell"

在这里插入图片描述
原来是在 shell.c 中的finsh_system_init()函数中创建了tshell线程,那理论上是不是就应该全局搜索在哪里调用了finsh_system_init()函数呢?确实是,但搜索后发现,这个函数没有被调用的记录,这又是为啥呢?

  1. 重点就在finsh_system_init()函数后面的一句:
INIT_APP_EXPORT(finsh_system_init);

查看定义如下(C语言中##起连接作用):
在这里插入图片描述
那么INIT_APP_EXPORT(finsh_system_init);这句翻译一下,就是:

INIT_EXPORT(finsh_system_init, "6");

再翻译一下,就是:

RT_USED const init_fn_t __rt_init_finsh_system_init SECTION(".rti_fn.6") = finsh_system_init;

这句就已经很清晰了吧,定义了一个变量__rt_init_finsh_system_init,变量类型为const init_fn_t(其中init_fn_t为函数指针),这个变量的值就是finsh_system_init()这个函数的起始地址,重点是:将这个函数起始地址放在了.rti_fn.6的段中!
分析到这里,就很容易再分析出另外几个宏定义的作用了:

INIT_BOARD_EXPORT(fn) : 将板子初始化函数起始地址放到.rti_fn.1的段中
INIT_PREV_EXPORT(fn) :将预初始化函数起始地址放到.rti_fn.2的段中
INIT_DEVICE_EXPORT(fn) :将设备初始化函数起始地址放到.rti_fn.3的段中
INIT_COMPONENT_EXPORT(fn) :将组件初始化函数起始地址放到.rti_fn.4的段中
INIT_ENV_EXPORT(fn) :将环境初始化函数起始地址放到.rti_fn.5的段中

这里再提醒一下,当在同一个段中放入多个变量时,这些变量会按照时间顺序依次往后排。

  1. 现在我们知道,tshell线程创建函数没有被直接调用,而是放在了一个特色的段中,那么这个段中的函数什么时候被执行的呢?这个我也没找到什么好的定位方法,只能从初始化函数依次看下来,然后发现,有这么一条函数调用链:

$Sub$$main() --> rtthread_startup() --> rt_application_init() --> main_thread_entry() --> rt_components_init()

在这里插入图片描述
通过以上代码,我们看到通过INIT_EXPORT()宏又定义了几个函数并放入了相应的段:

rti_start() --> .rti_fn.0
rti_board_start() --> .rti_fn.0.end
rti_board_end() --> .rti_fn.1.end
rti_end() --> .rti_fn.6.end

根据上面所定义段的命名,猜测一下这些段的地址关系应该是:

.rti_fn.0 --> .rti_fn.0.end --> .rti_fn.1 --> .rti_fn.1.end --> .rti_fn.2 --> .rti_fn.3 --> .rti_fn.4 --> .rti_fn.5 --> .rti_fn.6 --> .rti_fn.6.end

打开map文件看一下各个段的地址验证一下(由于我们例程中并没有使用到2-5这几个段,所以map中没有记录):
在这里插入图片描述

这里有个还有个疑问:各个段的地址是怎么确定的呢?由编译器指定还是由开发人员指定呢?
如果由编译器指定,那编译器又怎么知道哪个段需要放前面呢?如果由开发人员指定,又是在哪里指定的呢?

自问自答一下:我暂时没有找到什么资料,但是通过在代码中添加其他的段名进行各种尝试,发现段地址的由编译器决定的,而段的存放顺序是由段的名字按字母排序决定的!如下图:
在这里插入图片描述

接下来终于可以看看这些初始化函数是怎么调用的了:

/**
 * @brief  RT-Thread Components Initialization.
 */
void rt_components_init(void)
{
    volatile const init_fn_t *fn_ptr;

    for (fn_ptr = &__rt_init_rti_board_end; fn_ptr < &__rt_init_rti_end; fn_ptr ++)
    {
        (*fn_ptr)();
    }
}

通过以上函数中的for循环,直接将__rt_init_rti_board_end(即.rti_fn.1.end)__rt_init_rti_end(即.rti_fn.6.end)之间的所有初始化函数执行了一遍。当然,也就包括了在.rti_fn.6段中的finsh_system_init()函数了。

  1. 同理,通过以下的函数调用流程,可以将.rti_fn.1中的初始化函数也执行一遍:

$Sub$$main() --> rtthread_startup() --> rt_hw_board_init() --> rt_components_board_init()

/**
 * @brief  Onboard components initialization. In this function, the board-level
 *         initialization function will be called to complete the initialization
 *         of the on-board peripherals.
 */
void rt_components_board_init(void)
{
    volatile 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)();
    }
}

通过以上函数中的for循环,直接将__rt_init_rti_board_start(即.rti_fn.0.end)__rt_init_rti_board_end(即.rti_fn.1.end)之间的所有初始化函数执行了一遍。而且,在rtthread_startup()函数中,rt_hw_board_init()函数是比rt_application_init()函数更早调用,这就保证了.rti_fn.1段中的函数要早于其他段中函数的执行。

  1. rt-thread的启动流程这就讲完啦,大家现在应该知道为什么要把这些初始化函数放在这么多个段中了吧?

因为在同一个段的函数执行顺序是由编译器决定的,如果我们需要指定顺序,只能通过在不同段中实现。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值