【risc-v专栏 07】启动与 bare-metal 流程

发布时间:2026/9/29 21:43:17
【risc-v专栏 07】启动与 bare-metal 流程 本篇定位:RISC-V 专题第 7 篇。本篇把上电到 main 运行的完整链路讲透:启动汇编必做 5 件事、多核启动、ROM 固化规则、链接脚本与启动的配合。读完后你能从零写一个 bare-metal 启动流程,能调任何启动死机。一、启动链路总览RISC-V MCU 的典型启动链路:上电 │ ▼ BootROM(出厂固化,只读) │ - 硬件复位向量跳到这里 │ - 初始化最小环境(栈/时钟) │ - 加载下一级(Flash/bootloader)到 RAM │ - 跳转 ▼ Bootloader(可选,多级) │ - 初始化更多外设(DDR/外设时钟) │ - 加载应用固件 │ - 跳转 ▼ Application / RTOS │ - _start 启动汇编 │ - 初始化运行时(.data/.bss/gp/mtvec) │ - 调 main() ▼ main() 运行各级职责分工级谁做干什么BootROM芯片厂商最小初始化 加载下一级Bootloader你外设初始化 加载 appApp _start启动汇编 runtime运行时初始化 main二、启动汇编必做 5 件事(App _start)这是本篇核心。App 启动汇编(crt0.s或 startup.s)必须做 5 件事,缺一不可:2.1 第 1 件:初始化 sp(栈指针)_start: la sp, _stack_top # sp 指向栈顶(链接脚本定义)栈用于函数调用、局部变量、中断保存sp 没初始化,任何 C 函数调用都崩(压栈写到野地址)栈大小要够(中断嵌套深 任务多的场景),链接脚本里预留2.2 第 2 件:初始化 gp(全局指针)⭐ RISC-V 特有.option push .option norelax # 防 linker relaxation 把这步优化掉 la gp, __global_pointer$ .option popgp 指向.sdata段中点,用于 gp 相对寻址(②专题讲过)忘了设 gp,gp 相对寻址全崩——访问小全局变量读到野地址.option norelax必须加:linker relaxation 会把la gp优化成mv gp,gp(无意义),必须禁掉这步 relaxationgp 忘设是 RISC-V 启动常见崩点ARM 没有 gp 概念,你 ARM 经验带过来容易漏这步。症状:启动早期随机崩溃,访问全局变量拿到错值。调试启动死机,先确认 gp 有没有设——这是 RISC-V 启动汇编最容易被漏的一步。2.3 第 3 件:拷贝 .data 段(Flash → RAM)la t0, _sidata # .data 在 Flash 的加载地址(LMA) la t1, _sdata # .data 在 RAM 的运行地址(VMA) la t2, _edata 1: bgeu t1, t2, 2f lw t3, 0(t0) sw t3, 0(t1) addi t0, t0, 4 addi t1, t1, 4 j 1b 2:.data是已初始化全局变量,存储在 Flash(掉电保留),运行时要在 RAM(可读写)启动时从 Flash 拷到 RAM不拷贝:.data 变量读到 Flash 的值,写它就崩(Flash 只读)2.4 第 4 件:清零 .bss 段la t0, _sbss la t1, _ebss li t2, 0 1: bgeu t0, t1, 2f sw t2, 0(t0) addi t0, t0, 4 j 1b 2:.bss是未初始化/初始化为 0 的全局变量,链接脚本不占 Flash(只记大小),运行时在 RAMC 标准要求 .bss 默认 0不清零:全局变量是随机值,所有if (flag0)类逻辑全错——常见诡异 bug 源2.5 第 5 件:设置 mtvec(trap 入口)la t0, trap_vector csrw mtvec, t0 # 配 trap 入口地址没配 mtvec,任何异常/中断跳到默认地址(0 或保留),立刻死机套娃必须在开中断前配好(开 MIE 后才有中断,但 trap 随时可能来,如 ecall/异常)完整启动汇编骨架.section .text.init .global _start _start: # 1. sp la sp, _stack_top # 2. gp(防 relaxation) .option push .option norelax la gp, __global_pointer$ .option pop # 3. 拷 .data la t0, _sidata la t1, _sdata la t2, _edata 1: bgeu t1, t2, 2f lw t3, 0(t0) sw t3, 0(t1) addi t0, t0, 4 addi t1, t1, 4 j 1b 2: # 4. 清 .bss la t0, _sbss la t1, _ebss li t2, 0 3: bgeu t0, t1, 4f sw t2, 0(t0) addi t0, t0, 4 j 3b 4: # 5. mtvec la t0, trap_vector csrw mtvec, t0 # 6. (可选)初始化 FPU、cache、中断控制器 # 7. 调 main call main # main 返回(不应该),死循环 5: wfi j 5b三、链接脚本与启动的配合启动汇编用的_stack_top_sidata_sdata_edata_sbss_ebss__global_pointer$全是链接脚本定义的符号。典型链接脚本结构ENTRY(_start) MEMORY { FLASH (rx) : ORIGIN 0x08000000, LENGTH 512K RAM (rwx) : ORIGIN 0x20000000, LENGTH 128K } SECTIONS { .text : { KEEP(*(.text.init)) /* 启动代码在前 */ *(.text .text.*) *(.rodata .rodata.*) } FLASH .data : { _sdata .; /* VMA: RAM 里运行地址 */ *(.data .data.*) _edata .; } RAM AT FLASH /* LMA 在 Flash */ _sidata LOADADDR(.data); /* .data 的 Flash 加载地址 */ .bss : { _sbss .; *(.bss .bss.*) *(COMMON) _ebss .; } RAM .sdata : { /* 小数据段,gp 寻址 */ *(.sdata .sdata.*) } RAM .sbss : { *(.sbss .sbss.*) } RAM /* gp 指向 .sdata 中点 */ __global_pointer$ . 0x800; /* ±2KB 范围 */ /* 栈 */ _stack_top ORIGIN(RAM) LENGTH(RAM); }.data 的 LMA vs VMA.data RAM AT FLASH:VMA(Virtual Memory Address) RAM 地址,运行时在这里LMA(Load Memory Address) Flash 地址,启动前在这里_sidata LOADADDR(.data)给启动汇编用,从这拷贝到_sdata。LMA/VMA 概念不懂,启动汇编拷贝段就写不对。四、多核启动RISC-V 多核(SMP)启动,所有核上电都进 BootROM,但通常只有一个核(核0)跑启动流程,其他核park等待。4.1 主核(核0)流程_start: csrr t0, mhartid bnez t0, park # 非核0,去 park # 核0:正常启动流程 la sp, _stack_top # ... 5 件事 ... call main park: # 非主核:等待唤醒 wfi # 低功耗等待中断 j park # 醒来再检查(或被主核引导)4.2 主核唤醒从核主核跑起来后,通过 MSI(写从核的 MSIP)或设置共享内存的启动地址,唤醒从核:// 主核唤醒核1*(uint32_t*)(CLINTMSIP_OFFSET(1))1;// 触发核1的 MSI从核的 MSI handler 里跳转到应用代码。五、ROM 固化规则5.1 ROM 固化的三个硬条件位置无关或固定地址:ROM 烧死后地址不变不写自身所在段:ROM 只读可写状态在 SRAM:函数的可写全局状态必须在 SRAM,不能跟着代码在 ROM5.2 ROM 固化与启动的关系BootROM 本身就是最大的固化代码你固化的库函数(__divsi3/memcpy等)在 ROM,主程序链接时PROVIDE指向 ROM 地址启动汇编调用的早期函数(如memcpy拷 .data)若在 ROM,要先确认 ROM 可用5.3 启动早期不能用的函数启动汇编执行时,有些前提没建立,不能用:不能调 printf(stdio 没初始化)不能调 malloc(堆没初始化)不能调依赖 .data 的函数(.data 还没拷)不能调依赖全局对象的函数(C 构造没跑)早期只能用:纯计算函数、ROM 固化的无状态函数、直接寄存器操作。六、启动死机调试启动阶段死机最难调(没有 printf)。方法:6.1 串口/JTAG 早期输出用 UART 寄存器直接输出字符(不用 printf,绕过 stdio)在启动汇编每步后输出标记,定位卡在哪步_start: la sp, _stack_top uart_putc(1) # sp 设完 la gp, __global_pointer$ uart_putc(2) # gp 设完 # ...6.2 JTAG 单步JTAG 调试器能在启动早期挂上(若芯片支持)单步执行启动汇编,看哪步崩6.3 常见启动死机症状可能原因启动即崩,无输出sp 没设 / 复位向量错访问全局变量崩gp 没设全局变量值错.bss 没清零写全局变量崩.data 没拷贝(写 Flash)开中断后崩mtvec 没配随机崩链接脚本段地址错 / 栈溢出七、FreeRTOS 移植的启动钩子FreeRTOS RISC-V port 在启动流程里挂钩:7.1 FreeRTOS 需要的启动前提sp/gp/mtvec 已配(标准启动汇编)定时器(CLINT mtimecmp/外设timer)可配至少一个中断(MTI)使能7.2 FreeRTOS 启动序列// main 里intmain(void){hardware_init();// 你的外设初始化vPortDefineHeapRegions();// FreeRTOS 堆xTaskCreate(...);// 建任务vTaskStartScheduler();// 启动调度器while(1);// 不应到这}vTaskStartScheduler()内部:配定时器中断(vPortSetupTimerInterrupt配 mtimecmp)开全局中断(MIE)切到第一个任务(用 mret 或类似机制切栈)7.3 启动汇编里别开中断启动汇编不开 MIE,等 FreeRTOS 调度器启动时再开提前开中断,若 mtvec/handler 没就绪会崩port 层就是连接RISC-V 启动/trap/中断和FreeRTOS 调度的胶水:① 配 mtvec 指向 FreeRTOS 的 trap entry;② trap entry 用 mscratch 切栈(④专题);③ tick 用 MTI;④ 任务切换在 trap 里完成。本专题 ③④⑤⑦ 合起来,就是 FreeRTOS port 的全部基础。八、ARM vs RISC-V 启动对照ARM Cortex-MRISC-V复位向量向量表第1项(初始 SP) 第2项(reset)BootROM 跳 _start(或向量表)初始 SP硬件自动从向量表加载软件在 _start 里 la spgp无必须设(la gp, __global_pointer$).data 拷贝启动代码做启动代码做(一样).bss 清零启动代码做启动代码做(一样)trap 入口VTOR 配mtvec 配多核Cortex-M 多核少mhartid 判断主从九、本篇小结启动链路:BootROM → Bootloader → App _start → main启动汇编必做 5 件事:sp / gp / .data拷贝 / .bss清零 / mtvecgp 必须设且防 relaxation(.option norelax),RISC-V 特有易漏点链接脚本的 LMA/VMA 概念:.data 在 Flash(LMA)运行在 RAM(VMA)多核启动:mhartid 判断主从,从核 park,主核唤醒ROM 固化三硬条件:固定地址/不写自身段/状态在 SRAM启动早期不能用 printf/malloc/依赖 .data 的函数FreeRTOS port 挂在启动流程上:mtvec/tick/MIE速查表启动步骤代码设 spla sp, _stack_top设 gp.option norelax; la gp, __global_pointer$拷 .data从 _sidata 拷到 _sdata~_edata清 .bss_sbss~_ebss 清 0配 trapcsrw mtvec, trap_vector主核判断csrr t0, mhartid; bnez t0, park跳 maincall main.data LMA_sidata LOADADDR(.data)gp 范围__global_pointer$ .sdata中点,±2KB技术之路漫漫分享是为了更好地交流。如果本文的内容对你有启发希望能得到你的点赞 和收藏 ⭐。如果你在调试过程中遇到了其他问题欢迎在评论区 留言我们一起探讨。也欢迎关注 我一起交流底层开发的那些事儿。

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询