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

FreeRTOS Kernel / RISC-V Port

FreeRTOS 整体架构图集

以 RISC-V(机器模式、单核)GCC 移植 为例,整理启动、Tick 和上下文切换。相关步骤附有 port.c、portASM.S 和 portmacro.h 的代码片段,经过裁剪和中文注释;启动代码另用示例说明。

操作系统FreeRTOSRISC-V嵌入式

0 前置知识

本节先说明任务、调度、trap 和寄存器。已熟悉这些概念,可以跳到 第 1 节。

0.1 为什么单片机要用 RTOS

裸机程序常在 main() 的循环中安排工作,多个操作交错进行时需要自行组织状态机和中断。使用 RTOS 后,可以把工作拆成多个任务,由内核调度执行。实时系统关注的是响应能否满足截止时间;硬实时对错过期限有严格要求,软实时允许一定程度的超时。使用 FreeRTOS 提供了调度机制,具体期限仍需要根据任务执行时间和系统配置验证。

0.2 单核上的多任务与上下文切换

本文的单核模型中,一次运行一个任务。每个任务有自己的代码、栈和保存的寄存器状态。切换时,port 将任务 A 的上下文存入 A 的栈,再从任务 B 的栈恢复上下文,让 B 从上次停止的位置继续执行。后面的栈帧图展示的就是这些保存和恢复动作。

0.3 优先级、抢占与 Tick

启用抢占调度时,较高优先级任务就绪后可以抢占低优先级任务;同优先级任务是否按 Tick 轮转,取决于时间片配置。Tick 是周期性定时器中断,推进计数、唤醒延时到期的任务,并可能触发切换。空闲任务的优先级最低,没有其他就绪任务时运行。

0.4 任务的状态

就绪
能跑,排队
运行
在 CPU 上
阻塞
在等,不占 CPU
挂起
被冻结
进入运行:就绪 → 运行(被调度选中)
让出 CPU:运行 → 就绪(被抢占 / 时间片到)
去等待:运行 → 阻塞(vTaskDelay / 等队列·信号量)
被唤醒:阻塞 → 就绪(延时到期 / 等到资源)
冻结:运行 / 就绪 → 挂起(vTaskSuspend)
解冻:挂起 → 就绪(vTaskResume)

阻塞任务会被移出就绪列表,等待期间不执行,CPU 可以运行其他就绪任务。它与在循环中忙等待不同。

0.5 中断、异常与 trap

中断(Interrupt)由定时器或外设等异步事件触发。异常(Exception)与当前执行的指令有关,例如非法指令或 ecall。RISC-V 用 trap 统称中断和异常。本文使用 mtvec 直接模式,通过 mcause 区分来源。处理程序先保存寄存器状态,退出前恢复;ISR 是中断服务程序,执行时间需要纳入响应时延分析。

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

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

本文采用的 port 在 M-mode 中运行任务和内核,可以直接配置机器定时器和中断。mstatus、mtvec、mcause 都是机器模式的 CSR。ecall 触发环境调用异常;mret 根据保存的机器状态返回。这里的配置不代表所有 FreeRTOS 系统都不需要权限隔离。

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:由启动代码完成startup.S

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

startup.S (接上一步)
    la      t0, freertos_risc_v_trap_handler
    csrw    mtvec, t0                # ★ mtvec = 统一 trap 入口(直接模式)
这里需要由启动代码写入 mtvec;本图所用的 xPortStartScheduler() 片段没有包含这一步。
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 位 mtime;mtimecmp = 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 进入第一个任务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 中断更新比较值,安排下次定时事件;随后 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/mstatus,mret 跳进新任务。回到步骤 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。
让出即 ecall:portYIELD() 一条指令 → mcause=11 异常 → 统一切换路径。
首任务用 ret: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.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
实时操作系统,提供支持实时任务调度的机制;具体响应期限需要结合应用和配置验证。
内核 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 由硬件分离
主动让出ecall(mcause=11 异常)SCB->ICSR 写 PENDSVSET
首任务进入ret(载回寄存器 + 手动置 MIE)SVC 指令 + SVCHandler
中断后返回mretbx lr(EXC_RETURN)