RT-Thread启动过程

RT-Thread启动流程

一般了解一份代码大多从启动部分开始,同样这里也采用这种方式,先寻找启动的源头。
RT-Thread支持多种平台和多种编译器,而rtthread_startup()函数是RT-Thread规定的统一启动入口。

一般执行顺序是:系统先从启动文件开始运行,然后进入RT-Thread的启动函数rtthread_startup(),最后进入用户入口函数main(),如下图所示:
在这里插入图片描述
以MDK-ARM为例,用户程序入口为main函数,位于main.c文件中。系统启动后先从汇编代码startup_stm32f103xe.s开始运行,然后跳转到C代码,进行RT-Thread系统启动,最后进入用户程序入口函数main()。

为了在进入main()之前完成RT-Thread系统功能初始化,我们使用了MDK的扩展功能。
可以给main添加 S u b Sub Sub 的前缀符号作为一个新功能函数 的前缀符号作为一个新功能函数 的前缀符号作为一个新功能函数SubKaTeX parse error: Can't use function '$' in math mode at position 8: main,这个$̲Submain,这个 S u b Sub Sub m a i n 可以先调用一些补充在 m a i n 之前的功能函数,再调用 main可以先调用一些补充在main之前的功能函数,再调用 main可以先调用一些补充在main之前的功能函数,再调用Super$$main转到main()函数执行,这样可以让用户不去管main()之前的系统初始化操作。

在components.c中定义的这段代码:

int $Sub$$main(void)
{rtthread_startup();return 0;
}
int rtthread_startup(void)
{rt_hw_interrupt_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/* 由此创建一个用户 main 线程 */rt_application_init();/* 定时器线程初始化 */rt_system_timer_thread_init();/* 空闲线程初始化 */rt_thread_idle_init();/* 启动调度器 */rt_system_scheduler_start();/* 不会执行至此 */return 0;
}

这部分启动代码,大致可以分为四个部分:

  1. 初始化与系统相关的硬件。
  2. 初始化系统内核对象,例如定时器、调度器、信号;
  3. 创建main线程,在main线程中对各类模型依次进行初始化;
  4. 初始化定时器线程、空闲线程,并启动调度器。

启动调度器之前,系统所创建的线程并不会立马运行,它们会处于就绪状态等待系统调度;待启动调度器之后,系统才转入第一个线程开始运行,根据调度规则,选择的是就绪队列中优先级最高的线程。

rt_hw_board_init()中完成系统时钟设置,为系统提供心跳、串口初始化,将系统输入输出端绑定到这个串口,后续系统运行信息就会从串口打印出来。

main()函数是RT-Thread的用户代码入口,用户可以在main()函数里添加自己的应用。

RT-Thread程序内存分布

一般MCU包含的存储空间有:片内Flash与片内RAM。
RAM相当于内存,Flash相当于硬盘。
编译器会将一个程序分类为好几个部分,分别存储在MCU不同的存储区。

Keil工程在编译完之后,会有相应的程序所占用的空间提示信息。

linking...
Program Size: Code=48008 RO-data=5660 RW-data=604 ZI-data=2124
After Build - User command \#1: fromelf --bin.\\build\\rtthread-stm32.axf--output rtthread.bin
".\\build\\rtthread-stm32.axf" - 0 Error(s), 0 Warning(s).
Build Time Elapsed: 00:00:07

上面提到的 Program Size 包含以下几个部分:

1)Code:代码段,存放程序的代码部分;

2)RO-data:只读数据段,存放程序中定义的常量;

3)RW-data:读写数据段,存放初始化为非 0 值的全局变量;

4)ZI-data:0 数据段,存放未初始化的全局变量及初始化为 0 的变量;

编译完工程会生成一个.map的文件,该文件说明了各个函数占用的尺寸和地址,在文件最后几行也说明了上面几个字段的关系:

Total RO Size (Code + RO Data) 53668 ( 52.41kB)
Total RW Size (RW Data + ZI Data) 2728 ( 2.66kB)
Total ROM Size (Code + RO Data + RW Data) 53780 ( 52.52kB)
  1. RO Size:表示程序占用Flash空间的大小。
  2. RW Size:表示运行时占用的RAM的大小。
  3. ROM Size:表示烧写程序所占用的Flash空间的大小。

程序运行之前,需要有文件实体被烧录到STM32的Flash中,一般是bin或者hex文件,该被烧录文件称为可执行映像文件。
在这里插入图片描述
RO段中保存了Code、RO-data的数据,RW段保存了RW-data的数据,由于ZI-data都是0,所以未包含在映像文件中。

