摘要:在 components.c 的代码中找到 rtthread_startup 函数,我们看到 RT-Thread 的启动流程如下图所示:\u003C\u002Fp\u003E\u003Cimg src=\"http:\u002F\u002Fp3.pstatp.com\u002Flarge\u002Fpgc-image\u002FRXr9pAQ1JXnANO\" img_width=\"958\" img_height=\"576\" alt=\"从0-1带你入门物联网操作系统(4)——系统启动流程及内存分布\" inline=\"0\"\u003E\u003Cp\u003E其中 rtthread_startup 函数的代码如下所示:\u003C\u002Fp\u003E\u003Cpre\u003E\u003Ccode\u003E 1int rtthread_startup(void)\u003Cbr\u003E2{\u003Cbr\u003E3 rt_hw_interrupt_disable。\u003C\u002Fp\u003E\u003Cimg src=\"http:\u002F\u002Fp9.pstatp.com\u002Flarge\u002Fpgc-image\u002FR6jtWuPxCsbNJ\" img_width=\"420\" img_height=\"298\" alt=\"从0-1带你入门物联网操作系统(4)——系统启动流程及内存分布\" inline=\"0\"\u003E\u003Cp\u003E\u003Cstrong\u003ERT-Thread线上活动\u003C\u002Fstrong\u003E\u003C\u002Fp\u003E\u003Cp\u003E1、\u003Cstrong\u003E【RT-Thread能力认证考试12月——RCEA】\u003C\u002Fstrong\u003E经过第一次考试的验证,RT-Thread能力认证得到了更多社区开发者和产业界的大力支持。

