文章目录
-
- 每日一句正能量
- 摘要
- 一、引言:为什么要从零实现RTOS?
- [二、ARM Cortex-M 上下文切换原理](#二、ARM Cortex-M 上下文切换原理)
-
- [2.1 Cortex-M异常模型与双栈设计](#2.1 Cortex-M异常模型与双栈设计)
- [2.2 异常返回机制(EXC_RETURN)](#2.2 异常返回机制(EXC_RETURN))
- 三、PendSV:专为上下文切换设计的异常
-
- [3.1 为什么需要PendSV?](#3.1 为什么需要PendSV?)
- [3.2 PendSV的触发与执行](#3.2 PendSV的触发与执行)
- 四、任务控制块(TCB)与任务栈
-
- [4.1 TCB数据结构](#4.1 TCB数据结构)
- [4.2 任务栈初始化](#4.2 任务栈初始化)
- 五、上下文切换汇编实现
-
- [5.1 完整的PendSV_Handler](#5.1 完整的PendSV_Handler)
- [5.2 关键汇编指令解析](#5.2 关键汇编指令解析)
- 六、任务调度器实现
-
- [6.1 任务状态机](#6.1 任务状态机)
- [6.2 调度器核心代码](#6.2 调度器核心代码)
- 七、SysTick与调度触发
-
- [7.1 SysTick配置](#7.1 SysTick配置)
- 八、完整的最小RTOS示例
-
- [8.1 项目结构](#8.1 项目结构)
- [8.2 应用代码示例](#8.2 应用代码示例)
- [8.3 调试输出](#8.3 调试输出)
- 九、常见问题与调试技巧
-
- [9.1 上下文切换后HardFault](#9.1 上下文切换后HardFault)
- [9.2 任务不切换](#9.2 任务不切换)
- [9.3 栈溢出检测](#9.3 栈溢出检测)
- 十、总结

每日一句正能量
真正的善,是内心有尺,眼中有度,是知进退、懂取舍的清醒。
有尺→知道自己底线在哪;有度→知道对什么人、什么事该给几分。知进退→不盲目冲锋,也不冷漠旁观;懂取舍→不贪求"全善",只做有效的好。善不再是柔软,而是精准的平衡。
摘要
摘要:理解RTOS内核的最佳方式是亲手实现一个。本文从零开始,基于ARM Cortex-M架构,深入讲解上下文切换的汇编实现、PendSV异常的设计原理、任务控制块(TCB)的数据结构,以及抢占式调度器的完整实现。通过详细的代码注释和架构图解,帮助读者真正理解RTOS内核的工作机制。
一、引言:为什么要从零实现RTOS?
在使用FreeRTOS、RT-Thread等成熟RTOS时,开发者往往将其视为"黑盒"------知道如何调用API,却不理解底层实现。当遇到以下问题时,这种"知其然不知其所以然"的状态就会成为瓶颈:
- 上下文切换异常:任务切换后HardFault,无法定位问题
- 栈溢出调试:不知道栈边界在哪里,如何检测
- 中断延迟优化:不理解中断嵌套与调度延迟的关系
- 移植到新架构:需要为新的CPU架构编写移植层
从零实现一个最小RTOS内核,是打通这些瓶颈的最佳途径。本文将基于ARM Cortex-M3/M4架构,实现一个支持抢占式调度的微型RTOS。
二、ARM Cortex-M 上下文切换原理
2.1 Cortex-M异常模型与双栈设计
ARM Cortex-M处理器有两个栈指针:
- MSP(Main Stack Pointer):用于中断和异常处理
- PSP(Process Stack Pointer):用于任务(Thread模式)
当发生异常时,处理器自动从PSP切换到MSP,这为多任务操作系统提供了天然支持。

上图展示了上下文切换的完整过程:任务A运行时,其寄存器上下文保存在自己的栈中;切换时,硬件自动保存xPSR、PC、LR、R12、R3-R0,软件手动保存R11-R4;然后从任务B的TCB中恢复栈指针,逆向操作恢复所有寄存器。
2.2 异常返回机制(EXC_RETURN)
Cortex-M使用特殊的LR值(EXC_RETURN)控制异常返回行为:
| LR值 | 含义 |
|---|---|
| 0xFFFFFFF1 | 返回Handler模式,使用MSP |
| 0xFFFFFFF9 | 返回Thread模式,使用MSP |
| 0xFFFFFFFD | 返回Thread模式,使用PSP |
RTOS任务使用0xFFFFFFFD返回Thread模式并使用PSP,这是任务切换的关键。
三、PendSV:专为上下文切换设计的异常
3.1 为什么需要PendSV?
PendSV(Pendable Service Call)是Cortex-M中一个特殊的可挂起异常,其设计目标就是上下文切换。

上图解释了PendSV的设计原理:它是优先级最低的可配置异常,可以被所有其他中断延迟执行。这确保了上下文切换不会打断关键的中断服务程序。
关键设计:
- SysTick是周期性中断,不能用于异步任务切换
- SVC是同步异常,优先级固定,不够灵活
- PendSV是可挂起中断,可被更高优先级中断延迟
- 在所有其他中断完成后执行,避免上下文切换打断关键ISR
3.2 PendSV的触发与执行
c
/* 1. 配置PendSV为最低优先级 */
NVIC_SetPriority(PendSV_IRQn, 0xFF); /* 最低优先级 */
/* 2. 在SysTick或任务yield中挂起PendSV */
SCB->ICSR |= SCB_ICSR_PENDSVSET_Msk; /* 写PENDSVSET位 */
/* 3. PendSV Handler执行上下文切换 */
void PendSV_Handler(void);
四、任务控制块(TCB)与任务栈
4.1 TCB数据结构

上图展示了TCB结构和任务栈布局。TCB是调度器管理任务的核心数据结构,包含栈指针、优先级、状态列表项等关键字段。
c
/**
* @file tcb.h
* @brief 任务控制块定义
*/
#ifndef TCB_H
#define TCB_H
#include <stdint.h>
/* 任务状态 */
typedef enum {
TASK_READY = 0, /* 就绪态 */
TASK_RUNNING, /* 运行态 */
TASK_BLOCKED, /* 阻塞态 */
TASK_SUSPENDED, /* 挂起态 */
TASK_DELETED /* 删除态 */
} TaskState_t;
/* 列表项(用于就绪队列和阻塞队列) */
typedef struct xLIST_ITEM {
volatile uint32_t xItemValue; /* 排序值(优先级或超时时间) */
struct xLIST_ITEM *pxNext; /* 下一个 */
struct xLIST_ITEM *pxPrevious; /* 上一个 */
void *pvOwner; /* 所属TCB */
void *pvContainer; /* 所在列表 */
} ListItem_t;
/* 任务控制块 */
typedef struct tskTaskControlBlock {
volatile uint32_t *pxTopOfStack; /* 当前栈顶指针 */
uint32_t *pxStack; /* 栈底地址 */
uint32_t uxPriority; /* 当前优先级 */
uint32_t uxBasePriority; /* 基础优先级(用于优先级继承) */
uint32_t uxStackDepth; /* 栈深度(字) */
char pcTaskName[16]; /* 任务名称 */
/* 列表项 */
ListItem_t xStateListItem; /* 状态列表项 */
ListItem_t xEventListItem; /* 事件列表项 */
/* 阻塞相关 */
uint32_t xTicksToDelay; /* 延时计数 */
} TCB_t;
/* 就绪列表 */
typedef struct xLIST {
volatile uint32_t uxNumberOfItems;
ListItem_t *pxIndex;
ListItem_t xListEnd; /* 哨兵节点 */
} List_t;
#endif /* TCB_H */
4.2 任务栈初始化
c
/**
* @brief 初始化任务栈,模拟异常返回帧
*
* Cortex-M进入异常时,硬件自动保存的寄存器(从高到低):
* xPSR, PC, LR, R12, R3, R2, R1, R0
*
* 软件需要手动保存的寄存器:
* R11, R10, R9, R8, R7, R6, R5, R4
*/
uint32_t *pxPortInitialiseStack(uint32_t *pxTopOfStack,
TaskFunction_t pxCode,
void *pvParameters)
{
/* 栈必须8字节对齐 */
pxTopOfStack--;
/* 模拟xPSR寄存器,Thumb模式位必须置1 */
*pxTopOfStack = 0x01000000UL; /* xPSR: T-bit set */
pxTopOfStack--;
/* 任务入口地址 */
*pxTopOfStack = (uint32_t)pxCode; /* PC */
pxTopOfStack--;
/* 链接寄存器:任务不应返回,但设置异常返回值 */
*pxTopOfStack = 0xFFFFFFFDUL; /* LR: EXC_RETURN using PSP */
pxTopOfStack--;
/* R12 */
*pxTopOfStack = 0x00000000UL;
pxTopOfStack--;
/* R3, R2, R1 */
*pxTopOfStack = 0x00000000UL; /* R3 */
pxTopOfStack--;
*pxTopOfStack = 0x00000000UL; /* R2 */
pxTopOfStack--;
*pxTopOfStack = 0x00000000UL; /* R1 */
pxTopOfStack--;
/* R0 = 任务参数 */
*pxTopOfStack = (uint32_t)pvParameters; /* R0 */
pxTopOfStack--;
/* R11-R4 初始化为0 */
*pxTopOfStack = 0x00000000UL; /* R11 */
pxTopOfStack--;
*pxTopOfStack = 0x00000000UL; /* R10 */
pxTopOfStack--;
*pxTopOfStack = 0x00000000UL; /* R9 */
pxTopOfStack--;
*pxTopOfStack = 0x00000000UL; /* R8 */
pxTopOfStack--;
*pxTopOfStack = 0x00000000UL; /* R7 */
pxTopOfStack--;
*pxTopOfStack = 0x00000000UL; /* R6 */
pxTopOfStack--;
*pxTopOfStack = 0x00000000UL; /* R5 */
pxTopOfStack--;
*pxTopOfStack = 0x00000000UL; /* R4 */
return pxTopOfStack;
}
五、上下文切换汇编实现
5.1 完整的PendSV_Handler

上图展示了PendSV_Handler的完整汇编实现流程,分为三个步骤:保存当前任务上下文、选择新任务、恢复新任务上下文。
asm
; @file port_asm.s
; @brief ARM Cortex-M3/M4 上下文切换汇编实现
AREA |.text|, CODE, READONLY
THUMB
; 外部符号
EXTERN pxCurrentTCB
EXTERN vTaskSwitchContext
; PendSV Handler - 上下文切换核心
PendSV_Handler PROC
EXPORT PendSV_Handler
; Step 1: 保存当前任务的上下文
; ---------------------------------------------
; 获取当前任务的PSP(进程栈指针)
MRS R0, PSP
; 保存R4-R11到任务栈(软件保存)
; STMDB = Store Multiple, Decrement Before
; R0! = 先递减R0,再存储,更新R0
STMDB R0!, {R4-R11}
; 禁用中断,防止调度器状态不一致
CPSID I
; 将新的栈顶保存到当前TCB
; pxCurrentTCB->pxTopOfStack = R0
LDR R3, =pxCurrentTCB ; R3 = &pxCurrentTCB
LDR R2, [R3] ; R2 = pxCurrentTCB
STR R0, [R2, #0] ; TCB偏移0 = pxTopOfStack
; 保存LR(包含EXC_RETURN值)
MOV R4, LR
; Step 2: 调用调度器选择新任务
; ---------------------------------------------
BL vTaskSwitchContext
; 恢复LR
MOV LR, R4
; Step 3: 恢复新任务的上下文
; ---------------------------------------------
; 从新TCB获取栈顶
LDR R3, =pxCurrentTCB
LDR R2, [R3]
LDR R0, [R2, #0] ; R0 = pxTopOfStack
; 恢复R4-R11(软件恢复)
; LDMIA = Load Multiple, Increment After
; R0! = 先加载,再递增R0,更新R0
LDMIA R0!, {R4-R11}
; 更新PSP为新任务的栈顶
MSR PSP, R0
; 启用中断
CPSIE I
; 异常返回
; LR包含0xFFFFFFFD,表示使用PSP返回Thread模式
BX LR
ENDP
; SVC Handler - 用于启动第一个任务
SVC_Handler PROC
EXPORT SVC_Handler
; 获取当前任务的栈顶
LDR R3, =pxCurrentTCB
LDR R1, [R3]
LDR R0, [R1, #0] ; R0 = pxTopOfStack
; 恢复R4-R11
LDMIA R0!, {R4-R11}
; 更新PSP
MSR PSP, R0
; 启用中断
CPSIE I
; 异常返回,使用PSP
BX LR
ENDP
; 启动第一个任务
vPortStartFirstTask PROC
EXPORT vPortStartFirstTask
; 使用SVC启动第一个任务
; SVC 0 会触发SVC_Handler
SVC 0
ENDP
; 触发PendSV进行上下文切换
vPortYield PROC
EXPORT vPortYield
; 设置PendSV挂起位
LDR R0, =0xE000ED04 ; SCB->ICSR地址
LDR R1, =0x10000000 ; PENDSVSET位
STR R1, [R0]
; 数据同步屏障,确保写入完成
DSB
; 指令同步屏障,确保后续指令使用新状态
ISB
BX LR
ENDP
ALIGN
END
5.2 关键汇编指令解析
| 指令 | 作用 | 说明 |
|---|---|---|
MRS R0, PSP |
读取PSP到R0 | 获取当前任务栈指针 |
STMDB R0!, {R4-R11} |
存储多个寄存器 | 先递减R0,再存储,更新R0 |
LDMIA R0!, {R4-R11} |
加载多个寄存器 | 先加载,再递增R0,更新R0 |
MSR PSP, R0 |
写入PSP | 设置新任务的栈指针 |
CPSID I |
关闭中断 | 临界区保护 |
CPSIE I |
开启中断 | 退出临界区 |
DSB |
数据同步屏障 | 确保内存写入完成 |
ISB |
指令同步屏障 | 刷新指令流水线 |
六、任务调度器实现
6.1 任务状态机

上图展示了RTOS的任务状态机。任务可在就绪、运行、阻塞、挂起、删除五种状态间转换,调度器负责选择最高优先级的就绪任务执行。
6.2 调度器核心代码
c
/**
* @file scheduler.c
* @brief 抢占式优先级调度器
*/
#include \"tcb.h\"
/* 全局变量 */
TCB_t * volatile pxCurrentTCB = NULL;
static List_t pxReadyTasksLists[configMAX_PRIORITIES];
static volatile uint32_t uxTopReadyPriority = 0;
static volatile uint32_t xTickCount = 0;
/* 阻塞任务列表(按超时时间排序) */
static List_t xDelayedTaskList1;
static List_t xDelayedTaskList2;
static List_t * volatile pxDelayedTaskList;
static List_t * volatile pxOverflowDelayedTaskList;
/**
* @brief 初始化调度器
*/
void vTaskStartScheduler(void)
{
/* 创建空闲任务 */
xReturn = xTaskCreate(prvIdleTask, \"IDLE\", configMINIMAL_STACK_SIZE,
NULL, tskIDLE_PRIORITY, &xIdleTaskHandle);
/* 初始化Tick计数 */
xTickCount = 0;
/* 配置SysTick */
portCONFIGURE_TIMER_FOR_RUN_TIME_STATS();
/* 启动第一个任务(触发SVC异常) */
vPortStartFirstScheduler();
/* 不应该到达这里 */
for (;;);
}
/**
* @brief 调度器核心:选择最高优先级的就绪任务
*/
void vTaskSwitchContext(void)
{
/* 检查是否有任务需要解除阻塞 */
prvCheckDelayedTasks();
/* 找到最高优先级的就绪任务 */
while (listLIST_IS_EMPTY(&(pxReadyTasksLists[uxTopReadyPriority]))) {
configASSERT(uxTopReadyPriority);
uxTopReadyPriority--;
}
/* 获取列表中的第一个任务 */
pxCurrentTCB = (TCB_t *)listGET_OWNER_OF_HEAD_ENTRY(
&(pxReadyTasksLists[uxTopReadyPriority]));
}
/**
* @brief 检查阻塞任务是否超时
*/
static void prvCheckDelayedTasks(void)
{
TCB_t *pxTCB;
/* 检查Tick是否溢出 */
if (xTickCount == 0) {
/* 交换延迟列表 */
List_t *pxTemp = pxDelayedTaskList;
pxDelayedTaskList = pxOverflowDelayedTaskList;
pxOverflowDelayedTaskList = pxTemp;
}
/* 检查阻塞列表头部的任务 */
while (listLIST_IS_EMPTY(pxDelayedTaskList) == pdFALSE) {
pxTCB = (TCB_t *)listGET_OWNER_OF_HEAD_ENTRY(pxDelayedTaskList);
if (xTickCount < pxTCB->xTicksToDelay) {
break; /* 还未超时 */
}
/* 将任务从阻塞列表移到就绪列表 */
uxListRemove(&(pxTCB->xStateListItem));
prvAddTaskToReadyList(pxTCB);
}
}
/**
* @brief 将任务添加到就绪列表
*/
#define prvAddTaskToReadyList(pxTCB) \
do { \
vListInsertEnd(&(pxReadyTasksLists[(pxTCB)->uxPriority]), \
&((pxTCB)->xStateListItem)); \
if ((pxTCB)->uxPriority > uxTopReadyPriority) { \
uxTopReadyPriority = (pxTCB)->uxPriority; \
} \
} while (0)
/**
* @brief 任务延时
*/
void vTaskDelay(const uint32_t xTicksToDelay)
{
uint32_t xAlreadyYielded = pdFALSE;
if (xTicksToDelay > 0) {
/* 禁用中断 */
uint32_t uxSavedInterruptStatus = portSET_INTERRUPT_MASK_FROM_ISR();
/* 将任务从就绪列表移除 */
if (uxListRemove(&(pxCurrentTCB->xStateListItem)) == 0) {
/* 如果就绪列表为空,更新最高优先级 */
portRESET_READY_PRIORITY(pxCurrentTCB->uxPriority, uxTopReadyPriority);
}
/* 设置延时时间 */
pxCurrentTCB->xTicksToDelay = xTickCount + xTicksToDelay;
/* 添加到阻塞列表 */
vListInsert(pxDelayedTaskList, &(pxCurrentTCB->xStateListItem));
/* 恢复中断 */
portCLEAR_INTERRUPT_MASK_FROM_ISR(uxSavedInterruptStatus);
}
/* 如果调度器已启动,进行上下文切换 */
if (xSchedulerRunning != pdFALSE) {
if (xAlreadyYielded == pdFALSE) {
portYIELD();
}
}
}
/**
* @brief SysTick中断处理
*/
void SysTick_Handler(void)
{
uint32_t uxSavedInterruptStatus;
/* 递增Tick计数 */
uxSavedInterruptStatus = portSET_INTERRUPT_MASK_FROM_ISR();
xTickCount++;
portCLEAR_INTERRUPT_MASK_FROM_ISR(uxSavedInterruptStatus);
/* 检查是否需要上下文切换 */
if (xTaskIncrementTick() != pdFALSE) {
/* 挂起PendSV */
portNVIC_INT_CTRL_REG = portNVIC_PENDSVSET_BIT;
}
}
/**
* @brief 递增Tick,检查是否需要调度
*/
BaseType_t xTaskIncrementTick(void)
{
BaseType_t xSwitchRequired = pdFALSE;
TCB_t *pxTCB;
TickType_t xItemValue;
/* 检查阻塞任务 */
if (uxTopReadyPriority > pxCurrentTCB->uxPriority) {
xSwitchRequired = pdTRUE;
}
/* 检查时间片轮转 */
#if (configUSE_TIME_SLICING == 1)
{
if (listCURRENT_LIST_LENGTH(&(pxReadyTasksLists[pxCurrentTCB->uxPriority])) > 1) {
xSwitchRequired = pdTRUE;
}
}
#endif
return xSwitchRequired;
}
七、SysTick与调度触发
7.1 SysTick配置

上图展示了SysTick配置和调度触发机制。SysTick作为系统心跳,定期触发调度检查;实际上下文切换延迟到PendSV执行,确保所有中断处理完毕。
c
/**
* @brief 配置SysTick定时器
* @param ticks 两次中断间的Tick数
*/
uint32_t SysTick_Config(uint32_t ticks)
{
/* 检查是否溢出 */
if ((ticks - 1) > SysTick_LOAD_RELOAD_Msk) {
return 1; /* 重装载值无效 */
}
/* 设置重装载值 */
SysTick->LOAD = ticks - 1;
/* 设置中断优先级为最低 */
NVIC_SetPriority(SysTick_IRQn, (1 << __NVIC_PRIO_BITS) - 1);
/* 设置当前计数值为0 */
SysTick->VAL = 0;
/* 使能SysTick + 使能中断 + 使用处理器时钟 */
SysTick->CTRL = SysTick_CTRL_CLKSOURCE_Msk |
SysTick_CTRL_TICKINT_Msk |
SysTick_CTRL_ENABLE_Msk;
return 0;
}
/**
* @brief 启动调度器时配置SysTick
*/
void vPortSetupTimerInterrupt(void)
{
/* 计算1ms需要的Tick数 */
uint32_t ulReloadValue = configCPU_CLOCK_HZ / configTICK_RATE_HZ;
/* 配置SysTick */
SysTick_Config(ulReloadValue);
/* 配置PendSV为最低优先级 */
NVIC_SetPriority(PendSV_IRQn, configKERNEL_INTERRUPT_PRIORITY);
}
八、完整的最小RTOS示例
8.1 项目结构
text
mini_rtos/
├── include/
│ ├── tcb.h # 任务控制块
│ ├── list.h # 列表管理
│ ├── portmacro.h # 移植层宏
│ └── mini_rtos.h # 主头文件
├── port/
│ ├── port_asm.s # 汇编上下文切换
│ └── port.c # C移植层
├── kernel/
│ ├── scheduler.c # 调度器
│ ├── tasks.c # 任务管理
│ └── list.c # 列表实现
└── main.c # 应用入口
8.2 应用代码示例
c
/**
* @file main.c
* @brief 最小RTOS演示
*/
#include \"mini_rtos.h\"
/* 任务栈 */
static uint32_t task1_stack[256];
static uint32_t task2_stack[256];
static uint32_t task3_stack[256];
/* 任务句柄 */
static TaskHandle_t xTask1Handle;
static TaskHandle_t xTask2Handle;
static TaskHandle_t xTask3Handle;
/**
* @brief 任务1:高优先级,LED闪烁
*/
void vTask1(void *pvParameters)
{
(void)pvParameters;
while (1) {
/* LED ON */
GPIO_SetBits(GPIOC, GPIO_Pin_13);
vTaskDelay(pdMS_TO_TICKS(500));
/* LED OFF */
GPIO_ResetBits(GPIOC, GPIO_Pin_13);
vTaskDelay(pdMS_TO_TICKS(500));
}
}
/**
* @brief 任务2:中优先级,串口输出
*/
void vTask2(void *pvParameters)
{
(void)pvParameters;
uint32_t count = 0;
while (1) {
printf(\"Task2: count = %lu\\r\\n\", count++);
vTaskDelay(pdMS_TO_TICKS(1000));
}
}
/**
* @brief 任务3:低优先级,后台计算
*/
void vTask3(void *pvParameters)
{
(void)pvParameters;
uint32_t sum = 0;
while (1) {
/* 模拟耗时计算 */
for (int i = 0; i < 1000000; i++) {
sum += i;
}
printf(\"Task3: sum = %lu\\r\\n\", sum);
/* 主动让出CPU */
taskYIELD();
}
}
int main(void)
{
/* 硬件初始化 */
SystemInit();
GPIO_Config();
UART_Config();
printf(\"MiniRTOS Starting...\\r\\n\");
/* 创建任务 */
xTaskCreate(vTask1, \"LED\", 256, NULL, 3, &xTask1Handle);
xTaskCreate(vTask2, \"UART\", 256, NULL, 2, &xTask2Handle);
xTaskCreate(vTask3, \"CALC\", 256, NULL, 1, &xTask3Handle);
printf(\"Tasks created. Starting scheduler...\\r\\n\");
/* 启动调度器 */
vTaskStartScheduler();
/* 不应该到达这里 */
for (;;);
}
8.3 调试输出
text
MiniRTOS Starting...
Tasks created. Starting scheduler...
Task2: count = 0
Task3: sum = 499999500000
Task2: count = 1
Task3: sum = 499999500000
Task2: count = 2
...
九、常见问题与调试技巧
9.1 上下文切换后HardFault
原因1:栈未对齐
c
/* 确保栈8字节对齐 */
pxTopOfStack = (uint32_t *)(((uint32_t)pxTopOfStack) & ~0x7UL);
原因2:EXC_RETURN值错误
c
/* 必须使用0xFFFFFFFD返回Thread模式+PSP */
*pxTopOfStack = 0xFFFFFFFDUL;
原因3:PSP未正确初始化
c
/* 首次启动任务时,PSP必须指向正确的栈位置 */
MSR PSP, R0
9.2 任务不切换
原因1:PendSV优先级设置错误
c
/* PendSV必须是最低优先级 */
NVIC_SetPriority(PendSV_IRQn, 0xFF);
原因2:SysTick未配置
c
/* 确保SysTick中断使能 */
SysTick->CTRL |= SysTick_CTRL_TICKINT_Msk;
原因3:中断被全局禁用
c
/* 检查是否有__disable_irq()未配对 */
9.3 栈溢出检测
c
/* 在栈底填充水印值 */
#define STACK_FILL_VALUE 0xA5A5A5A5
void vInitStackWatermark(uint32_t *pxStack, uint32_t ulStackDepth)
{
for (uint32_t i = 0; i < ulStackDepth; i++) {
pxStack[i] = STACK_FILL_VALUE;
}
}
/* 检查水印是否被破坏 */
uint32_t ulGetStackHighWaterMark(uint32_t *pxStack, uint32_t ulStackDepth)
{
uint32_t i = 0;
while (i < ulStackDepth && pxStack[i] == STACK_FILL_VALUE) {
i++;
}
return i; /* 返回未使用的字数 */
}
十、总结
从零实现RTOS内核,核心在于理解三个机制:
- 上下文切换:通过PendSV异常,保存当前任务寄存器(R4-R11 + PSP),恢复新任务寄存器
- 双栈设计:MSP用于中断,PSP用于任务,通过EXC_RETURN(0xFFFFFFFD)自动切换
- 调度触发:SysTick定期检查是否需要切换,实际切换延迟到PendSV执行
掌握这些原理后,阅读FreeRTOS、RT-Thread等成熟RTOS的源码将变得清晰明了。更重要的是,当遇到移植问题或性能优化需求时,你将具备深入分析和解决的能力。
转载自:https://blog.csdn.net/u014727709/article/details/162496451
欢迎 👍点赞✍评论⭐收藏,欢迎指正