嵌入式系统之初:STM32启动文件详解——内存篇
本文最后更新于149 天前,其中的信息可能已经过时,如有错误请发送邮件到527388734@qq.com

从神秘的汇编代码到C语言的main()函数,这中间发生了什么?为什么我们的全局变量能够正常使用?为什么中断服务程序能够准确响应?这一切的答案,都隐藏在那个常常被我们忽略的启动文件中。

在嵌入式开发中,我们往往专注于业务逻辑的实现,却很少深入思考:当芯片上电复位的那一刻,硬件是如何一步步搭建起C语言的运行环境的?今天,就让我们一同揭开STM32启动文件的神秘面纱,探寻从硬件到软件的桥梁是如何搭建的。

本系列文章将从内存管理的角度出发,基于Cortex-M3内核的启动文件进行深度解析。建议读者打开任意一个STM32F103工程的启动文件(通常命名为startup_stm32f103xb.s),跟随本文的脚步,一起探索。

在启动文件之初,首先会通过汇编指令为堆区(Heap)和栈区(Stack)分配内存空间,这也是搭建C语言运行环境的关键步骤,具体代码如下所示:

Stack_Size		EQU     0x400
                AREA    STACK, NOINIT, READWRITE, ALIGN=3
Stack_Mem       SPACE   Stack_Size
__initial_sp
; <h> Heap Configuration
;   <o>  Heap Size (in Bytes) <0x0-0xFFFFFFFF:8>
; </h>
Heap_Size      EQU     0x200
                AREA    HEAP, NOINIT, READWRITE, ALIGN=3
__heap_base
Heap_Mem        SPACE   Heap_Size
__heap_limit
  • Stack_Size 定义了栈区的大小,通过 EQU 0x400 将其设置为 1024 字节(1KB)
  • Heap_Size 同样使用 EQU 指令,为堆区分配了 512 字节的空间

在这里就会为大家抛出一个疑问:什么是堆和栈?它们在嵌入式系统中扮演着什么角色?

简单来说,堆和栈都是位于 STM32 RAM(随机存取存储器) 中的内存区域。RAM 具有可读可写的特性,通常用于存储程序运行过程中产生的临时数据,这些数据在断电后会丢失。除了堆和栈之外,RAM 中还包含 .data 段和 .bss 段,它们共同构成了程序的运行时内存布局:

  • 堆(Heap):用于动态内存分配,程序员可以通过 mallocfree 等函数主动申请和释放内存空间。
  • 栈(Stack):主要用于存储函数调用时的局部变量、函数参数和返回地址等临时数据。
  • .data段:已初始化的全局/静态变量。
  • .bss 段:未初始化的全局/静态变量。

了解了 RAM 的用途,我们再来看看 STM32 的另一个重要存储介质——ROM(只读存储器),也就是我们常说的 Flash。与 RAM 不同,Flash 中存储的数据具有掉电不丢失的特性,因此我们编译好的程序代码就是存储在 Flash 中的(想象一下如果程序存储在 RAM 中,每次重新上电都需要重新烧录,那将是多么可怕的场景哈哈)。Flash 主要包含以下段:

  • .text 段:存储程序代码的可执行代码。
  • .rodata 段:常量字符串、const常量,具有只读属性(从这个角度来看我们也就可以知道为什么const修饰的变量我们不能修改了,因为他存储在ROM只读区)。

理解了这些基础知识后,我们再回头看启动文件中的配置,就能发现以下代码在当前工程设置下是存在问题的:

void function1() {
    char buf[2048];  // 栈溢出!(栈只有1KB),而此处声明了2KB。
}
void function2() {
    char *buf = malloc(1024);  // 堆空间不足!堆空间在启动文件中只是分配了512个字节
}

看到启动文件为我们分配的堆栈资源如此有限,很多开发者可能会产生疑问:在面对需要大量内存的应用场景时(比如处理HTTP协议、图像处理、复杂通信协议等),我们是否就束手无策了呢?当然不是! 关键在于我们要主动管理和优化内存资源。

首先,让我们探查STM32芯片中可用的RAM资源。点击Keil的”魔术棒”(Options for Target),在Target选项卡中可以看到内存配置:

  • RAM起始地址: 0x20000000
  • 大小: 0x5000 (20KB)