"\u003Cp\u003E\u003C\u002Fp\u003E\u003Ch2\u003E1、从0-1带你入门物联网操作系统(1)——Keil 模拟器 STM32F103 上手指南\u003C\u002Fh2\u003E\u003Ch2\u003E2、从0-1带你入门物联网操作系统(2)——潘多拉 STM32L475 上手指南\u003C\u002Fh2\u003E\u003Cp\u003E3、从0-1带你入门物联网操作系统(3)——内核介绍\u003C\u002Fp\u003E\u003Cp\u003E\u003Cstrong\u003E物联网操作系统RT-Thread 启动流程\u003C\u002Fstrong\u003E\u003C\u002Fp\u003E\u003Cp\u003E一般了解一份代码大多从启动部分开始,同样这里也采用这种方式,先寻找启动的源头。以 MDK-ARM 为例,MDK-ARM 的用户程序入口为 main 函数,位于 main.c 文件中。系统启动后先从汇编代码 startup_stm32f103xe.s 开始运行,然后跳转到 C 代码,进行 RT-Thread 系统功能初始化,最后进入用户程序入口 main。\u003C\u002Fp\u003E\u003Cp\u003E为了在进入 main 之前完成 RT-Thread 系统功能初始化,我们使用了 MDK 的扩展功能 \u003Ccode\u003E$Sub$$\u003C\u002Fcode\u003E和\u003Ccode\u003E$Super$$\u003C\u002Fcode\u003E。可以给 main 添加\u003Ccode\u003E$Sub$$\u003C\u002Fcode\u003E的前缀符号作为一个新功能函数\u003Ccode\u003E$Sub$$main\u003C\u002Fcode\u003E,这个\u003Ccode\u003E$Sub$$main\u003C\u002Fcode\u003E可以先调用一些要补充在 main 之前的功能函数(这里添加 RT-Thread 系统初始化功能),再调用\u003Ccode\u003E$Super$$main\u003C\u002Fcode\u003E转到 main 函数执行,这样可以让用户不用去管 main 之前的系统初始化操作。\u003C\u002Fp\u003E\u003Cp\u003E关于 \u003Ccode\u003E$Sub$$\u003C\u002Fcode\u003E和\u003Ccode\u003E$Super$$\u003C\u002Fcode\u003E扩展功能的使用,详见 ARM® Compiler v5.06 for µVision®armlink User Guide。\u003C\u002Fp\u003E\u003Cp\u003E下面我们来看看在 components.c 中定义的这段代码:\u003C\u002Fp\u003E\u003Cpre\u003E\u003Ccode\u003E1\u002F* $Sub$$main 函数 *\u002F\u003Cbr\u003E2int $Sub$$main(void)\u003Cbr\u003E3{\u003Cbr\u003E4 rtthread_startup;\u003Cbr\u003E5 return 0;\u003Cbr\u003E6}\u003Cbr\u003E\u003C\u002Fcode\u003E\u003C\u002Fpre\u003E\u003Cp\u003E在这里 \u003Ccode\u003E$Sub$$main\u003C\u002Fcode\u003E函数仅仅调用了 rtthread_startup 函数。RT-Thread 支持多种平台和多种编译器,而 rtthread_startup 函数是 RT-Thread 规定的统一入口点,所以\u003Ccode\u003E$Sub$$main\u003C\u002Fcode\u003E函数只需调用 rtthread_startup 函数即可(例如采用 GNU GCC 编译器编译的 RT-Thread,就是直接从汇编启动代码部分跳转到 rtthread_startup 函数中,并开始第一个 C 代码的执行)。在 components.c 的代码中找到 rtthread_startup 函数,我们看到 RT-Thread 的启动流程如下图所示:\u003C\u002Fp\u003E\u003Cimg src=\"http:\u002F\u002Fp3.pstatp.com\u002Flarge\u002Fpgc-image\u002FRXr9pAQ1JXnANO\" img_width=\"958\" img_height=\"576\" alt=\"从0-1带你入门物联网操作系统(4)——系统启动流程及内存分布\" inline=\"0\"\u003E\u003Cp\u003E其中 rtthread_startup 函数的代码如下所示:\u003C\u002Fp\u003E\u003Cpre\u003E\u003Ccode\u003E 1int rtthread_startup(void)\u003Cbr\u003E2{\u003Cbr\u003E3 rt_hw_interrupt_disable;\u003Cbr\u003E4\u003Cbr\u003E5 \u002F* 板级初始化:需在该函数内部进行系统堆的初始化 *\u002F\u003Cbr\u003E6 rt_hw_board_init;\u003Cbr\u003E7\u003Cbr\u003E8 \u002F* 打印 RT-Thread 版本信息 *\u002F\u003Cbr\u003E9 rt_show_version;\u003Cbr\u003E10\u003Cbr\u003E11 \u002F* 定时器初始化 *\u002F\u003Cbr\u003E12 rt_system_timer_init;\u003Cbr\u003E13\u003Cbr\u003E14 \u002F* 调度器初始化 *\u002F\u003Cbr\u003E15 rt_system_scheduler_init;\u003Cbr\u003E16\u003Cbr\u003E17#ifdef RT_USING_SIGNALS\u003Cbr\u003E18 \u002F* 信号初始化 *\u002F\u003Cbr\u003E19 rt_system_signal_init;\u003Cbr\u003E20#endif\u003Cbr\u003E21\u003Cbr\u003E22 \u002F* 由此创建一个用户 main 线程 *\u002F\u003Cbr\u003E23 rt_application_init;\u003Cbr\u003E24\u003Cbr\u003E25 \u002F* 定时器线程初始化 *\u002F\u003Cbr\u003E26 rt_system_timer_thread_init;\u003Cbr\u003E27\u003Cbr\u003E28 \u002F* 空闲线程初始化 *\u002F\u003Cbr\u003E29 rt_thread_idle_init;\u003Cbr\u003E30\u003Cbr\u003E31 \u002F* 启动调度器 *\u002F\u003Cbr\u003E32 rt_system_scheduler_start;\u003Cbr\u003E33\u003Cbr\u003E34 \u002F* 不会执行至此 *\u002F\u003Cbr\u003E35 return 0;\u003Cbr\u003E36}\u003Cbr\u003E\u003C\u002Fcode\u003E\u003C\u002Fpre\u003E\u003Cp\u003E这部分启动代码,大致可以分为四个部分:\u003C\u002Fp\u003E\u003Cp\u003E(1)初始化与系统相关的硬件;\u003C\u002Fp\u003E\u003Cp\u003E(2)初始化系统内核对象,例如定时器、调度器、信号;\u003C\u002Fp\u003E\u003Cp\u003E(3)创建 main 线程,在 main 线程中对各类模块依次进行初始化;\u003C\u002Fp\u003E\u003Cp\u003E(4)初始化定时器线程、空闲线程,并启动调度器。\u003C\u002Fp\u003E\u003Cp\u003Ert_hw_board_init 中完成系统时钟设置,为系统提供心跳、串口初始化,将系统输入输出终端绑定到这个串口,后续系统运行信息就会从串口打印出来。\u003C\u002Fp\u003E\u003Cp\u003Emain 函数是 RT-Thread 的用户代码入口,用户可以在 main 函数里添加自己的应用。\u003C\u002Fp\u003E\u003Cpre\u003E\u003Ccode\u003E1int main(void)\u003Cbr\u003E2{\u003Cbr\u003E3 \u002F* user app entry *\u002F\u003Cbr\u003E4 return 0;\u003Cbr\u003E5}\u003Cbr\u003E\u003C\u002Fcode\u003E\u003C\u002Fpre\u003E\u003Cp\u003E\u003C\u002Fp\u003E\u003Ch2\u003ERT-Thread 程序内存分布\u003C\u002Fh2\u003E\u003Cp\u003E一般 MCU 包含的存储空间有:片内 Flash 与片内 RAM,RAM 相当于内存,Flash 相当于硬盘。编译器会将一个程序分类为好几个部分,分别存储在 MCU 不同的存储区。\u003C\u002Fp\u003E\u003Cp\u003EKeil 工程在编译完之后,会有相应的程序所占用的空间提示信息,如下所示:\u003C\u002Fp\u003E\u003Cpre\u003E\u003Ccode\u003E1linking...\u003Cbr\u003E2Program Size: Code=48008 RO-data=5660 RW-data=604 ZI-data=2124\u003Cbr\u003E3After Build - User command #1: fromelf --bin.\\build\\rtthread-stm32.axf--output rtthread.bin\u003Cbr\u003E4\".\\build\\rtthread-stm32.axf\" - 0 Error(s), 0 Warning(s).\u003Cbr\u003E5Build Time Elapsed: 00:00:07\u003Cbr\u003E\u003C\u002Fcode\u003E\u003C\u002Fpre\u003E\u003Cp\u003E上面提到的 Program Size 包含以下几个部分:\u003C\u002Fp\u003E\u003Cp\u003E1)Code:代码段,存放程序的代码部分;\u003C\u002Fp\u003E\u003Cp\u003E2)RO-data:只读数据段,存放程序中定义的常量;\u003C\u002Fp\u003E\u003Cp\u003E3)RW-data:读写数据段,存放初始化为非 0 值的全局变量;\u003C\u002Fp\u003E\u003Cp\u003E4)ZI-data:0 数据段,存放未初始化的全局变量及初始化为 0 的变量;\u003C\u002Fp\u003E\u003Cp\u003E编译完工程会生成一个. map 的文件,该文件说明了各个函数占用的尺寸和地址,在文件的最后几行也说明了上面几个字段的关系:\u003C\u002Fp\u003E\u003Cpre\u003E\u003Ccode\u003E1Total RO Size (Code + RO Data) 53668 ( 52.41kB)\u003Cbr\u003E2Total RW Size (RW Data + ZI Data) 2728 ( 2.66kB)\u003Cbr\u003E3Total ROM Size (Code + RO Data + RW Data) 53780 ( 52.52kB)\u003Cbr\u003E\u003C\u002Fcode\u003E\u003C\u002Fpre\u003E\u003Cp\u003E1)RO Size 包含了 Code 及 RO-data,表示程序占用 Flash 空间的大小;\u003C\u002Fp\u003E\u003Cp\u003E2)RW Size 包含了 RW-data 及 ZI-data,表示运行时占用的 RAM 的大小;\u003C\u002Fp\u003E\u003Cp\u003E3)ROM Size 包含了 Code、RO Data 以及 RW Data,表示烧写程序所占用的 Flash 空间的大小;\u003C\u002Fp\u003E\u003Cp\u003E程序运行之前,需要有文件实体被烧录到 STM32 的 Flash 中,一般是 bin 或者 hex 文件,该被烧录文件称为可执行映像文件。如图 3-3 中左图所示,是可执行映像文件烧录到 STM32 后的内存分布,它包含 RO 段和 RW 段两个部分:其中 RO 段中保存了 Code、RO-data 的数据,RW 段保存了 RW-data 的数据,由于 ZI-data 都是 0,所以未包含在映像文件中。\u003C\u002Fp\u003E\u003Cp\u003ESTM32 在上电启动之后默认从 Flash 启动,启动之后会将 RW 段中的 RW-data(初始化的全局变量)搬运到 RAM 中,但不会搬运 RO 段,即 CPU 的执行代码从 Flash 中读取,另外根据编译器给出的 ZI 地址和大小分配出 ZI 段,并将这块 RAM 区域清零。\u003C\u002Fp\u003E\u003Cimg src=\"http:\u002F\u002Fp1.pstatp.com\u002Flarge\u002Fpgc-image\u002FRXr9pApGophtPd\" img_width=\"586\" img_height=\"335\" alt=\"从0-1带你入门物联网操作系统(4)——系统启动流程及内存分布\" inline=\"0\"\u003E\u003Cp\u003E其中动态内存堆为未使用的 RAM 空间,应用程序申请和释放的内存块都来自该空间。\u003C\u002Fp\u003E\u003Cp\u003E如下面的例子:\u003C\u002Fp\u003E\u003Cpre\u003E\u003Ccode\u003E1rt_uint8_t* msg_ptr;\u003Cbr\u003E2msg_ptr = (rt_uint8_t*) rt_malloc (128);\u003Cbr\u003E3rt_memset(msg_ptr, 0, 128);\u003Cbr\u003E\u003C\u002Fcode\u003E\u003C\u002Fpre\u003E\u003Cp\u003E代码中的 msg_ptr 指针指向的 128 字节内存空间位于动态内存堆空间中。\u003C\u002Fp\u003E\u003Cp\u003E而一些全局变量则是存放于 RW 段和 ZI 段中,RW 段存放的是具有初始值的全局变量(而常量形式的全局变量则放置在 RO 段中,是只读属性的),ZI 段存放的系统未初始化的全局变量,如下面的例子:\u003C\u002Fp\u003E\u003Cpre\u003E\u003Ccode\u003E 1#include <rtthread.h>\u003Cbr\u003E2\u003Cbr\u003E3const static rt_uint32_t sensor_enable = 0x000000FE;\u003Cbr\u003E4rt_uint32_t sensor_value;\u003Cbr\u003E5rt_bool_t sensor_inited = RT_FALSE;\u003Cbr\u003E6\u003Cbr\u003E7void sensor_init\u003Cbr\u003E8{\u003Cbr\u003E9 \u002F* ... *\u002F\u003Cbr\u003E10}\u003Cbr\u003E\u003C\u002Fcode\u003E\u003C\u002Fpre\u003E\u003Cp\u003Esensor_value 存放在 ZI 段中,系统启动后会自动初始化成零(由用户程序或编译器提供的一些库函数初始化成零)。sensor_inited 变量则存放在 RW 段中,而 sensor_enable 存放在 RO 段中。\u003C\u002Fp\u003E\u003Cimg src=\"http:\u002F\u002Fp9.pstatp.com\u002Flarge\u002Fpgc-image\u002FR6jtWuPxCsbNJ\" img_width=\"420\" img_height=\"298\" alt=\"从0-1带你入门物联网操作系统(4)——系统启动流程及内存分布\" inline=\"0\"\u003E\u003Cp\u003E\u003Cstrong\u003ERT-Thread线上活动\u003C\u002Fstrong\u003E\u003C\u002Fp\u003E\u003Cp\u003E1、\u003Cstrong\u003E【RT-Thread能力认证考试12月——RCEA】\u003C\u002Fstrong\u003E经过第一次考试的验证,RT-Thread能力认证得到了更多社区开发者和产业界的大力支持!如果您有晋升、求职、寻找更好机会的需要,有深入学习和掌握RT-Thread的需求,欢迎垂询\u002F报考!\u003C\u002Fp\u003E\u003Cp\u003E能力认证官网链接:https:\u002F\u002Fwww.rt-thread.org\u002Fpage\u002Frac.html(在外部浏览器打开)\u003C\u002Fp\u003E\u003Cp\u003E立即报名\u003C\u002Fp\u003E\u003Cp\u003E\u003Cstrong\u003E#题外话#\u003C\u002Fstrong\u003E喜欢RT-Thread不要忘了在GitHub上留下你的\u003C\u002Fp\u003E\u003Cimg src=\"http:\u002F\u002Fp1.pstatp.com\u002Flarge\u002Fpgc-image\u002FR69B8gK696FgHO\" img_width=\"48\" img_height=\"48\" alt=\"从0-1带你入门物联网操作系统(4)——系统启动流程及内存分布\" inline=\"0\"\u003E\u003Cp\u003ESTAR哦,你的star对我们来说非常重要!链接地址:https:\u002F\u002Fgithub.com\u002FRT-Thread\u002Frt-thread\u003C\u002Fp\u003E\u003Cp\u003E你可以添加微信18917005679为好友,注明:公司+姓名,拉进 RT-Thread 官方微信交流群\u003C\u002Fp\u003E\u003Cp\u003E\u003Cstrong\u003ERT-Thread\u003C\u002Fstrong\u003E\u003C\u002Fp\u003E\u003Cp\u003E让物联网终端的开发变得简单、快速,芯片的价值得到最大化发挥。Apache2.0协议,可免费在商业产品中使用,不需要公布源码,无潜在商业风险。\u003C\u002Fp\u003E\u003Cimg src=\"http:\u002F\u002Fp3.pstatp.com\u002Flarge\u002Fpgc-image\u002FRLvZTTOCLSAUqZ\" img_width=\"27\" img_height=\"28\" alt=\"从0-1带你入门物联网操作系统(4)——系统启动流程及内存分布\" inline=\"0\"\u003E\u003Cp\u003E\u003Cstrong\u003E看这里,求赞!求转发!\u003C\u002Fstrong\u003E\u003C\u002Fp\u003E\u003Cimg src=\"http:\u002F\u002Fp1.pstatp.com\u002Flarge\u002Fpgc-image\u002FRLvZTZTFF01tt\" img_width=\"100\" img_height=\"100\" alt=\"从0-1带你入门物联网操作系统(4)——系统启动流程及内存分布\" inline=\"0\"\u003E"'.slice(6, -6), groupId: '6720009914896351757
相关文章