YZ返回博客
Interactive field noteFreeRTOS 整体架构图集:RISC-V 移植版
技术

FreeRTOS Kernel / RISC-V Port

FreeRTOS 整体架构图集

RISC-V(机器模式、单核)GCC 移植 为锚点。每个流程步骤下直接附上游真实代码port.c / portASM.S / portmacro.h,为清晰做了裁剪与中文注释)。

操作系统FreeRTOSRISC-V嵌入式

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:运行 → 就绪(被抢占 / 时间片到)
去等待:运行 → 阻塞(vTaskDelay / 等队列·信号量)
被唤醒:阻塞 → 就绪(延时到期 / 等到资源)
冻结:运行 / 就绪 → 挂起(vTaskSuspend)
解冻:挂起 → 就绪(vTaskResume)

关键:阻塞态不占 CPU——任务等延时时不是"空转",而是被移出就绪表,CPU 去跑别的任务。这是 RTOS 高效的核心。

0.5 中断、异常、trap(本文最关键的概念)

中断(Interrupt):硬件主动发的"打断信号",让 CPU 暂停当前任务去处理紧急事件(定时器到期、按键、收到数据),处理完自动回来。异常(Exception):CPU 执行指令时撞上的"状况"(非法指令、除零、主动 ecall),与指令 同步 发生。trap:RISC-V 对"中断或异常"的 统称——都进同一入口 mtvec,靠 mcause 区分。保存现场:一进 trap 先存当前寄存器(否则处理函数会冲掉任务的值),处理完恢复。ISR:中断里跑的函数,越短越好。

0.6 RISC-V 的"模式"——特权级别

模式全称用途能干什么
M-modeMachine 机器上电复位就在这;裸机 FreeRTOS 跑在这最高权限:所有 CSR、所有硬件都能碰
S-modeSupervisor 监管给操作系统内核(Linux 等)用中等权限
U-modeUser 用户给普通应用程序用最低,被隔离

为什么 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 与启动代码。

应用层用户业务逻辑
任务 A任务 B任务 C中断 ISR
API 层(头文件)稳定接口
task.hqueue.h/semphr.htimers.hevent_groups.hstream_buffer.h
内核核心架构无关
tasks.c 调度/任务/延迟/通知queue.c 队列/信号量/互斥量list.c 双向链表timers.cevent_groups.cstream_buffer.c
可移植层 · 内存
heap_1~5.c pvPortMalloc / vPortFree
port 移植层(RISC-V)架构相关
port.c 启动调度器/配 TickportASM.S + portContext.h trap handler/切换portmacro.h
启动与硬件抽象复位初始化 + 装载 mtvec + 时钟
startup.S / crt0链接脚本 .ldBSP / HAL 时钟 · CLINT
RISC-V 硬件CSR + 机器定时器
mstatus 全局中断使能mtvec trap 入口mie / mip 中断使能/挂起mcause / mepc 异常原因/返回地址mtime / mtimecmp 滴答源

2 内核核心数据结构

调度器的全部状态集中在 tasks.c 的若干全局变量里。

调度器全局状态
pxCurrentTCB · uxTopReadyPriority · xTickCount
就绪列表 pxReadyTasksLists[ ]
优先级 0链表
优先级 1链表
优先级 N链表(最高)
uxTopReadyPriority 位图一步定位最高非空表
延迟列表(双表+指针)
xDelayedTaskList1 / List2
pxDelayedTaskList →当前
pxOverflowDelayedTaskList →Tick 回绕
vTaskDelay/等队列超时 → 此表;到期 → 回就绪
挂起 / 待搬运
xSuspendedTaskListvTaskSuspend
xPendingReadyList中断里就绪、待离开临界区再搬
TCB 内两个节点
xStateListItem就绪/延迟/挂起
xEventListItem队列/信号量事件表
一个任务:1 个状态表 + 至多 1 个事件表

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 清零。

startup.S (示意,标准结构)
.global _start
_start:
    csrw    mstatus, zero            # 关中断
    la      sp, __stack_top          # 1) 栈顶(来自链接脚本 .ld)

    /* 2) 搬 .data(Flash→RAM)、清 .bss —— 此处省略循环 */

    call    main                     # 进入 C 世界
5:  wfi
    j       5b
3★ 装载 mtvec(port 不负责!)startup.S

把统一 trap 入口写进 mtvecport 层自己不写 mtvec——漏了这步,首个 Tick 中断就找不到入口。

startup.S (接上一步)
    la      t0, freertos_risc_v_trap_handler
    csrw    mtvec, t0                # ★ mtvec = 统一 trap 入口(直接模式)
⚠ 这两行是 startup.S 的责任,xPortStartScheduler()没有写 mtvec。
4call main():建任务 + 启动调度器main.c

xTaskCreate 内部:pvPortMalloc 分配 TCB+栈 → pxPortInitialiseStack() 预填栈帧 → 挂进就绪列表。

main.c
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

portASM.S · pxPortInitialiseStack (真实,裁剪)  a0=栈顶,a1=pxCode,a2=pvParameters
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、开中断、切到第一个任务。

portable/GCC/RISC-V/port.c · xPortStartScheduler (真实,裁剪)
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 位 mtimemtimecmp = mtime + 一个 Tick

port.c · vPortSetupTimerInterrupt (真实,CLINT)  configMTIME(S)_BASE_ADDRESS 指向 CLINT
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 返回。

portASM.S · xPortStartFirstTask (真实,裁剪)
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 节流程图对应的代码):

portASM.S · freertos_risc_v_trap_handler (真实,裁剪)
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            # 从(新)任务栈恢复;mret