这意味着我们的芯片拥有20KB的RAM资源!大概的布局就是这样的。

而系统只给我们分配了1KB的堆和0.5KB的栈,可以见得是不是太小气了点哈哈。

看到这个配置,你可能会想:20 - 1 - 0.5 = 18.5KB,这不是还有大量空闲RAM吗?且慢!别忘记了之前提及的RAM资源可不只有堆区和栈区,还有一个重要的”内存消耗者”——全局变量。

在嵌入式系统中,RAM不仅要为堆栈服务,还要存储程序中所有的全局变量和静态变量。这些变量的内存占用不会在启动文件中体现,而是在编译后自动确定。幸运的是,Keil在编译后会生成一个.map文件(直接在工程目录下搜.map即可找到),这个文件就像我们的”内存账本”,详细记录了每一字节内存的去向。查看其中的关键数据:

可见我们的全局变量用了大概8.62KB的RAM,所以我们真实结余的RAM空间是20-1-0.5-8.62=9.88KB!既然发现了这9.88KB的可利用空间,我们就可以根据应用需求,智能地重新分配堆栈比例。比如,对于一个需要处理网络数据的应用:

Stack_Size      EQU     0x1000     ; 栈: 4KB (原来1KB)
Heap_Size       EQU     0x0800     ; 堆: 2KB (原来0.5KB)

现在的RAM使用情况就是20-4-2-9.88=4.12KB(合理预留!)

在深入理解启动文件之前,博主也曾在堆栈资源的困扰中挣扎——栈溢出导致的数据踩踏、堆空间不足造成的内存分配失败,这些内存问题屡屡导致程序崩溃。过去的解决思路往往局限于”节流”:削减缓冲区大小、简化数据结构、甚至牺牲功能特性。这种方法虽然能暂时缓解问题,但本质上治标不治本,既费时费力,又限制了系统的扩展性。

通过今天介绍的主动内存管理方法,我们完全可以换一种思路:基于准确的内存使用数据,对RAM资源进行科学的架构设计。这种”开源”而非”节流”的思维转变,让我们能够在有限的硬件资源内实现最优的性能配置。

说到这里,不得不提连接整个内存体系的关键一环——分散加载文件(.sct文件)。这个由Keil根据工程配置自动生成的文件,实际上定义了程序的内存布局蓝图(我们同样可以在项目目录下搜索.sct文件)。

; *************************************************************
; *** Scatter-Loading Description File generated by uVision ***
; *************************************************************

LR_IROM1 0x08000000 0x00010000  {    ; 加载区域:Flash 64KB
  ER_IROM1 0x08000000 0x00010000  {  ; 执行区域:代码和常量
   *.o (RESET, +First)               ; 中断向量表优先放置
   *(InRoot$$Sections)               ; 库的初始化段
   .ANY (+RO)                        ; 所有只读数据
  }
  RW_IRAM1 0x20000000 0x00005000  {  ; 执行区域:RAM 20KB
   .ANY (+RW +ZI)                    ; 所有读写和零初始化数据
  }
}

.sct文件与启动文件形成了完美的分工协作:

  • .sct文件:在编译链接阶段划定内存边界
  • 启动文件:在运行阶段,在边界内划分堆栈

理解了这三者的关系(Keil配置 → .sct文件 → 启动文件),我们就建立起了从源码到硬件的完整认知链条。下次面对内存挑战时,你将拥有从架构层面解决问题的底气与能力!

文末附加内容

评论

  1. rick
    Android Chrome
    6 月前
    2025-10-29 5:38:31

    打卡

    • 博主
      rick
      Windows Edge
      6 月前
      2025-10-30 22:20:37

      😊

  2. a
    Windows Chrome
    6 月前
    2025-10-29 9:26:57

    hhhh

  3. Android Chrome
    已编辑
    6 月前
    2025-10-29 10:07:04

    点我

  4. Android Chrome
    已编辑
    6 月前
    2025-10-29 10:24:59
  5. yu
    Android Firefox
    6 月前
    2025-10-29 10:27:26

    感谢分享

    • 博主
      yu
      Windows Edge
      6 月前
      2025-10-30 22:19:33

      希望可以帮到你!

  6. Android Chrome
    已编辑
    6 月前
    2025-10-29 10:28:21

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