轮询式调度机制介绍
任务 A 调用 vTaskDelay( x ) → 内部 portYIELD()
触发 ecall → 异常向量 0 → freertos_risc_v_exception_handler
SAVE ctx → vTaskSwitchContext() 选出任务 B
RESTORE ctx → mret → 任务 B 继续执行
当 CPU 空闲进入 Idle → IdleHook 软算 Tick,如需切换重复 1-4。

异常 & 中断入口(vectored)
mtvec 已设置为 freertos_vector_table|1,因此:
异常(ecall、非法指令…)
cause[63]=0 ⇒ 同步,PC 跳到 BASE(表首地址,对应 vector.S:IRQ_0)→j freertos_risc_v_exception_handler.
中断(Software/MachineTimer/External)
cause[63]=1 ⇒ 异步,PC = BASE + 4×IRQn,vector.S 已为每个槽写:
j freertos_risc_v_interrupt_handler // 一般外设
j freertos_risc_v_mtimer_interrupt_handler // IRQ7启动与第一条任务
start.S(build/gcc/start.S)
reset_vector → _start:初始化栈指针、BSS,随后 jal main。
此时 CPU 位于 M-mode,mstatus.MIE=0(关全局中断)。
main.c
mtvec ← freertos_vector_table | 0x1 → 进入“Machine-vectored”模式。
调用 main_blinky() 创建 2 任务 + 1 队列;随后 vTaskStartScheduler()。
vTaskStartScheduler()(tasks.c)
为 Idle/Timer 任务分配(静态)TCB。
调用 xPortStartScheduler() → 进一步跳至汇编 xPortStartFirstTask。
xPortStartFirstTask(portASM.S)
从 pxCurrentTCB 取首任务栈顶,执行 portcontextRESTORE_CONTEXT:
把 31 个通用寄存器、mstatus、mepc 等一并从任务栈弹出。
mret → CPU 直接跳到该任务入口,开始多任务世界。
简单任务切换
软件 Tick(IdleHook)
FreeRTOSConfig.h 把 configMTIME_BASE_ADDRESS 设 0 ⇒ 禁用硬件定时器,vApplicationIdleHook() 里软件读取 mcycle,达到 25 000 周期就:
if( xTaskIncrementTick() ) taskYIELD();相当于 1 kHz “软时钟”。若新任务就绪则调用 taskYIELD()(宏为 ecall)。
显示让出(portYIELD / vTaskDelay 等)
portmacro.h:
#define portYIELD() __asm volatile("ecall");任何任务调用阻塞 API 最终执行 ecall 或 portYIELD_WITHIN_API(),进入同步异常流程。
(若你启用 MTIME,中断则从 vector.S 的 IRQ_7 跳转 freertos_risc_v_mtimer_interrupt_handler,流程与下文异步中断相同,只是入口不同。)
上下文保护
portContext.h 里宏族
portcontextSAVE_INTERRUPT_CONTEXT
portcontextSAVE_EXCEPTION_CONTEXT
portcontextRESTORE_CONTEXT主要做三件事:a) 把 31 个 GPR + mstatus + mepc + 额外寄存器保存到 当前任务栈;b) 把 sp 切换到 ISR 专用栈 xISRStackTop(防止嵌套打穿任务栈);c) 退出前反向弹栈 + mret 恢复 mepc/mstatus。
差异点
异常宏 会把 mepc+4 存回(确保返回到触发指令之后)。
中断宏 直接保存原 mepc;软中断/硬中断都要“回到被打断的指令”。
调度路径
【同步异常:ecall / portYIELD】
vector.S → freertos_risc_v_exception_handler
① portcontextSAVE_EXCEPTION_CONTEXT
② 判断 mcause==11?(M-mode ecall)
是:call vTaskSwitchContext(C 级调度算法)
否:转 freertos_risc_v_application_exception_handler
③ portcontextRESTORE_CONTEXT → 新任务 mret
【异步中断:MachineTimer / 外设】
vector.S → freertos_risc_v_mtimer_interrupt_handler / freertos_risc_v_interrupt_handler
① portcontextSAVE_INTERRUPT_CONTEXT
② (如果是 MTIMER) portUPDATE_MTIMER_COMPARE_REGISTER 更新下一 Tick
call xTaskIncrementTick
若返回 pdTRUE → call vTaskSwitchContext
③ portcontextRESTORE_CONTEXT → 新/原任务 mret
ECALL和MRET
ecall —— 主动触发一次「同步异常」
1) 指令功能
RISC-V 定义 ecall 为 Environment Call。
执行时不会继续走下一条指令,而是:
mcause ← 11(代表 Environment Call from M-mode)mepc ← 触发 ecall 的地址PC ← mtvec(向量基址)或向量槽2) 在 FreeRTOS 中的用途
portmacro.h: #define portYIELD() __asm volatile("ecall")
任何需要“自愿让出 CPU”的 API(vTaskDelay() / taskYIELD() 等)最终都会执行 portYIELD(),于是进入同步异常流程:
ecall
└→ freertos_risc_v_exception_handler
├─ portcontextSAVE_EXCEPTION_CONTEXT // 把当前任务寄存器压栈
├─ if (mcause==11) vTaskSwitchContext(); // C 级调度,挑选下一任务
└─ portcontextRESTORE_CONTEXT → mret
通过这种方式,不依赖硬件中断也能完成上下文切换。3) 为什么不用 SRET?
整个系统运行在 M-mode(特权最高),因此所有陷入都走 mret 系列;没有 S-mode/U-mode 就无需 sret/uret。mret —— 从陷入返回、恢复上下文
1) 指令功能
Machine RETurn,从异常/中断处理器返回。
取出 mstatus.MPIE → MIE,恢复中断使能位;
PC ← mepc(异常入口保存的返回地址);
同时恢复 mstatus.MPP 等字段,可完成特权级切换。
2) 在 FreeRTOS 中的具体流程a) portcontextRESTORE_CONTEXT 最后一步执行 mret。b) 在执行前,汇编已把 新任务 的寄存器、mstatus、mepc 从该任务栈弹出:
... lw ra, (sp) // etc.
... lw mepc, offset(sp)
... lw mstatus, offset(sp)
addi sp, sp, portCONTEXT_SIZE
mret // ← 关键一步
c) mret 使 CPU 跳到新任务的 PC(mepc),并恢复 mstatus,因此完成了上下文切换。旧任务的寄存器仍留在它自己的栈顶,等待下次被恢复。3) 在中断场景的作用
若启用硬件 MTIMER,freertos_risc_v_mtimer_interrupt_handler 在增量比较寄存器、调用 xTaskIncrementTick() 后,也会通过 mret 返回(有可能切到别的任务)。