→ 可视化分流逻辑见 第 4 节流程图;让出 ecall 与临界区宏也见 第 4 节

14重配 mtimecmp + xTaskIncrementTick()portASM.Stasks.c

每次 Tick 中断里把比较值往后推一个 Tick,否则不再触发;随后 xTaskIncrementTick() 推进计数、搬到期任务回就绪表。

portASM.S · portUPDATE_MTIMER_COMPARE_REGISTER (真实,64 位路径)
.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
.endm
15vTaskSwitchContext():选最高优先级就绪 TCBtasks.c

uxTopReadyPriority(位图)一步定位最高非空优先级,取该就绪链表头,更新 pxCurrentTCB。纯 C、与架构无关。

16RESTORE_CONTEXT + mret:进入新任务portASM.S

从(新)任务栈恢复 x1..x31 + mepc/mstatusmret 跳进新任务。回到步骤 12 循环。

→ 逐寄存器保存/恢复的微观见 第 4 节"切换微观"

4 关键中断与上下文切换(RISC-V)

直接模式 mtvec:所有中断/异常进 freertos_risc_v_trap_handler,按 mcause 最高位分流。对应的真实汇编已在第 3 节【步骤 13】、mtimecmp 重配在【步骤 14】;本节给出可视化与机制补充。

进入 freertos_risc_v_trap_handler
portcontextSAVE_CONTEXT_INTERNAL
保存 x1..x31 + mepc + mstatus → 当前任务栈;更新 pxTopOfStack
csrr a0,mcause   csrr a1,mepc
mcause ≥ 0 ?(看最高位)
0 = 同步异常
mepc += 4(跳过触发指令)
sp = xISRStackTop
mcause == 11 ?
(M-mode ecall)
是 → portYIELD()
call vTaskSwitchContext
application_exception_handler
(默认 j . 挂死)
1 = 异步中断
保存 mepc(不改)
sp = xISRStackTop
mcause == MSB|7 ?
机器定时器
是 → Tick
portUPDATE_MTIMER_COMPARE_REGISTER
call xTaskIncrementTick
非0 → vTaskSwitchContext
application_interrupt_handler
(用户外部中断)
portcontextRESTORE_CONTEXT → mret
注意:切到中断栈用的是软件变量 xISRStackTop不是 mscratch。上下文存在任务自己的栈上;RISC-V 进 trap 时硬件清 MIE,处理期间不嵌套,故栈模型简单。

主动让出 = 一条 ecall;临界区靠 mstatus.MIE

portmacro.h · 真实
#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 继续]

要点汇总

不用 mscratch:上下文存在任务自己的栈,随后 sp = xISRStackTop 切到专用中断栈执行 C handler。硬件清 MIE,处理期间不嵌套。
无 PendSV:在同一个 trap handler 末尾完成 保存→选任务→恢复→mret
让出即 ecallportYIELD() 一条指令 → mcause=11 异常 → 统一切换路径。
首任务用 retxPortStartFirstTask 载回寄存器并 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.cport 移植层启动调度器、配 Tick、临界区计数xPortStartScheduler · vPortSetupTimerInterrupt · xCriticalNesting
portASM.S + portContext.hport 移植层trap handler、栈初始化、首任务启动、上下文保存/恢复freertos_risc_v_trap_handler · pxPortInitialiseStack · xPortStartFirstTask
portmacro.hport 移植层移植类型与宏(让出/临界区)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 节 建立整体印象,再来这里查具体词。

A. RTOS / 调度
RTOS
实时操作系统:支持多任务、对响应时间有确定性保证的微型内核。FreeRTOS 是其一。
内核 Kernel
操作系统的核心;这里指 FreeRTOS 的调度 + 同步 + 通信那层。
任务 Task / 线程
一个独立执行流,有自己的栈和寄存器现场;基本调度单位。
调度器 Scheduler
决定"此刻运行哪个任务"的机制。
上下文切换 Context Switch
保存当前任务寄存器现场、恢复另一任务现场的过程。
优先级 Priority
任务的重要级,高者优先。
抢占 Preemption
高优先级任务打断低优先级任务。
时间片 / 轮转
同优先级任务轮流各跑一个 Tick。
Tick 滴答
固定周期的定时器中断,系统的"心跳"。
临界区 Critical Section
不可被打断的代码段,靠关中断保护。
空闲任务 Idle Task
最低优先级的系统任务,无任务可跑时执行。
B. FreeRTOS 对象
TCB 任务控制块
描述一个任务的结构体(栈顶、状态节点、优先级…)。
就绪/延迟/挂起列表
分别装"能跑的""在等的""被冻结的"任务的链表。
队列 Queue
任务间传数据的 FIFO,自带阻塞/唤醒。
信号量 Semaphore
计数式"凭证",用于同步/计数资源;二值的叫二值信号量。
互斥量 Mutex
带"优先级继承"的二值信号量,防优先级反转。
优先级反转
高优先级等低优先级持有的锁,反被中优先级抢 CPU 的现象。
任务通知
轻量的任务间事件/数据机制,比队列快。
软件定时器
由"定时器守护任务"回调,非硬件中断。
事件组 Event Group
一组二进制位,用于"等多事件"。
流/消息缓冲
单生产者-单消费者的高效缓冲。
堆 Heap / heap_1~5
内核对象(TCB/队列/栈)的内存分配,5 种策略。
C. RISC-V / 架构
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)。
D. 构建与嵌入式
复位向量 / 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 由硬件分离
主动让出ecallmcause=11 异常)SCB->ICSR 写 PENDSVSET
首任务进入ret(载回寄存器 + 手动置 MIE)SVC 指令 + SVCHandler
中断后返回mretbx lr(EXC_RETURN)