代码重定位,理解单片机的启动过程 📅 2026/7/15 3:41:25 目录一.段的概念二.重定位1.是什么2.为什么3.谁完成?三.清除.BSS段(ZI段)1.是什么2.清除四.代码段重定位一.段的概念看以下现象查看两者的地址程序虽然烧录进了 Flash起始地址 0x08000000ROM 里也确实记录了 A 和 B但程序执行到读取 RAM 中的 g_a 时打印出来的却是乱码。虽然 g_a A 这个初始值被烧录进了 ROMFlash但它只是“备份”在那里。芯片上电后必须由启动代码主动将这个“备份值”从 ROM 搬运到 RAM 中 g_a 的真正归属地程序才能正确读取。如果搬运失败或缺失RAM 中的 g_a 里残留的就是随机垃圾值打印出来自然就是乱码。char g_Char A; // 可读可写不能放在ROM上应该放在RAM里 const char g_Char2 B; // 只读变量可以放在ROM上 int g_A 0; // 初始值为0干嘛浪费空间保存在ROM上没必要 int g_B; // 没有初始化干嘛浪费空间保存在ROM上没必要所以程序分为这几个段代码段(RO-CODE)就是程序本身不会被修改可读可写的数据段(RW-DATA)有初始值的全局变量、静态变量需要从ROM上复制到内存只读的数据段(RO-DATA)可以放在ROM上不需要复制到内存BSS段或ZI段初始值为0的全局变量或静态变量没必要放在ROM上使用之前清零就可以未初始化的全局变量或静态变量没必要放在ROM上使用之前清零就可以局部变量保存在栈中运行时生成堆一块空闲空间使用malloc函数来管理它malloc函数可以自己写二.重定位1.是什么重定位 就是 把存储在 Flash只读存储器中的初始值在芯片上电后搬运到 RAM随机存取存储器中对应的变量地址上 的过程。2.为什么RAM 是易失性存储器芯片掉电后RAM 里的数据会全部丢失。因此初始值必须在掉电也不丢失的 Flash 里保存一份“备份”。变量的值需要被修改全局变量在程序运行中是可以被修改的例如 g_a C。这个修改发生的地点必须是 RAM因为 Flash 的写入速度慢且有寿命限制不适合频繁修改。3.谁完成?这个“搬运”任务是由 启动文件startup 文件如 startup_stm32f407xx.s 中的 Reset_Handler 函数来完成的。重定位就是把代码段、只读数据段、数据段复制到它的链接地址处。在keil中使用散列文件来描述。 散列分散排列 是的在STM32F103这类资源紧缺的单片机芯片中代码段保存在Flash上直接在Flash上运行数据段保存在Flash上使用前被复制到内存里下面这个就是散列文件; ************************************************************* ; *** Scatter-Loading Description File generated by uVision *** ; ************************************************************* LR_IROM1 0x08000000 0x00080000 { ; load region size_region ER_IROM1 0x08000000 0x00080000 { ; load address execution address *.o (RESET, First) .ANY (RO) .ANY (XO) } RW_IRAM1 0x20000000 0x00010000 { ; RW data .ANY (RW ZI) } }目的就是把0x08000000的ROM拷贝到0x20000000的RAM这样‘A’就被赋值可以打印出来了三.清除.BSS段(ZI段)现象发现打印出来并不是01.是什么程序里的全局变量如果它的初始值为0或者没有设置初始值这些变量被放在BSS段里也叫ZI段。char g_Char A; const char g_Char2 B; int g_A 0; // 放在BSS段 int g_B; // 放在BSS段BSS段并不会放入bin文件中否则也太浪费空间了。 在使用BSS段里的变量之前把BSS段所占据的内存清零就可以了。注意对于keil来说一个本该放到BSS段的变量如果它所占据的空间小于等于8字节自己keil仍然会把它放在data段里。只有当它所占据的空间大于8字节时才会放到BSS段。int g_A[3] {0, 0}; // 放在BSS段 char g_B[9]; // 放在BSS段 int g_A[2] {0, 0}; // 放在data段 char g_B[8]; // 放在data段2.清除四.代码段重定位如果我将代码段的执行地址的地址修改一下那复位后程序会跳转到0x20000000地址处执行但是此时地址为空程序就就会出错。当然你也可以直接改变向量表的地址直接跳转到ROM继续运行也是可以。正常的执行流程应该是这样的上电复位↓CPU从0x08000000读取中断向量表↓从向量表获取复位向量Reset_Handler地址↓执行复位处理程序在Flash中↓启动代码startup.s执行1. 将代码段从Flash拷贝到SRAM (0x20000000)2. 重定位.data段3. 清零.bss段4. 跳转到SRAM中的main函数 (0x20000000 offset)↓程序在SRAM中高速运行来看下正确的重定位代码PRESERVE8 THUMB ; Vector Table Mapped to Address 0 at Reset AREA RESET, DATA, READONLY EXPORT __Vectors __Vectors DCD 0 DCD Reset_Handler ; Reset Handler AREA |.text|, CODE, READONLY ; Reset handler Reset_Handler PROC EXPORT Reset_Handler [WEAK] IMPORT main IMPORT SystemInit LDR SP, 0x20000000 0x20000 BL SystemInit ;BL main LDR R0, main BLX R0 ENDP ENDvoid SystemInit(void) { extern int Image$$ER_IROM1$$Base; extern int Image$$ER_IROM1$$Length; extern int Load$$ER_IROM1$$Base; extern int Image$$RW_IRAM1$$Base; extern int Image$$RW_IRAM1$$Length; extern int Load$$RW_IRAM1$$Base; extern int Image$$RW_IRAM1$$ZI$$Base; extern int Image$$RW_IRAM1$$ZI$$Length; /* text relocate */ memcpy(Image$$ER_IROM1$$Base, Load$$ER_IROM1$$Base, Image$$ER_IROM1$$Length); /* data relocate */ memcpy(Image$$RW_IRAM1$$Base, Load$$RW_IRAM1$$Base, Image$$RW_IRAM1$$Length); /* bss clear */ memset(Image$$RW_IRAM1$$ZI$$Base, 0, Image$$RW_IRAM1$$ZI$$Length); }