Toggle navigation
首页
问答
文章
积分商城
专家
专区
更多专区...
文档中心
返回主站
搜索
提问
会员
中心
登录
注册
2024-RSOC
RT-Thread内核学习
发布于 2024-07-31 04:19:10 浏览:342
订阅该版
[tocm] # RT-Thread 内核学习 2024/7/31晚,未完待续,前几天有事情加上板子到的晚,遇见看不懂的地方还得一直查资料,所以进度慢的出奇。参考文章:[自动初始化原理](https://club.rt-thread.org/ask/article/be086a860a44c5c0.html) [INIT_EXPORT的使用](https://blog.csdn.net/WX_django/article/details/139576192?spm=1001.2014.3001.5501) ## RT-Thread启动流程 ![rtt_startup.png](https://oss-club.rt-thread.org/uploads/20240731/ade3788cc21026947215c7c57034a1f4.png.webp) 以星火一号的stm32f407ZGT6为开发板,RT-Thread首先从startup_stm32f407xx.s启动 ![image-20240731002052778.png](https://oss-club.rt-thread.org/uploads/20240731/66110ea1edd9ab08e7ab4516f2946121.png.webp) 系统从entry函数进入rtthread_startup()函数中开始初始化,**rtthread_startup()** 函数是 RT-Thread 规定的统一启动入口 ```c int rtthread_startup(void) { #ifdef RT_USING_SMP rt_hw_spin_lock_init(&_cpus_lock); #endif /* 中断禁止*/ rt_hw_local_irq_disable(); /* 板级初始化 注意:请在板初始化内初始化堆。 */ rt_hw_board_init(); /* 展示 RT-Thread 版本 */ rt_show_version(); /* 定时器初始化 */ rt_system_timer_init(); /* 调度器初始化 */ rt_system_scheduler_init(); #ifdef RT_USING_SIGNALS /* 信号初始化 */ rt_system_signal_init(); #endif /* RT_USING_SIGNALS */ /* 创建用户main线程 */ rt_application_init(); /* 定时器线程初始化 */ rt_system_timer_thread_init(); /* 空闲线程(idle)初始化 */ rt_thread_idle_init(); #ifdef RT_USING_SMP rt_hw_spin_lock(&_cpus_lock); #endif /* RT_USING_SMP */ /* 启动调度器 */ rt_system_scheduler_start(); /* 不会执行至此 */ return 0;rt_hw_local_irq_disable(); } #endif /* RT_USING_USER_MAIN */ ``` ### 1.rt_hw_local_irq_disable(); ```assembly /* * rt_base_t rt_hw_interrupt_disable(); */ .global rt_hw_interrupt_disable .type rt_hw_interrupt_disable, %function rt_hw_interrupt_disable: /*读取PRIMASK寄存器的值到r0寄存器*/ MRS r0, PRIMASK /*关闭全局中断*/ CPSID I /*从函数返回。BX LR 指令将程序计数器设置为链接寄存器(LR)的值,从而返回到调用该函数的地址。*/ BX LR ``` rt_base_t为N位CPU相关长数据类型 .global rt_hw_interrupt_disable:声明 rt_hw_interrupt_disable 为全局符号,使得该函数可以在其他文件中被引用。 .type rt_hw_interrupt_disable, %function:定义 rt_hw_interrupt_disable 的类型为函数。 **MRS**指令: 把状态寄存器的值传到通用寄存器的传送指令。 **PRIMASK寄存器**的作用如下: ![ecef069e381a7baaf2c9af49fd40f4e6.png](https://oss-club.rt-thread.org/uploads/20240731/a0c59bbe6b201e72680259260ddde271.png) MRS r0, PRIMASK:将当前的中断状态(PRIMASK 寄存器的值)保存到寄存器 r0 中。 **CPSID指令**:通过设置 PRIMASK 寄存器来**禁用所有中断**。 ![755d182a3d7461cba6603d2a172589dc.png](https://oss-club.rt-thread.org/uploads/20240731/1de8c17fba48b632dd7f0abd6c1379f7.png) CPSID I 指令将中断屏蔽位(I 位)设置为 1,从而禁用中断。 **BX指令**:从函数返回,BX LR 指令将程序计数器设置为链接寄存器(LR)的值,从而返回到调用该函数的地址。 使用 MRS 指令将 PRIMASK 寄存器的值保存到 r0 寄存器里,然后使用 “CPSID I” 指令关闭全局中断,最后使用 BX 指令返回。r0 存储的数据就是这个中断失能函数的返回值。中断可以发生在 “MRSr0, PRIMASK” 指令和 “CPSID I” 之间,这并不会导致全局中断状态的错乱。 ### 2.rt_hw_board_init() ```c rt_weak void rt_hw_board_init(void) { #ifdef BSP_SCB_ENABLE_I_CACHE /* Enable I-Cache---------------------------------------------------------*/ SCB_EnableICache(); #endif #ifdef BSP_SCB_ENABLE_D_CACHE /* Enable D-Cache---------------------------------------------------------*/ SCB_EnableDCache(); #endif /*调用 HAL_Init() 函数初始化硬件抽象层(HAL)库 */ HAL_Init(); /* 配置系统时钟 */ SystemClock_Config(); #if defined(RT_USING_HEAP) /* 初始化堆内存,使用 HEAP_BEGIN 和 HEAP_END 作为堆的起始和结束地址。 */ rt_system_heap_init((void *)HEAP_BEGIN, (void *)HEAP_END); #endif #ifdef RT_USING_PIN /* 初始化引脚 */ rt_hw_pin_init(); #endif #ifdef RT_USING_SERIAL /* 初始化串口 */ rt_hw_usart_init(); #endif #if defined(RT_USING_CONSOLE) && defined(RT_USING_DEVICE) /*设置 shell 控制台输出设备*/ rt_console_set_device(RT_CONSOLE_DEVICE_NAME); #endif #if defined(RT_USING_CONSOLE) && defined(RT_USING_NANO) extern void rt_hw_console_init(void); rt_hw_console_init(); #endif #ifdef RT_USING_COMPONENTS_INIT /* 初始化板级组件 */ rt_components_board_init(); #endif } ``` 根据RT-Thread官方文档中写道,该系统拥有自动初始化机制,自动初始化机制是指初始化函数不需要被显式调用,只需要在函数定义处通过宏定义的方式进行申明,就会在系统启动过程中被执行。 在RT-Thread启动流程中,rt_hw_board_init()中的rt_hw_usart_init(void)函数最后会自动初始化串口 ```c int rt_hw_usart_init(void) /* 串口初始化函数,来自官方文档 */ { ... ... /* 注册串口 1 设备 */ rt_hw_serial_register(&serial1, "uart1", RT_DEVICE_FLAG_RDWR | RT_DEVICE_FLAG_INT_RX, uart); return 0; } INIT_BOARD_EXPORT(rt_hw_usart_init); /* 使用组件自动初始化机制 */ ``` 然而在新的源码中,我没找到INIT_BOARD_EXPORT函数进行自动初始化,可能是经过修改了。 用来实现自动初始化功能的宏接口定义详细描述如下表所示: | **初始化顺序** | **宏接口** | **描述** | | -------------- | ------------------------- | -------------------------------------------- | | 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 | INIT_APP_EXPORT(fn) | 应用初始化,比如 GUI 应用 | 初始化函数主动通过这些宏接口进行申明,如 INIT_BOARD_EXPORT(rt_hw_usart_init),链接器会自动收集所有被申明的初始化函数,放到 RTI 符号段中,该符号段位于内存分布的 RO 段中,该 RTI 符号段中的所有函数在系统初始化时会被自动调用。 我们拿INIT_BOARD_EXPORT(fn)进行分析,在RT-Thread的api中,可以看到INIT_BOARD_EXPORT的定义 | #define | [INIT_BOARD_EXPORT](https://www.rt-thread.org/document/api/group___system_init.html#ga78777415f5aabee4917e1ad14d26bb94)(fn) INIT_EXPORT(fn, "1") | | ------- | ------------------------------------------------------------ | | | 板级驱动初始化,fn为函数指针 | #### INIT_EXPORT宏的作用 ```c #ifdef RT_DEBUGING_AUTO_INIT struct rt_init_desc { const char* fn_name; const init_fn_t fn; }; #define INIT_EXPORT(fn, level) \ const char __rti_##fn##_name[] = #fn; \ rt_used const struct rt_init_desc __rt_init_desc_##fn rt_section(".rti_fn." level) = \ { __rti_##fn##_name, fn}; #else #define INIT_EXPORT(fn, level) \ rt_used const init_fn_t __rt_init_##fn rt_section(".rti_fn." level) = fn #endif /* RT_DEBUGING_AUTO_INIT */ ``` 上面这个代码块是这个宏的原型,先说一下这个宏展开之后到底是什么。 这个宏接受两个两个参数,一个是函数指针fn,另一个是字符串level(用于确定初始化函数的等级)。宏展开后,执行`const char __rti_##fn##_name[] = #fn;` ,## 是宏拼接运算符,使用宏拼接生成一个唯一的名称。用来将 fn 参数与前缀` __rt_init_desc_ `拼接在一起,形成一个新的变量名。也就是说,如果传入的函数指针名字是OLED_Init,那么`__rti_##fn##_name`会将OLED_Init进行拼接,形成一个新的数组名称`__rti_OLED_Init_name`,`#fn`的作用是将fn转化成字符串,并将这个字符串赋值给`__rti_OLED_Init_name`这个字符数组,如果你去访问`__rti_OLED_Init_name`这个数组的话,它的内容是OLED_Init这个字符串。 随后执行`RT_USED const struct rt_init_desc __rt_init_desc_##fn SECTION(".rti_fn."level) = { __rti_##fn##_name, fn};`这句代码,这句代码比较长。 我们先说一下RT_USED宏和SECTION宏的定义与作用: + 最开头的RT_USED宏原型也是在rtdef.h文件中 ,crtl+F一下可以找到这个宏的原型: `#define RT_USED __attribute__((used))` 这个宏定义的作用是告诉编译器,被标记为 RT_USED 的变量或函数是被使用的,以防止编译器在链接过程中将其优化掉。在编程中,有时会定义一些变量或函数,但在某些情况下可能并没有直接被使用,例如在某些条件下的编译选项不同,或者在某些库中可能有一些函数只在特定情况下被调用。如果没有使用 RT_USED 这样的标记,编译器可能会将这些看似未使用的变量或函数优化掉,从而导致链接错误或者不正确的程序行为。 + 第二个宏是SECTION,同样也是在rtdef.h文件中被定义,转到定义可以看到,宏定义的原型为: `#define SECTION(x) __attribute__((section(x)))` 这个宏定义的作用就是告诉编译器将特定变量或函数放置在指定的段(section)中。 使用`__attribute__((section(x)))`时,一定要将其放在等号的左边和变量的中间,对于`RT_USED const struct rt_init_desc __rt_init_desc_##fnSECTION(".rti_fn."level) = { __rti_##fn##_name, fn};` 来说,也就是要放在`__rt_init_desc_##fn`这个结构体变量和等号之间。 说完了两个宏,我们先尝试着去掉这两个宏来理解这句代码,也就是这样: `const struct rt_init_desc __rt_init_desc_##fn = { __rti_##fn##_name, fn};` + `const struct rt_init_desc`:定义一个常量结构体变量,其类型是`rt_init_desc` ```c struct rt_init_desc { const char* fn_name; const init_fn_t fn; }; ``` ```c typedef int (*init_fn_t)(void);//init_fn_t的定义,其实是一个函数指针 ``` + `__rt_init_desc_##fn`:使用宏拼接生成一个唯一的结构体变量名称。 + `rt_section(".rti_fn." level)`:将这个结构体变量放到特定的段中,level 是初始化级别。 + `{ __rti_##fn##_name, fn }`:初始化结构体,包含函数名称和函数指针。 这样来看这句代码是将结构体变量进行了赋值,并将这个结构体标记为const。这个结构体包含了要被初始化函数的一些信息,函数名的字符串,以及初始化函数的函数指针。其实我们在这个结构体变量的命名中也可以看出这个结构体的作用,desc是description的缩写,所以这个结构体是对初始化函数的一些描述。 结合上面所说的RT_USED和SECTION这两个宏的作用,这个结构体被放在了".rti_fn.“level”"的段中,加上RT_USED这个宏进行修饰,告诉编译器这个段中的数据是要被使用的,防止编译器将这个段中的数据被优化掉。 **综上所述,INIT_EXPORT宏的作用就是利用函数指针更改函数的名字,并将其存放在指定的位置中。** #### INIT_EXPORT具体应用 在RT-Thread的启动流程中,调用了两个函数 rt_components_board_init() 与 rt_components_init() 就完成了上述6个部分的初始化工作。从初始化启动流程图中我们可以看出: rt_components_board_init() 完成了第 1 部分工作, rt_components_init() 完成了第2-6部分的工作。那么接下来我们先看这两个函数源代码。 ```c void rt_components_board_init(void) { #ifdef RT_DEBUGING_AUTO_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_DEBUGING_AUTO_INIT */ } ``` ```C void rt_components_init(void) { #ifdef RT_DEBUGING_AUTO_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 / ``` 可以看到两个函数在非调试模式下都是通过for循环会遍历位于rt_init_rti_board_start 到 rt_init_rti_board_end 以及rt_init_rti_board_end 到 rt_init_rti_end之间保存的函数指针,然后依次执行这些函数。那么接下来我们看看上述函数指针。我们先编译下,找到.map文件,我们在文件中搜索上述的函数指针。 ![111.png](https://oss-club.rt-thread.org/uploads/20240731/c2eba98058035e6ff4cc0f1a819e6d9b.png.webp) ![y3Un9s.png](https://oss-club.rt-thread.org/uploads/20240731/ad986e426d3876b012fef3691a4ccae7.png.webp) 我们再来看下面一段compoents.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宏作用的讲解,我们知道了自动初始化的机制,`INIT_EXPORT(rti_start, “0”);`,这段代码将函数rti_start改名为`__rt_init_rti_start`,存入`.rti_fn.0`这个地方。 当`rt_components_board_init() `与` rt_components_init()`运行时,遍历位于`rt_init_rti_board_start` 到 `rt_init_rti_board_end` 以及`rt_init_rti_board_end `到 `rt_init_rti_end`之间保存的函数指针,然后依次执行这些函数,实现自动初始化 #### 自动初始化实例 ```c /* * Copyright (c) 2023, RT-Thread Development Team * * SPDX-License-Identifier: Apache-2.0 * * Change Logs: * Date Author Notes * 2023-07-06 Supperthomas first version * 2023-12-03 Meco Man support nano version */ #include
#include
#include
#ifndef RT_USING_NANO #include
#endif /* RT_USING_NANO */ #define GPIO_LED_B GET_PIN(F, 11) #define GPIO_LED_R GET_PIN(F, 12) static int LED_init(void); int main(void) { while (1) { rt_pin_write(GPIO_LED_R, PIN_HIGH); rt_thread_mdelay(500); rt_pin_write(GPIO_LED_R, PIN_LOW); rt_thread_mdelay(500); } } static int LED_init(void) { rt_pin_mode(GPIO_LED_R, PIN_MODE_OUTPUT); return 0; } INIT_BOARD_EXPORT(LED_init); ``` 将LED_init放在INIT_BOARD_EXPORT函数中,由系统自动在启动阶段初始化,得到的现象与实验例程一样,自动初始化成功 ### 3.rt_show_version(); ```c void rt_show_version(void) { rt_kprintf("\n \\ | /\n"); #if defined(RT_USING_SMART) rt_kprintf("- RT - Thread Smart Operating System\n"); #elif defined(RT_USING_NANO) rt_kprintf("- RT - Thread Nano Operating System\n"); #else rt_kprintf("- RT - Thread Operating System\n"); #endif rt_kprintf(" / | \\ %d.%d.%d build %s %s\n", (rt_int32_t)RT_VERSION_MAJOR, (rt_int32_t)RT_VERSION_MINOR, (rt_int32_t)RT_VERSION_PATCH, __DATE__, __TIME__); rt_kprintf(" 2006 - 2024 Copyright by RT-Thread team\n"); } RTM_EXPORT(rt_show_version); ``` 这段代码的主要功能是显示 RT-Thread 操作系统的版本信息,包括操作系统名称、版本号、构建日期时间和版权信息。通过条件编译指令,可以根据不同的配置打印不同的操作系统名称。
0
条评论
默认排序
按发布时间排序
登录
注册新账号
关于作者
dianfu233
这家伙很懒,什么也没写!
文章
3
回答
0
被采纳
0
关注TA
发私信
相关文章
推荐文章
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总线
FinSH
ART-Pi
USB
DMA
文件系统
RT-Thread
SCons
RT-Thread Nano
线程
MQTT
STM32
RTC
FAL
rt-smart
ESP8266
I2C_IIC
WIZnet_W5500
ota在线升级
UART
PWM
cubemx
freemodbus
flash
packages_软件包
BSP
潘多拉开发板_Pandora
定时器
ADC
GD32
flashDB
socket
中断
Debug
编译报错
msh
SFUD
keil_MDK
rt_mq_消息队列_msg_queue
at_device
ulog
C++_cpp
本月问答贡献
踩姑娘的小蘑菇
7
个答案
3
次被采纳
张世争
8
个答案
2
次被采纳
rv666
5
个答案
2
次被采纳
用户名由3_15位
11
个答案
1
次被采纳
KunYi
6
个答案
1
次被采纳
本月文章贡献
程序员阿伟
6
篇文章
2
次点赞
hhart
3
篇文章
4
次点赞
大龄码农
1
篇文章
2
次点赞
ThinkCode
1
篇文章
1
次点赞
Betrayer
1
篇文章
1
次点赞
回到
顶部
发布
问题
投诉
建议
回到
底部