Toggle navigation
首页
问答
文章
积分商城
专家
专区
更多专区...
文档中心
返回主站
搜索
提问
会员
中心
登录
注册
自动初始化
RT-Thread 自动初始化机制详解
2.00
发布于 2021-10-13 08:55:57 浏览:5871
订阅该版
[tocm] # RT-Thread 自动初始化机制详解 ## 案例引入 ### 一般初始化 嵌入式开发在初始化某个外设的时候大部分都是以下这种形式 ```c int main(int argc, char *argv[]) { clk_init(); led_init(); beep_init(); key_init(); ..... while(1) { ... } } ``` 上面的初始化顺序比较清晰,初始化了哪些外设以及先后顺序一眼就可以看出来,但是这样 `main` 函数显得特别繁琐,尤其是需要初始化的外设比较多的时候。 ### 自动初始化 在电脑编写 C 语言程序的时候,打印一个 `hello world` ```c #include
int main(int argc, char *argv[]) { printf("hello world\r\n"); return 1; } ``` 这里我们直接就可以使用 `printf` 进行打印,而没有进行一些其它的初始化,参考这个思路引出了 `RT-Thread` 的自动初始化机制。 `RT-Thread` 自动初始化引入 ```c int led_init() { ... } INIT_APP_EXPORT(led_init); int main(int argc, char *argv[]) { led_on(); rt_kprintf("hello rt thread\r\n"); return 1; } ``` 自动初始化的核心思想就是在执行到 `main` 函数之前,各个外设的初始化全部都初始化完成了,直接在 `main` 函数中使用即可。例如上面的程序中直接使用 `rt_kprintf` 进行输出,以及 `LED` 的点亮。 ## 自动初始化 API 从 `RT-Thread` 源码中截取的自动初始化的 `API` 如下 ```c /* board init routines will be called in board_init() function */ #define INIT_BOARD_EXPORT(fn) INIT_EXPORT(fn, "1") /* pre/device/component/env/app init routines will be called in init_thread */ /* components pre-initialization (pure software initilization) */ #define INIT_PREV_EXPORT(fn) INIT_EXPORT(fn, "2") /* device initialization */ #define INIT_DEVICE_EXPORT(fn) INIT_EXPORT(fn, "3") /* components initialization (dfs, lwip, ...) */ #define INIT_COMPONENT_EXPORT(fn) INIT_EXPORT(fn, "4") /* environment initialization (mount disk, ...) */ #define INIT_ENV_EXPORT(fn) INIT_EXPORT(fn, "5") /* appliation initialization (rtgui application etc ...) */ #define INIT_APP_EXPORT(fn) INIT_EXPORT(fn, "6") ``` 各个 `API` 的作用如下表所示 |初始化顺序|API|描述| |-|-|-| |1|INIT_BOARD_EXPORT(fn)|非常早期的初始化,此时调度器还未启动| |2|INIT_PREV_EXPORT(fn)|主要是用于纯软件的初始化、没有太多依赖的函数| |3|INIT_DEVICE_EXPORT(fn)|外设驱动初始化相关,比如网卡设备| |4|INIT_COMPONENT_EXPORT(fn)|组件初始化,比如文件系统或者 LWIP| |5|INIT_ENV_EXPORT(fn)|系统环境初始化,比如挂载文件系统| |6|NIT_APP_EXPORT(fn)|应用初始化,比如 GUI 应用| 以上表格来自 `RT-Thread` 官网 ## 原理分析 ### INIT_EXPORT 函数 从各个初始化函数可以看出最终调用的都是 `INIT_EXPORT` 这一个函数,只是传递的参数不同。接下来看一下这个函数的定义 ```c #define INIT_EXPORT(fn, level) \ RT_USED const init_fn_t __rt_init_##fn SECTION(".rti_fn." level) = fn ``` `INIT_EXPORT()` 函数有两个参数,第一个参数表示需要初始化哪一个函数,传递的是函数指针也就是函数名,第二个参数表示将函数指针放到哪一个段。接下来就来分析一下这个宏 分析之前需要有几个预备知识 - RT_USED ```c #define RT_USED __attribute__((used)) ``` 标记为 `attribute__((used))` 的函数被标记在目标文件中,以避免链接器删除未使用的节。 - init_fn_t 类型 ```c typedef int (*init_fn_t)(void); ``` 这里定义了一个返回值为 `int`,函数参数为 `void` 的一个函数指针类型并重命名为 `init_fn_t` - `##` `##` 这个属于 C 语言的知识,它的作用是用来把两个语言符号组合成单个语言符号 - SECTION ```c #define SECTION(x) __attribute__((section(x))) ``` `__attribute__((section(name)))` 将作用的函数或数据放入指定名为 `name` 的输入段中 有了上面的预备知识后再来分析以下 `INIT_EXPORT` 这个宏,将宏展开后如下所示 ``` RT_USED const init_fn_t __rt_init_fn SECTION(".rti_fn." level) = fn ``` 这个宏的作用就是将函数 `fn` 的指针赋值给 `__rt_init_fn` 这个变量,这个变量的类型是 `RT_USED const init_fn_t`,这个变量存放在指定的段 `.rti_fn.level` 中。所以函数使用自动初始化宏导出后,这些数据段中就会存储指向各个初始化函数的指针。当我们对这些指针进行解引用的时候也就相当于执行了相应的函数。 ### 段的划分 在 `component.c` 中对各个段进行了划分,源码如下 ```c static int rti_start(void) { return 0; } INIT_EXPORT(rti_start, "0"); static int rti_board_start(void) { return 0; } INIT_EXPORT(rti_board_start, "0.end"); static int rti_board_end(void) { return 0; } INIT_EXPORT(rti_board_end, "1.end"); static int rti_end(void) { return 0; } INIT_EXPORT(rti_end, "6.end"); ``` 上面使用 `INIT_EXPORT` 宏导出的段分布如下表所示 |序号|段名|函数指针/函数名| |-|-|-| |1|.rti_fn.0|__rt_init_rti_start| |2|.rti_fn.0.end|__rti_init_rti_board_start| |3|.rti_fn.1.end|__rti_init_rti_board_end| |4|.rti_fn.6.end|__rti_init_rti_end| 加上自动初始化导出的 6 个段之后,各个段的分布如下表所示 |序号|段名|函数指针/函数名| |-|-|-| |1|.rti_fn.0|__rt_init_rti_start| |2|.rti_fn.0.end|__rti_init_rti_board_start| |3|.rti_fn.1|INIT_BOARD_EXPORT(fn)| |4|.rti_fn.1.end|__rti_init_rti_board_end| |5|.rti_fn.2|INIT_PREV_EXPORT(fn)| |6|.rti_fn.3|INIT_DEVICE_EXPORT(fn)| |7|.rti_fn.4|INIT_COMPONENT_EXPORT(fn)| |8|.rti_fn.5|INIT_ENV_EXPORT(fn)| |9|.rti_fn.6|INIT_APP_EXPORT(fn)| |10|.rti_fn.6.end|__rti_init_rti_end| ### rt_components_board_init 函数 有了上面段的划分,接下来看一下 `rt_components_board_init` 函数的实现 ```c 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 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)(); } #endif } ``` 不考虑 `RT_DEBUG_INIT` 那么此函数最终的执行的就是 ```c 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)(); } ``` 这段代码定义了一个 `fn_ptr` 指针,当指针的范围在 `__rt_init_rti_board_start` 和 `__rt_init_rti_board_end` 范围之内时就对该指针进行解引用,这里的指针就是自动初始化时放的函数指针,所以这里就相当与函数的执行。也就是执行了 `INIT_BOARD_EXPORT(fn)` 导出的函数 ### rt_components_init 函数 该函数的源码如下 ```c void rt_components_init(void) { #if RT_DEBUG_INIT int result; const struct rt_init_desc *desc; rt_kprintf("do components initialization.\n"); for (desc = &__rt_init_desc_rti_board_end; desc < &__rt_init_desc_rti_end; desc ++) { rt_kprintf("initialize %s", desc->fn_name); result = desc->fn(); rt_kprintf(":%d done\n", result); } #else 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)(); } #endif } ``` 同样不考虑 `RT_DEBUG_INIT` ,该函数最终执行的是 ```c 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)(); } ``` 这段代码同样也定义了一个 `fn_ptr` 指针,当指针的范围在 `__rt_init_rti_board_end` 和 `__rt_init_rti_end` 范围之内时就对该指针进行解引用,这里的指针就是自动初始化时放的函数指针,所以这里就相当与函数的执行。也就是执行了 `INIT_PREV_EXPORT(fn)` 到 `INIT_APP_EXPORT(fn)` 这两个段之间导出的函数 ## 自动初始化函数的执行 `RT-Thread` 的启动流程如下图所示 ![init.png](https://oss-club.rt-thread.org/uploads/20211013/c7ba0d6d2db2b8ce3ee4a8eb518e5f57.png.webp) 上图来自 `RT-Thread` 官网 从系统的启动流程可以看出,`rt_components_board_init()` 函数以及 `rt_componenets_init()` 函数的执行位置。 ## 使用示例 在 `main.c` 函数中添加如下测试代码 ```c int led_init(void) { return 1; } INIT_APP_EXPORT(led_init); ``` 编译生成的 `.map` 文件如下图所示 ![map2.png](https://oss-club.rt-thread.org/uploads/20211013/449204c96be3f13e3ddd1732367029f2.png) 函数指针 `__rt_init_led_init` 位于 `.rti_fn.6` 的段中,函数 `rt_components_init()` 函数执行的时候会对这个指针进行解引用,也就是执行 `led_init` 这个函数 参考:https://blog.csdn.net/yang1111111112/article/details/93982354
24
条评论
默认排序
按发布时间排序
登录
注册新账号
关于作者
tyustli
这家伙很懒,什么也没写!
文章
14
回答
570
被采纳
12
关注TA
发私信
相关文章
1
生成一个功能组件,带有自动初始化函数,源码可以自动执行,但是生成库后不起作用
2
IAR INIT_EXPORT 开始的地址比结束的还大
3
自动初始编译出现警告
4
Studio初始化adc提示错误,没定义HAL_ADC_Init
5
硬件初始化函数 HAL_XXX_MspInit在哪里被调用了?
6
基于w5500的libmodbus复位后modbusTCP的监听一直失败
7
自动初始化的信息输出
8
[新功能] 组件初始化
9
INIT_EXPORT 宏理解
10
关于自动初始化的一些疑问
推荐文章
1
RT-Thread应用项目汇总
2
玩转RT-Thread系列教程
3
国产MCU移植系列教程汇总,欢迎查看!
4
机器人操作系统 (ROS2) 和 RT-Thread 通信
5
五分钟玩转RT-Thread新社区
6
【技术三千问】之《玩转ART-Pi》,看这篇就够了!干货汇总
7
关于STM32H7开发板上使用SDIO接口驱动SD卡挂载文件系统的问题总结
8
STM32的“GPU”——DMA2D实例详解
9
RT-Thread隐藏的宝藏之completion
10
【ART-PI】RT-Thread 开启RTC 与 Alarm组件
热门标签
RT-Thread Studio
串口
Env
LWIP
SPI
AT
Bootloader
Hardfault
CAN总线
ART-Pi
FinSH
USB
DMA
文件系统
RT-Thread
SCons
RT-Thread Nano
线程
MQTT
STM32
RTC
rt-smart
FAL
ESP8266
I2C_IIC
WIZnet_W5500
ota在线升级
UART
cubemx
PWM
flash
packages_软件包
freemodbus
BSP
潘多拉开发板_Pandora
定时器
ADC
GD32
flashDB
socket
中断
Debug
编译报错
msh
SFUD
keil_MDK
rt_mq_消息队列_msg_queue
ulog
C++_cpp
at_device
本月问答贡献
出出啊
1516
个答案
342
次被采纳
小小李sunny
1440
个答案
289
次被采纳
张世争
799
个答案
171
次被采纳
crystal266
547
个答案
161
次被采纳
whj467467222
1222
个答案
148
次被采纳
本月文章贡献
出出啊
1
篇文章
1
次点赞
小小李sunny
1
篇文章
1
次点赞
张世争
1
篇文章
4
次点赞
crystal266
2
篇文章
2
次点赞
whj467467222
2
篇文章
1
次点赞
回到
顶部
发布
问题
投诉
建议
回到
底部