FreeRTOS Kernel / RISC-V Port
FreeRTOS 整体架构图集
以 RISC-V(机器模式、单核)GCC 移植 为锚点。每个流程步骤下直接附上游真实代码(port.c / portASM.S / portmacro.h,为清晰做了裁剪与中文注释)。
0 前置知识:从零理解
已熟悉 RTOS / 嵌入式 / RISC-V 的可跳到 第 1 节。本节用大白话把"为什么、是什么"讲清楚,后面的架构图和代码才看得懂。
0.1 为什么单片机要用 RTOS
裸机程序:一个 main() 塞个 while(1),事情串行做,等延时时 CPU 干等空转;想"一边采数据一边发串口"得自己拆状态机。RTOS 把程序拆成多个独立 任务,内核负责 调度 谁先跑。"实时"不是"快",是 可预测/确定——承诺某动作一定在截止时间内完成(硬实时错过即失败,如安全气囊;软实时偶尔可容忍,如视频掉帧)。FreeRTOS 是小巧开源、工业界用得最多的 RTOS 之一。
0.2 一个 CPU 怎么"同时"跑多个任务(核心直觉)
CPU 同一时刻只执行一条指令,"多任务"是快速切换造成的 假象。每个任务 = 一段代码 + 自己的栈 + 一组"自己的寄存器值"。上下文切换:把 A 此刻的寄存器值 存进 A 的栈,把 B 上次存的寄存器值 从 B 的栈取回——B 就"继续"运行,仿佛没被打断。这就是本文反复出现"保存/恢复上下文、栈帧"的原因——它就是切换的物理动作。
0.3 调度:优先级、抢占、Tick 心跳
优先级高者一旦就绪立即 抢占 正在跑的低者;同优先级 时间片轮转 各跑一个 Tick;Tick 是固定间隔(常 1ms)的定时器中断,系统的"心跳",每次推进计数、唤醒到期任务、可能切换;空闲任务优先级最低,没任务可跑时它运行。
0.4 任务的状态
能跑,排队
在 CPU 上
在等,不占 CPU
被冻结
关键:阻塞态不占 CPU——任务等延时时不是"空转",而是被移出就绪表,CPU 去跑别的任务。这是 RTOS 高效的核心。
0.5 中断、异常、trap(本文最关键的概念)
中断(Interrupt):硬件主动发的"打断信号",让 CPU 暂停当前任务去处理紧急事件(定时器到期、按键、收到数据),处理完自动回来。异常(Exception):CPU 执行指令时撞上的"状况"(非法指令、除零、主动 ecall),与指令 同步 发生。trap:RISC-V 对"中断或异常"的 统称——都进同一入口 mtvec,靠 mcause 区分。保存现场:一进 trap 先存当前寄存器(否则处理函数会冲掉任务的值),处理完恢复。ISR:中断里跑的函数,越短越好。
0.6 RISC-V 的"模式"——特权级别
| 模式 | 全称 | 用途 | 能干什么 |
|---|---|---|---|
| M-mode | Machine 机器 | 上电复位就在这;裸机 FreeRTOS 跑在这 | 最高权限:所有 CSR、所有硬件都能碰 |
| S-mode | Supervisor 监管 | 给操作系统内核(Linux 等)用 | 中等权限 |
| U-mode | User 用户 | 给普通应用程序用 | 最低,被隔离 |
为什么 FreeRTOS 选 M-mode?裸机没有"内核 vs 用户程序"的隔离需求,直接用最高权限最省事——能直接配定时器、直接收中断。模式之间切换靠专门指令(ecall 向下请求、mret 向上返回)和 CSR 的 MPP 字段。所以代码里全是 mstatus/mtvec/mcause 这些 m 开头的——m = Machine 模式专属。
0.7 反复出现的硬件概念
寄存器:CPU 内部极快的存储。① 通用寄存器 32 个 x0–x31,有小名:ra(返回地址)、sp(栈顶)、a0–a7(参数)、s0–s11(需保存)、t0–t6(临时)。② CSR(控制状态寄存器):管 CPU 状态的特殊寄存器,如 mstatus(中断开关)、mtvec(trap 入口)、mcause(原因)、mepc(断点 PC);用 csrr 读、csrw 写、csrs/csrc 置/清位。
栈:一段后进先出的内存,RISC-V 上 向下生长(用得越多地址越小),sp 指栈顶。内存映射:把硬件寄存器绑到某内存地址,读写该地址就是操作硬件,所以见 *(volatile uint32_t*)0x...,volatile 防编译器优化。CLINT(核心本地中断器):提供 mtime(走时器)、mtimecmp(比较器,相等触发定时中断)、MSIP(软中断)——本文 Tick 靠它。PLIC:管外部中断(按键/串口)。hart:一个 RISC-V 执行核,多核有多个。
0.8 构建 / 嵌入式常识
上电复位:CPU 从固定地址(复位向量)开始,常先跑芯片厂的 Boot ROM 再跳到你的程序。startup.S / crt0:程序开头汇编,负责设栈、把 .data(有初值全局变量,从 Flash 搬到 RAM)、清 .bss(无初值,整块清零)、装 mtvec、调 main。链接脚本(.ld):定义段布局、给 __stack_top 等符号。HAL/BSP:封装硬件细节(配时钟、初始化外设)。
1 总体架构
从上到下 6 层。应用只依赖 API 头文件;内核核心是纯 C、可移植;底部是架构相关的 port 与启动代码。
2 内核核心数据结构
调度器的全部状态集中在 tasks.c 的若干全局变量里。
pxCurrentTCB · uxTopReadyPriority · xTickCount
TCB 结构(关键字段)
typedef struct tskTaskControlBlock {
volatile StackType_t *pxTopOfStack; /* 必为首字段! portASM 靠固定偏移取栈顶 */
ListItem_t xStateListItem; /* 挂在 就绪/延迟/挂起 列表 */
ListItem_t xEventListItem; /* 挂在 队列/信号量 事件列表 */
UBaseType_t uxPriority; /* 当前优先级 */
StackType_t *pxStack; /* 栈起始(分配 / 栈溢出检查) */
char pcTaskName[configMAX_TASK_NAME_LEN];
UBaseType_t uxBasePriority; /* 基础优先级(互斥量继承后还原) */
volatile uint32_t ulNotifiedValue[]; /* 任务通知 */
} tskTCB;
pxTopOfStack 必须是结构体第一个字段——portASM 靠固定偏移读写它,不能改顺序。3 上电 → 正常工作 全流程(RISC-V)
从硬件复位到第一个任务、再到 Tick 驱动的稳定调度。每一步展开即见其上游真实代码。
1上电复位硬件Boot ROM
PC 指向复位向量(SoC Boot ROM 提供的固定地址)。无软件代码——纯硬件行为。
2_start:设 sp / 搬 .data / 清 .bssstartup.S
C 运行时初始化:栈顶来自链接脚本,.data 从 Flash 拷到 RAM,.bss 清零。
.global _start
_start:
csrw mstatus, zero # 关中断
la sp, __stack_top # 1) 栈顶(来自链接脚本 .ld)
/* 2) 搬 .data(Flash→RAM)、清 .bss —— 此处省略循环 */
call main # 进入 C 世界
5: wfi
j 5b3★ 装载 mtvec(port 不负责!)startup.S
把统一 trap 入口写进 mtvec。port 层自己不写 mtvec——漏了这步,首个 Tick 中断就找不到入口。
la t0, freertos_risc_v_trap_handler
csrw mtvec, t0 # ★ mtvec = 统一 trap 入口(直接模式)startup.S 的责任,xPortStartScheduler() 里没有写 mtvec。4call main():建任务 + 启动调度器main.c
xTaskCreate 内部:pvPortMalloc 分配 TCB+栈 → pxPortInitialiseStack() 预填栈帧 → 挂进就绪列表。
int main(void)
{
prvInitHardware(); /* 时钟/外设/CLINT(BSP) */
xTaskCreate(vTaskA, "A", 256, NULL, tskIDLE_PRIORITY + 2, NULL);
xTaskCreate(vTaskB, "B", 256, NULL, tskIDLE_PRIORITY + 1, NULL);
vTaskStartScheduler(); /* 不返回 */
for (;;) {}
}5prvInitHardware()BSP / HAL
配置系统时钟、外设、CLINT。由板级支持包提供,与内核无关。
6xTaskCreate() × N:预填栈帧portASM.Sheap
pxPortInitialiseStack() 从栈顶向下铺一帧"假的"寄存器状态,顺序必须和恢复顺序镜像。关键:mstatus=0x1880(MPIE=1、MPP=Machine)、a0=pvParameters、栈顶放 pxCode。
pxPortInitialiseStack:
addi a0, a0, -portWORD_SIZE
store_x x0, 0(a0) # 临界区嵌套计数 = 0
addi a0, a0, -(22 * portWORD_SIZE) # x10..x31
store_x a2, 0(a0) # pvParameters → x10/a0(任务参数)
addi a0, a0, -(6 * portWORD_SIZE) # x5..x9 + x1(ra)
load_x t0, xTaskReturnAddress
store_x t0, 0(a0) # ra = 任务"返回"地址
csrr t0, mstatus
andi t0, t0, ~0x8 # 清 MIE
addi t1, x0, 0x188 ; slli t1, t1, 4 # t1 = 0x1880 = MPIE | MPP=Machine
or t0, t0, t1
addi a0, a0, -portWORD_SIZE
store_x t0, 0(a0) # mstatus 入栈
addi a0, a0, -portWORD_SIZE
store_x a1, 0(a0) # pxCode 入栈(= pxTopOfStack 指向处)
ret预填后栈帧(自 pxTopOfStack 向上):pxCode(兼作 mepc/ra) → mstatus=0x1880 → chip-specific → ra/s0..s11/a0..a7(a0=pvParameters) → 临界区计数。
7vTaskStartScheduler() → xPortStartScheduler()port.c
port 层做三件事:配 Tick、开中断、切到第一个任务。
BaseType_t xPortStartScheduler( void )
{
extern void xPortStartFirstTask( void );
vPortSetupTimerInterrupt(); /* 1) 配机器定时器 */
__asm volatile ( "csrs mie, %0" :: "r"(0x880) ); /* 2) 开 MTIE+MEIE */
xPortStartFirstTask(); /* 3) 切到第一个任务(不返回)*/
return pdFAIL;
}8vPortSetupTimerInterrupt():设 mtimecmpport.cCLINT
读 mhartid 算 mtimecmp 地址;原子读 64 位 mtime;mtimecmp = mtime + 一个 Tick。
void vPortSetupTimerInterrupt( void ) /* weak,可被应用覆盖 */
{
volatile uint32_t * const pulTimeHigh = (volatile uint32_t*)(configMTIME_BASE_ADDRESS + 4);
volatile uint32_t * const pulTimeLow = (volatile uint32_t*)(configMTIME_BASE_ADDRESS);
volatile uint32_t ulHartId;
uint32_t ulCurrentTimeHigh, ulCurrentTimeLow;
__asm volatile ( "csrr %0, mhartid" : "=r"(ulHartId) );
pullMachineTimerCompareRegister =
(volatile uint64_t*)(configMTIMECMP_BASE_ADDRESS + ulHartId * sizeof(uint64_t));
do { /* 原子读 64 位 mtime */
ulCurrentTimeHigh = *pulTimeHigh;
ulCurrentTimeLow = *pulTimeLow;
} while (ulCurrentTimeHigh != *pulTimeHigh);
ullNextTime = ((uint64_t)ulCurrentTimeHigh << 32) | ulCurrentTimeLow;
ullNextTime += uxTimerIncrementsForOneTick; /* = CPU_CLOCK_HZ / TICK_RATE_HZ */
*pullMachineTimerCompareRegister = ullNextTime; /* 到期触发第一个 Tick */
}9csrs mie, 0x880:开中断port.c
就是上一步 xPortStartScheduler 里那一条:
__asm volatile ( "csrs mie, %0" :: "r"(0x880) );
// 0x080 = bit7 MTIE 机器定时器中断
// 0x800 = bit11 MEIE 外部中断(全局 mstatus.MIE 在下一步 xPortStartFirstTask 里再置位。)
10xPortStartFirstTask():切到第一个任务(用 ret,不是 mret!)portASM.S
把寄存器直接载回寄存器堆、手动置 mstatus.MIE、然后 ret 跳到 ra(=pxCode)。此时并没有"正在处理的中断"可以 mret 返回。
xPortStartFirstTask:
load_x sp, pxCurrentTCB # sp = &pxCurrentTCB
load_x sp, 0(sp) # sp = pxCurrentTCB->pxTopOfStack(首字段)
load_x x1, 0(sp) # x1/ra = 偏移0 = pxCode(任务入口)
# portasmRESTORE_ADDITIONAL_REGISTERS # 恢复 chip-specific 寄存器
load_x x5, 1*portWORD_SIZE(sp) # x5 = 初始 mstatus
addi x5, x5, 0x08 # 置 MIE(因下面用 ret 而非 mret)
csrw mstatus, x5 # ★ 此刻起中断开启
load_x x7, 5*portWORD_SIZE(sp) # t2
load_x x8, 6*portWORD_SIZE(sp) # s0/fp
# … 恢复 x9..x31 …
# 恢复该任务临界区嵌套计数 → xCriticalNesting
addi sp, sp, portCONTEXT_SIZE # 弹整帧
ret # ★ 跳到 ra=pxCode,进第一个任务11进入第一个任务应用
中断已开,开始跑任务函数。mepc 那套机制留给真实中断/异常后的恢复(见步骤 13/16)。
12正常运行tasks.cqueue.c
任务跑业务;遇 vTaskDelay/等队列则阻塞进延迟/事件表;更高优先级就绪即抢占。下方 13–16 是稳定运行期的 Tick 循环。
13Tick 中断 → trap handler(按 mcause 分流)portASM.S
mtime ≥ mtimecmp 触发机器定时器中断(mcause = MSB|7),进统一入口。完整汇编如下(也是第 4 节流程图对应的代码):
freertos_risc_v_trap_handler:
portcontextSAVE_CONTEXT_INTERNAL # 保存到任务栈;更新 pxCurrentTCB->pxTopOfStack
csrr a0, mcause
csrr a1, mepc
bge a0, x0, synchronous_exception # 最高位=0 → 同步异常
asynchronous_interrupt: # ── 中断 ──
store_x a1, 0(sp) # 保存原 mepc
load_x sp, xISRStackTop # ★ 切到专用中断栈(非 mscratch)
j handle_interrupt
synchronous_exception: # ── 异常(含 ecall 让出)──
addi a1, a1, 4 # mepc 前进一条(跳过 ecall)
store_x a1, 0(sp)
load_x sp, xISRStackTop
j handle_exception
handle_interrupt: # 是否机器定时器? mcause==(1<<(xlen-1))|7
li t0, 1 ; slli t0, t0, __riscv_xlen-1 ; addi t1, t0, 7
bne a0, t1, application_interrupt_handler
portUPDATE_MTIMER_COMPARE_REGISTER # 重配 mtimecmp(见步骤 14)
call xTaskIncrementTick
beqz a0, processed_source # 没解阻塞 → 不切换
call vTaskSwitchContext
j processed_source
handle_exception:
li t0, 11 # 11 = M-mode ecall(即 portYIELD)
bne a0, t0, application_exception_handler
call vTaskSwitchContext # ecall 让出 → 直接切换
processed_source:
portcontextRESTORE_CONTEXT # 从(新)任务栈恢复;mret14重配 mtimecmp + xTaskIncrementTick()portASM.Stasks.c
每次 Tick 中断里把比较值往后推一个 Tick,否则不再触发;随后 xTaskIncrementTick() 推进计数、搬到期任务回就绪表。
.macro portUPDATE_MTIMER_COMPARE_REGISTER
load_x a0, pullMachineTimerCompareRegister # a0 = &mtimecmp
load_x a1, pullNextTime # a1 = &ullNextTime
ld t2, 0(a1) # t2 = ullNextTime
sd t2, 0(a0) # mtimecmp = ullNextTime
ld t0, uxTimerIncrementsForOneTick
add t4, t0, t2 # 下一刻
sd t4, 0(a1) # ullNextTime += 一个 Tick
.endm15vTaskSwitchContext():选最高优先级就绪 TCBtasks.c
用 uxTopReadyPriority(位图)一步定位最高非空优先级,取该就绪链表头,更新 pxCurrentTCB。纯 C、与架构无关。
16RESTORE_CONTEXT + mret:进入新任务portASM.S
从(新)任务栈恢复 x1..x31 + mepc/mstatus,mret 跳进新任务。回到步骤 12 循环。
→ 逐寄存器保存/恢复的微观见 第 4 节"切换微观"。
4 关键中断与上下文切换(RISC-V)
设 直接模式 mtvec:所有中断/异常进 freertos_risc_v_trap_handler,按 mcause 最高位分流。对应的真实汇编已在第 3 节【步骤 13】、mtimecmp 重配在【步骤 14】;本节给出可视化与机制补充。
保存 x1..x31 + mepc + mstatus → 当前任务栈;更新 pxTopOfStack
sp = xISRStackTop
(M-mode ecall)
(默认 j . 挂死)
sp = xISRStackTop
机器定时器
call xTaskIncrementTick
非0 → vTaskSwitchContext
(用户外部中断)
xISRStackTop,不是 mscratch。上下文存在任务自己的栈上;RISC-V 进 trap 时硬件清 MIE,处理期间不嵌套,故栈模型简单。主动让出 = 一条 ecall;临界区靠 mstatus.MIE
#define portYIELD() __asm volatile ( "ecall" )
/* 临界区 */
#define portDISABLE_INTERRUPTS() __asm volatile ( "csrc mstatus, 8" ) /* 清 MIE(bit3) */
#define portENABLE_INTERRUPTS() __asm volatile ( "csrs mstatus, 8" ) /* 置 MIE */
#define portENTER_CRITICAL() { portDISABLE_INTERRUPTS(); xCriticalNesting++; }
#define portEXIT_CRITICAL() { xCriticalNesting--; \
if( xCriticalNesting == 0 ) portENABLE_INTERRUPTS(); }ecall 触发 M-mode 环境调用异常(mcause=11)→ 走 handle_exception → vTaskSwitchContext。所以"主动让出"和"Tick 抢占"共用同一套保存/恢复代码——这正是 RISC-V 不需要 PendSV 的原因。xCriticalNesting 启动初值故意为 0xaaaaaaaa(非 0)防误开中断;退出临界区只有计数归零才真正开中断(支持嵌套)。切换微观(任务 A → 任务 B)
[任务 A 运行中] ──Tick/ecall──▶ 保存 A 的 x1..x31 + mepc + mstatus 到 A 的栈
pxCurrentTCB->pxTopOfStack = A.sp
sp = xISRStackTop ← 切到中断栈跑 C
▼
xTaskIncrementTick() / vTaskSwitchContext()
pxCurrentTCB = B(最高优先级就绪)
▼
RESTORE_CONTEXT: sp = B.pxTopOfStack
从 B 栈恢复 x1..x31;mepc←栈; mstatus←栈
▼
mret ──▶ [任务 B 继续]
要点汇总
sp = xISRStackTop 切到专用中断栈执行 C handler。硬件清 MIE,处理期间不嵌套。mret。portYIELD() 一条指令 → mcause=11 异常 → 统一切换路径。xPortStartFirstTask 载回寄存器并 ret(见步骤 10)。5 模块速查表
| 模块(文件) | 层 | 职责 | 关键符号 |
|---|---|---|---|
list.c | 内核核心 | 通用双向链表 List_t,一切状态/事件队列的基础 | vListInitialise · listINSERT_END · listGET_OWNER_OF_NEXT_ENTRY |
tasks.c | 内核核心 | 任务增删、调度器、就绪/延迟列表、阻塞超时、任务通知 | xTaskCreate · vTaskStartScheduler · vTaskSwitchContext · xTaskIncrementTick |
queue.c | 内核核心 | 队列 + 信号量 + 互斥量(优先级继承);传数据 + 阻塞同步 | xQueueSend · xQueueReceive · xSemaphoreTake |
timers.c | 内核核心 | 软件定时器,由守护任务调度回调 | xTimerCreate · xTimerStart · prvTimerTask |
event_groups.c | 内核核心 | 事件组(位级同步) | xEventGroupCreate · xEventGroupWaitBits |
stream_buffer.c | 内核核心 | 流/消息缓冲(单生产-单消费) | xStreamBufferSend · xMessageBufferSend |
heap_1~5.c | 可移植层·内存 | 内核对象内存分配,5 种策略 | pvPortMalloc · vPortFree |
port.c | port 移植层 | 启动调度器、配 Tick、临界区计数 | xPortStartScheduler · vPortSetupTimerInterrupt · xCriticalNesting |
portASM.S + portContext.h | port 移植层 | trap handler、栈初始化、首任务启动、上下文保存/恢复 | freertos_risc_v_trap_handler · pxPortInitialiseStack · xPortStartFirstTask |
portmacro.h | port 移植层 | 移植类型与宏(让出/临界区) | portYIELD(ecall) · portDISABLE/ENABLE_INTERRUPTS |
FreeRTOSConfig.h | 配置 | 裁剪内核 + CLINT 地址 | configMAX_PRIORITIES · configMTIME_BASE_ADDRESS · configMTIMECMP_BASE_ADDRESS |
startup.S / crt0 | 启动 | 复位初始化 sp/.data/.bss、装载 mtvec、调用 main | _start |
链接脚本 .ld | 启动 | 段布局、栈顶地址 | __stack_top |
| BSP / HAL | 硬件抽象 | 时钟、外设、CLINT | — |
6 术语表(速查)
按类别列出本文术语,每条一句话。新手先读 第 0 节 建立整体印象,再来这里查具体词。
- RTOS
- 实时操作系统:支持多任务、对响应时间有确定性保证的微型内核。FreeRTOS 是其一。
- 内核 Kernel
- 操作系统的核心;这里指 FreeRTOS 的调度 + 同步 + 通信那层。
- 任务 Task / 线程
- 一个独立执行流,有自己的栈和寄存器现场;基本调度单位。
- 调度器 Scheduler
- 决定"此刻运行哪个任务"的机制。
- 上下文切换 Context Switch
- 保存当前任务寄存器现场、恢复另一任务现场的过程。
- 优先级 Priority
- 任务的重要级,高者优先。
- 抢占 Preemption
- 高优先级任务打断低优先级任务。
- 时间片 / 轮转
- 同优先级任务轮流各跑一个 Tick。
- Tick 滴答
- 固定周期的定时器中断,系统的"心跳"。
- 临界区 Critical Section
- 不可被打断的代码段,靠关中断保护。
- 空闲任务 Idle Task
- 最低优先级的系统任务,无任务可跑时执行。
- TCB 任务控制块
- 描述一个任务的结构体(栈顶、状态节点、优先级…)。
- 就绪/延迟/挂起列表
- 分别装"能跑的""在等的""被冻结的"任务的链表。
- 队列 Queue
- 任务间传数据的 FIFO,自带阻塞/唤醒。
- 信号量 Semaphore
- 计数式"凭证",用于同步/计数资源;二值的叫二值信号量。
- 互斥量 Mutex
- 带"优先级继承"的二值信号量,防优先级反转。
- 优先级反转
- 高优先级等低优先级持有的锁,反被中优先级抢 CPU 的现象。
- 任务通知
- 轻量的任务间事件/数据机制,比队列快。
- 软件定时器
- 由"定时器守护任务"回调,非硬件中断。
- 事件组 Event Group
- 一组二进制位,用于"等多事件"。
- 流/消息缓冲
- 单生产者-单消费者的高效缓冲。
- 堆 Heap / heap_1~5
- 内核对象(TCB/队列/栈)的内存分配,5 种策略。
- M / S / U 模式
- 三档特权级,M 最高(本文 FreeRTOS 跑在 M)。
- CSR
- 控制状态寄存器,管 CPU 状态的特殊寄存器。
- mstatus
- 机器状态(全局中断使能 MIE、前一权限 MPP 等)。
- mtvec
- trap 入口地址;有直接/向量两种模式。
- mie / mip
- 中断使能 / 中断挂起寄存器。
- mcause
- trap 原因(最高位区分中断/异常,余为编号:7=机器定时器,11=ecall)。
- mepc
- 触发 trap 的指令地址;mret 回到这里。
- mret
- 从机器态 trap 返回的指令(恢复 PC 和权限)。
- ecall
- 环境调用指令,触发异常;用于主动让出。portYIELD() 就是它。
- mtime / mtimecmp
- 机器计时器/比较器;mtime≥mtimecmp 触发定时中断。
- CLINT / PLIC
- 本地中断器(定时+软中断)/ 外部中断控制器。
- hart
- 一个 RISC-V 执行核(hardware thread)。
- 通用寄存器 x0–x31
- 32 个通用寄存器,有 ABI 小名(ra/sp/a0–a7/s0–s11/t0–t6)。
- trap
- 中断或异常的统称,进 mtvec。
- ISR
- 中断服务程序。
- xISRStackTop
- 本移植专用中断栈顶(上下文存任务栈,再切到它跑 C)。
- 复位向量 / Boot ROM
- 上电后 CPU 第一条指令的地址,常由芯片厂固化。
- startup.S / crt0
- C 运行时启动代码(设栈、搬 .data、清 .bss、装 mtvec、调 main)。
- .data / .bss
- 有初值 / 无初值的全局变量段。
- 链接脚本 .ld
- 内存段布局定义,给 __stack_top 等符号。
- 内存映射寄存器
- 硬件寄存器映射到内存地址,用指针访问(配 volatile)。
- 弱符号 weak
- 允许应用覆盖的默认实现(如 vPortSetupTimerInterrupt)。
附 与 Cortex-M 的差异速记
| 维度 | RISC-V(本图) | Cortex-M(对照) |
|---|---|---|
| 中断入口 | 单一 mtvec(直接模式)按 mcause 分流;也支持向量模式 | 向量表,每路独立 handler |
| Tick 源 | 机器定时器(mtime/mtimecmp,CLINT) | SysTick |
| 切换专用中断 | 无,在 trap handler 末尾完成 | PendSV |
| ISR 栈切换 | sp = xISRStackTop(软件变量),上下文存任务栈 | PSP/MSP 由硬件分离 |
| 主动让出 | ecall(mcause=11 异常) | SCB->ICSR 写 PENDSVSET |
| 首任务进入 | ret(载回寄存器 + 手动置 MIE) | SVC 指令 + SVCHandler |
| 中断后返回 | mret | bx lr(EXC_RETURN) |