[RISC-V MCU 应用开发] RISC-V MCU 启动流程与链接脚本实战:从 _start 到 .data/.bss 初始化

[复制链接]
4|0
【RISC-V MCU 启动流程与链接脚本实战】

很多刚上手 RISC-V MCU 的朋友,第一次看启动文件时都会有点懵:为什么复位后不是直接跑 main?全局变量是怎么被初始化的?中断向量表又放在哪里?这篇帖子把从复位到 main 的完整链路拆开讲清楚,最后给出一份可直接复用的链接脚本模板。

一、复位之后发生了什么

以常见的 RISC-V MCU(GD32VF103、CH32V 系列、沁恒、先楫等)为例,芯片上电或复位后:先从固定复位向量地址取指;第一条指令通常是一条跳转,跳到真正的启动代码 _start;此时 .data 段还在 Flash 里、.bss 段还没清零,而 C 代码假定这些变量已经就绪,所以必须在进入 main 之前完成搬运与清零。这就是启动汇编存在的唯一理由:为 C 语言准备一个已经初始化完毕的运行环境。

二、启动汇编 _start 的关键动作

一个典型的 RV32 启动骨架:

    .section .init
    .globl _start
_start:
    la sp, _sp            1. 设置栈指针
    csrw mstatus, zero    2. 关闭全局中断
    csrw mie, zero
    la a0, _data_lma      3. 拷贝 .data:Flash -> RAM
    la a1, _data_vma
    la a2, _edata
    bgeu a1, a2, 2f
1:  lw t0, (a0)
    sw t0, (a1)
    addi a0, a0, 4
    addi a1, a1, 4
    bltu a1, a2, 1b
2:  la a0, _bss_start    4. 清零 .bss
    la a1, _bss_end
    bgeu a0, a1, 4f
3:  sw zero, (a0)
    addi a0, a0, 4
    bltu a0, a1, 3b
4:  call SystemInit
    call main
    j .

最容易出错的是第 3、4 步的边界判断。如果链接脚本里 _data_lma 与 _data_vma 计算错误,就会出现变量初值是随机数的经典现象。

三、链接脚本里的三个地址

链接脚本的核心是回答一个问题:同一份数据,在 Flash 里在哪里、在 RAM 里在哪里。

MEMORY
{
  FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 128K
  RAM  (rwx) : ORIGIN = 0x20000000, LENGTH = 32K
}

SECTIONS
{
  .text : { KEEP(*(.init)) *(.text*) _etext = .; } > FLASH
  .data : AT(_data_lma) { _data_vma = .; *(.data*) _edata = .; } > RAM
  _data_lma = LOADADDR(.data);
  .bss (NOLOAD) : { _bss_start = .; *(.bss*) *(COMMON) _bss_end = .; } > RAM
  .stack (NOLOAD) : { . = ALIGN(16); . = . + 0x1000; _sp = .; } > RAM
}

几点经验:.data 段一定要用 AT() 指定加载地址 LMA,否则链接器会认为它不需要搬运;KEEP(*(.init)) 很关键,开了 --gc-sections 之后启动代码属于没人调用的段,很容易被回收;.bss 必须加 NOLOAD,避免 bin 文件凭空多出几 KB 的零;栈顶 _sp 建议 16 字节对齐,RISC-V ABI 要求栈指针 16 字节对齐,不对齐在某些库函数里会直接触发异常。

四、中断向量表怎么放

RISC-V 的向量表位置由 mtvec 决定,常见两种模式:直接模式(低两位 00,所有异常跳同一地址)和向量模式(低两位 01,入口地址 = BASE + 4 × cause)。多数国产 MCU 用了自家定制的向量表布局,建议直接参考厂商启动文件,不要自己臆造。向量表若放在 Flash,注意必须满足 BASE 的对齐要求。

五、几个高频踩坑点

1、变量初值不对:九成是 .data 搬运没做或 LMA/VMA 写错,可以反汇编确认 _data_lma 是否落在 Flash 区间;
2、一进 main 就跑飞:检查栈指针是否落在 RAM 内、.stack 段有没有被其它段覆盖;
3、开优化后启动失败:优先怀疑 --gc-sections 把 .init 回收了;
4、全局变量在中断里被莫名改掉:检查 .bss 清零是否放在了中断使能之后;
5、启动阶段不要调用标准库的 memcpy/memset,此时运行环境还不完整,搬运建议自己写循环。

六、一份可复用的检查清单

- 复位向量是否指向 _start
- .data 的 LMA 与 VMA 是否都已定义
- .bss 是否 NOLOAD
- 栈顶是否 16 字节对齐且在 RAM 范围内
- KEEP 是否保护了 .init
- map 文件里 .data 的大小与预期是否一致

把上面这些过一遍,绝大多数上电不跑的问题都能定位。欢迎在评论区贴出你遇到的启动问题,一起交流。
您需要登录后才可以回帖 登录 | 注册

本版积分规则

397

主题

3248

帖子

4

粉丝
快速回复 在线客服 返回列表 返回顶部
0