本文共 2987 字,大约阅读时间需要 9 分钟。
最近做了一个项目,接触到了 rt-thread 这款国产实时操作系统,进行了简单的配置之后就能够在板子上面调试,确实很方便!
在配置过程中,我遇到了很多有趣的挑战,尤其是与 rt-thread 的初始化机制相关的内容。相比于常见的嵌入式实时操作系统,rt-thread 的初始化方式有着显著的不同。
rt-thread 系统驱动与组件的初始化与常见的嵌入式实时操作系统有较大区别。rt-thread通过将初始化函数地址以不同的初始化级别排序后存储到初始化段中,在段的前后设置锚点,通过遍历获取锚点间存储的函数指针,依次执行来完成初始化工作。
初始化工作可以分为组件板级初始化与组件系统级初始化两种类别,这两种类别的初始化函数指针依次存放。以 rt-thread 中的 component.c 源代码为例,初始化函数的执行顺序如下:
a. rti_start
-> 0
BOARD_EXPORT
-> 1c. rti_board_end
-> 1.endd. DEVICE_EXPORT
-> 2e. COMPONENT_EXPORT
-> 3f. FS_EXPORT
-> 4g. ENV_EXPORT
-> 5h. APP_EXPORT
-> 6i. rti_end
-> 6.end 在我的项目的 map 文件中与初始化函数段相关的数据如下:
0x60033030 __rt_init_start = . *(SORT(.rti_fn*)) .rti_fn.0 0x60033034 __rt_init_board_start .rti_fn.1 0x60033038 __rt_init_imxrt_hw_uart_init .rti_fn.1 0x6003303c __rt_init_rt_hw_pin_init .rti_fn.1 0x60033040 __rt_init_rt_hw_spi_bus_init .rti_fn.1.end 0x60033044 __rt_init_rti_board_end .rti_fn.2 0x60033048 __rt_init_dfs_init .rti_fn.2 0x6003304c __rt_init_lwip_system_init .rti_fn.2 0x60033050 __rt_init_rt_mmcsd_core_init .rti_fn.3 0x60033054 __rt_init_rt_hw_hp_rtc_init .rti_fn.3 0x60033058 __rt_init_rt_hw_i2c_init .rti_fn.3 0x6003305c __rt_init_rt_hw_lcd_init .rti_fn.3 0x60033060 __rt_init_imxrt_mci_init .rti_fn.3 0x60033064 __rt_init_rt_hw_imxrt_eth_init .rti_fn.4 0x60033068 __rt_init_elm_init .rti_fn.4 0x6003306c __rt_init_libc_system_init .rti_fn.4 0x60033070 __rt_init_rt_i2c_core_init .rti_fn.6 0x60033074 __rt_init_finsh_system_init .rti_fn.6.end 0x60033078 __rt_test_init_rti_end 0x6003307c __rt_init_end = .
从链接脚本可以看出以下几点:
__rt_init_start
是初始化函数段的开始地址,__rt_init_end
是该段的结束地址。KEEP(*(SORT(.rti_fn*)))
旗语的作用:rti_fn
为前缀的所有段。.o
文件中的段进行排序,这一点尤为重要。在构建第一个可以调试的工程时,我发现链接脚本中缺少了 KEEP(*(SORT(.rti_fn*)))
这条语句,导致无法正常完成初始化工作,系统一运行便进入断言。如果你在使用 rt-thread 时遇到断言问题,可以通过生成的 map 文件中搜索 __rti_fn
进行核查,确保相关段存在且排列有序。如果搜索不到,或者排列混乱,很可能是链接脚本中缺少了相应的语句。
我曾经对这背后的逻辑感到困惑,直到深入研究了初始化部分的代码。
两个主要的初始化函数如下:
void rt_components_board_init(void) { for (fn_ptr = &__rt_init_rti_board_start; fn_ptr < &__rt_init_rti_board_end; fn_ptr++) { (*fn_ptr)(); } } void rt_components_init(void) { for (fn_ptr = &__rt_init_rti_board_end; fn_ptr < &__rt_init_rti_end; fn_ptr ++) { (*fn_ptr)(); } }
rt_components_board_init
是组件的板级初始化函数,依次遍历 __rt_init_rti_board_start
到 __rt_init_rti_board_end
之间的函数地址并执行。如果没有进行排序,这些函数的地址可能不在这两个锚点之间,造成无法获取到相应的函数地址,进而无法完成初始化工作。
rt_components_init
是组件的系统级初始化函数,原理与上述函数相同,这里就不赘述了。
在 rt-thread 系统中,系统并没有像常规嵌入式系统那样使用 main
函数作为入口。而是通过 entry
函数作为系统执行入口。在使用 arm-none-eabi-gcc
编译时,可以通过添加 -eentry
参数来以 entry
代替 main
,这是一个不错的技巧!
转载地址:http://ugdjz.baihongyu.com/