STM32在上电启动之后默认从Flash启动,启动之后会将RW段中的RW-data搬运到RAM中,但不会搬运RO段,即CPU的执行代码从Flash中读取,另外根据编译器给出的ZI地址和大小分配出ZI段,并将这块RAM区域清零。

其中动态内存堆为未使用的RAM空间,应用程序申请和释放的内存块都来自该空间。

如下面的例子:

rt_uint8_t *msg_ptr;
msg_ptr = (rt_uint8_t *)rt_malloc(128);
rt_memset(msg_ptr, 0, 128);

代码中的msg_ptr指针指向128字节内存空间位于动态内存堆空间中。

而一些全局变量则是存放于 RW 段和 ZI 段中,RW 段存放的是具有初始值的全局变量(而常量形式的全局变量则放置在 RO 段中,是只读属性的),ZI 段存放的系统未初始化的全局变量。

RT-Thread自动初始化机制

自动初始化机制是指初始化函数不需要被显示调用,只需要在函数定义处通过宏定义的方式进行申明,就会在系统启动过程中被执行。

例如在串口驱动中调用一个宏定义告知系统初始化需要调用的函数,代码如下:

int rt_hw_usart_init(void) /* 串口初始化函数 */
{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(rt_hw_usart_init)表示使用自动初始化功能,按照这种方式,rt_hw_usart_init()函数就会被系统自动调用,那么它是在哪里被调用的呢?

在系统启动流程图中,有两个函数:rt_components_board_init() 与 rt_components_init(),其后的带底色方框内部的函数表示被自动初始化的函数,其中:

“board init functions” 为所有通过 INIT_BOARD_EXPORT(fn) 申明的初始化函数。

“pre-initialization functions” 为所有通过 INIT_PREV_EXPORT(fn)申明的初始化函数。

“device init functions” 为所有通过 INIT_DEVICE_EXPORT(fn) 申明的初始化函数。

“components init functions” 为所有通过 INIT_COMPONENT_EXPORT(fn)申明的初始化函数。

“enviroment init functions” 为所有通过 INIT_ENV_EXPORT(fn) 申明的初始化函数。

“application init functions” 为所有通过 INIT_APP_EXPORT(fn)申明的初始化函数。

rt_conponents_board_init()函数执行的比较早,主要初始化相关硬件环境,执行这个函数时将会遍历通过INIT_BOARD_EXPORT(fn)申明的初始化函数表,并调用各个函数。

rt_conponents_init()函数会在操作系统运行起来之后创建的main线程里被调用执行,这个时候硬件环境和操作系统已经初始化完成,可以执行应用相关代码。

rt_components_init() 函数会遍历通过剩下的其他几个宏申明的初始化函数表。

RT-Thread的自动初始化机制使用了自定义RTI符号段,将需要在启动时进行初始化的函数指针放到了该段中,形成一张初始化函数表,在系统启动过程中会遍历该表,并调用表中的函数,达到自动初始化的目的。

在这里插入图片描述
初始化函数主动通过这些宏接口进行申明,如INIT_BOARD_EXPORT(rt_hw_usart_init),,链接器会自动收集所有被申明的初始化函数,放到 RTI 符号段中,该符号段位于内存分布的 RO 段中,该 RTI 符号段中的所有函数在系统初始化时会被自动调用。

静态对象和动态对象

RT-Thread内核采用面向对象的设计思想进行设计,系统级的基础设施都是一种内核对象,例如线程,信号量,互斥量,定时器等。
内核对象分为两类:静态内核对象和动态内核对象。
静态内核对象通常放在RW段和ZI段,在系统启动后在程序中初始化;
动态内核对象则是从内存堆中创建的,而后手工做初始化。

thread1 是一个静态线程对象,而 thread2 是一个动态线程对象。
thread1对象的内存空间,包括线程控制块thread1与栈空间thread1_stack都是编译时决定的,因为代码中不存在初始值,都统一放在未初始化数据段中。

静态对象会占用RAM空间,不依赖于内存堆管理器,内存分配时间确定。
动态对象则依赖于内存堆管理器,运行时申请RAM空间,当对象被删除后,占用的RAM空间被释放。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/296511.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

LVM-系统

# Linux常见的文件系统:ext4,xfs,vfat(linux和window都能够识别) mkfs.ext4 /dev/sdb1 # 格式化为ext4文件系统 mkfs.xfs /dev/sdb2 # 格式化为xfs文件系统 mkfs.vfat /dev/sdb1 # 格式化为vfat文件系统 mksw…

带您了解目前AI在测试领域能够解决的那些问题

AI在测试领域主要应用场景 话不多说,直接给结论: 接口测试脚本的自动生成和校验(依赖研发ai工具)测试用例的自动生成UI自动化测试脚本的自动生成和校验测试文档的自动生成快速了解初涉的业务领域 关于ai对研发和测试的整体影响…

MFC 视图窗口

目录 视图窗口概述 视图窗口的使用 视图窗口创建流程 命令消息 WM_COMMAND 处理顺序 对象关系 视图窗口概述 作用:提供了一个用于显示数据的窗口 关于视图窗口 视图类是用来展示用户,文档类是用来存储和管理数据视图窗口是覆盖掉框架窗口的客户区…

整数规划-割平面法

整数规划-割平面法 割平面法思想Gomorys割平面法原理实例 谨以此博客作为学习期间的记录。 割平面法思想 在之前,梳理了分支定界法的流程:分支定界法 除了分支定界法,割平面法也是求解整数规划的另一个利器。 我们已经知道,线性规划的可行域…

全新资源素材站源码 功能齐备 界面干净整洁

源码介绍 简单安装说明: 1、整站程序上传后台 2、然后导入数据库文件到数据库, 3、修改conf里面的conf的数据库名字及密码 4、配置伪静态 规则: location ~* \.(htm)$ { rewrite "^(.*)/(.?).htm(.*?)$" $1/index.php?$2…

【postgres】8、Range 类型

文章目录 8.17 Range 类型8.17.1 内置类型8.17.2 示例8.17.3 开闭区间8.17.4 无穷区间 https://www.postgresql.org/docs/current/rangetypes.html 8.17 Range 类型 Range 类型,可以描述一个数据区间,有明确的子类型,而且子类型应该能被排序…

IgH调试注意事项

1,不要在虚拟机测试,否则IgH无法收发数据包 现象:虚拟机中运行IgH master并绑定网卡后,主站由ORPHANED状态转换成IDLE状态,但无法收发数据报。 这是因为虚拟机用的是虚拟网卡,需通过iptables将数据包到转…

有关List的线程安全、高效读取:不变模式下的CopyOnWriteArrayList类、数据共享通道:BlockingQueue

有关List的线程安全 队列、链表之类的数据结构也是极常用的,几乎所有的应用程序都会与之相关。在java中, ArrayList和Vector都使用数组作为其内部实现。两者最大的不同在与Vector是线程安全的。 而ArrayList不是。此外LinkedList使用链表的数据结构实现…

【VB测绘程序设计】案例4——简单的四则运算练习Select Case语句的使用(附源码)

【VB测绘程序设计】案例4——简单的四则运算练习(附源码) 文章目录 前言一、界面预览二、程序介绍总结前言 在新手学习VB程序设计中,四则运算是基础,通过设计的TexT、按钮、label等控件,定义变量,实现简单程序的编写,提高对VB程序的入门训练。 一、界面预览 二、程序介…

【MybatisPlus快速入门】(2)SpringBoot整合MybatisPlus 之 标准数据层开发 代码示例

目录 1 标准CRUD使用2 新增3 删除4 修改5 根据ID查询6 查询所有7 MyBatis-Plus CRUD总结 之前我们已学习MyBatisPlus在代码示例与MyBatisPlus的简介,在这一节中我们重点学习的是数据层标准的CRUD(增删改查)的实现与分页功能。代码比较多,我们一个个来学习…

【开源】基于JAVA语言的企业项目合同信息系统

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 数据中心模块2.2 合同审批模块2.3 合同签订模块2.4 合同预警模块2.5 数据可视化模块 三、系统设计3.1 用例设计3.2 数据库设计3.2.1 合同审批表3.2.2 合同签订表3.2.3 合同预警表 四、系统展示五、核心代码5.1 查询合同…

九、W5100S/W5500+RP2040之MicroPython开发<HTTPOneNET示例>

文章目录 1. 前言2. 平台操作流程2.1 创建设备2.2 创建数据流模板 3. WIZnet以太网芯片4. 示例讲解以及使用4.1 程序流程图4.2 测试准备4.3 连接方式4.4 相关代码4.5 烧录验证 5. 注意事项6. 相关链接 1. 前言 在这个智能硬件和物联网时代,MicroPython和树莓派PICO正…