目录
[1. 静态布局](#1. 静态布局)
[1.1 FLASH里存了什么?](#1.1 FLASH里存了什么?)
[1.1.1 中断向量表 (Vector Table)](#1.1.1 中断向量表 (Vector Table))
[1.1.2 代码段 (.text)](#1.1.2 代码段 (.text))
[1.1.3 只读数据段 (.rodata)](#1.1.3 只读数据段 (.rodata))
[1.1.4 读写数据段 (.rwdata)](#1.1.4 读写数据段 (.rwdata))
[1.2 SRAM里存了什么?](#1.2 SRAM里存了什么?)
[1.2.1 已初始化数据段 (.data)](#1.2.1 已初始化数据段 (.data))
[1.2.2 未初始化数据段 (.bss)](#1.2.2 未初始化数据段 (.bss))
[1.2.3 堆区 (Heap)](#1.2.3 堆区 (Heap))
[1.2.4 栈区 (Stack)](#1.2.4 栈区 (Stack))
[2. 时间维度上内存的变化过程](#2. 时间维度上内存的变化过程)
[2.1 硬件复位 - 读取向量表](#2.1 硬件复位 - 读取向量表)
[2.2 Reset_Handler 开始执行](#2.2 Reset_Handler 开始执行)
[2.3 .data 段初始化 - 从 FLASH 拷贝到 SRAM](#2.3 .data 段初始化 - 从 FLASH 拷贝到 SRAM)
[2.4 .bss 段清零](#2.4 .bss 段清零)
[2.5 进入 main() → RT-Thread 初始化](#2.5 进入 main() → RT-Thread 初始化)
[2.6 创建线程 → 调度器启动](#2.6 创建线程 → 调度器启动)
1. 静态布局
图1.1-FLASH与SRAM内容总览
1.1 FLASH里存了什么?
- 向量表(MSP初始值 + Reset_Handler地址 + 中断处理函数地址)
- .text 段(所有函数的机器码)
- .rodata 段(const 常量、字符串)
- .rwdata段 (全局变量的初始值副本)
1.1.1 中断向量表 (Vector Table)
中断向量表是系统最先读取的地方,决定了程序从哪里开始执行。前两个最重要的字: 0x08000000: 初始 MSP 值(主栈指针)、 0x08000004: Reset_Handler 地址(复位后第一条指令的位置)。**后续是各种中断处理函数地址:**NMI_Handler, HardFault_Handler, SysTick_Handler, USART1_IRQHandler...
1.1.2 代码段 (.text)
代码段存的是所有函数编译后的机器指令,CPU 从这里取指令执行。
包含内容:
- 启动代码 (Reset_Handler, SystemInit)
- main() 函数及你写的所有函数
- RT-Thread 内核代码 (rt_thread_create, rt_schedule...)【ps:有用到RT-Thread的情况下】
- 库函数代码 (HAL_GPIO_WritePin, printf...)
1.1.3 只读数据段 (.rodata)
只读数据段存的是程序运行期间不会改变的数据。
典型内容:
const char *msg = "Hello";→ 字符串 "Hello" 存这里const int table[] = {1,2,3};→ 常量数组switch-case跳转表- 浮点常量、查找表
1.1.4 读写数据段 (.rwdata)
读写数据段存的是已初始化全局变量的初始值,启动时会被拷贝到 SRAM。
为什么需要这个?
int counter = 100;→ 100 这个初始值存在 FLASH- 因为 SRAM 掉电丢失,每次上电需要从 FLASH 恢复初始值
- 启动代码负责把这块数据拷贝到 SRAM 的 .data 段
总结:
FLASH 是"只读"的程序存储器,存放不变的东西:代码、常量、以及变量的初始值模板。CPU 执行代码时从 FLASH 取指令。
1.2 SRAM里存了什么?
- .data 段(已初始化全局变量,从 FLASH 拷贝来)
- .bss 段(未初始化全局变量,启动时清零)
- 堆(rt_malloc 动态分配区)
- 栈(局部变量、函数调用)
1.2.1 已初始化数据段 (.data)
.data段存的是有初始值的全局变量和静态变量,值从 FLASH 拷贝而来。
哪些变量在这里:
int g_counter = 100;→ 全局变量,初始值 100static int s_flag = 1;→ 静态变量,初始值 1char g_name[] = "LED";→ 初始化的数组
**特点:**可读可写,程序运行时可以修改这些值
1.2.2 未初始化数据段 (.bss)
.bss段存的是未初始化或初始化为 0 的全局/静态变量,启动时被清零。
哪些变量在这里:
int g_value;→ 未初始化,默认 0static char buffer[256];→ 未初始化的数组int g_zero = 0;→ 显式初始化为 0
**为什么单独分出来?**不需要在 FLASH 中存储初始值,节省 FLASH 空间
1.2.3 堆区 (Heap)
动态内存分配区域,如果使用了RT-Thread,则由 RT-Thread 内存管理器管理。裸机开发则在.s启动文件中进行配置堆区的空间大小。
谁使用堆:
- malloc()
rt_malloc()/rt_free()rt_thread_create()→ 分配 TCB + 线程栈rt_mq_create()→ 消息队列缓冲区rt_sem_create()→ 信号量控制块
RT-Thread 堆初始化:
rt_system_heap_init(heap_begin, heap_end);
1.2.4 栈区 (Stack)
函数调用的工作区:局部变量、返回地址、寄存器保存
主栈 (MSP) 用于:
- 启动代码执行 (Reset_Handler → main)
- 所有中断处理函数
线程栈 (PSP) 用于:
- 每个 RT-Thread 线程有独立的栈
- 线程的局部变量、函数调用链
栈上存什么:
int local_var = 5;→ 局部变量- 函数参数、返回地址、保存的寄存器
总结:
SRAM 是"可读写"的运行时存储器,存放会变化的东西:全局变量、动态分配的内存、函数调用栈。掉电后内容全部丢失。
2. 时间维度上内存的变化过程
图2.1-时间维度上内存的变化过程
2.1 硬件复位 - 读取向量表
- CPU 复位,所有寄存器清零
- 硬件自动从 0x08000000 读取 4 字节 → 加载到 MSP
- 硬件自动从 0x08000004 读取 4 字节 → 加载到 PC
- CPU 开始从 PC 指向的地址取指令执行
2.2 Reset_Handler 开始执行
- 调用 SystemInit() 配置时钟、Flash等待周期
- 准备进行内存初始化
- 此时还不能使用全局变量!(因为还没初始化)
2.3 .data 段初始化 - 从 FLASH 拷贝到 SRAM
- 找到 FLASH 中 .data 初始值的位置
- 找到 SRAM 中 .data 段的位置 (_sdata)
- 逐字节拷贝,直到 _edata
- 所有带初始值的全局变量现在有了正确的值
2.4 .bss 段清零
- 找到 .bss 段的起始 (_sbss) 和结束 (_ebss)
- 将整个区域填充为 0
- 所有未初始化的全局变量现在是 0
2.5 进入 main() → RT-Thread 初始化
- 调用 main()
- RT-Thread 初始化内核
- rt_system_heap_init() 初始化堆内存管理器
- 现在可以使用 rt_malloc() 了
2.6 创建线程 → 调度器启动
- rt_thread_create() 从堆中分配 TCB + 栈
- 初始化线程栈帧
- rt_thread_startup() 将线程加入就绪队列
- rt_system_scheduler_start() 启动调度
- 切换到 PSP,第一个线程开始运行
总结:
上电后:硬件设置 MSP/PC → 启动代码拷贝 .data → 清零 .bss → 初始化堆 → 创建线程 → 运行