自定义RTOS内核:从零实现上下文切换与任务调度——汇编、PendSV

文章目录


每日一句正能量

真正的善,是内心有尺,眼中有度,是知进退、懂取舍的清醒。

有尺→知道自己底线在哪;有度→知道对什么人、什么事该给几分。知进退→不盲目冲锋,也不冷漠旁观;懂取舍→不贪求"全善",只做有效的好。善不再是柔软,而是精准的平衡。

摘要

摘要:理解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内核,核心在于理解三个机制:

  1. 上下文切换:通过PendSV异常,保存当前任务寄存器(R4-R11 + PSP),恢复新任务寄存器
  2. 双栈设计:MSP用于中断,PSP用于任务,通过EXC_RETURN(0xFFFFFFFD)自动切换
  3. 调度触发:SysTick定期检查是否需要切换,实际切换延迟到PendSV执行

掌握这些原理后,阅读FreeRTOS、RT-Thread等成熟RTOS的源码将变得清晰明了。更重要的是,当遇到移植问题或性能优化需求时,你将具备深入分析和解决的能力。


转载自:https://blog.csdn.net/u014727709/article/details/162496451

欢迎 👍点赞✍评论⭐收藏,欢迎指正

相关推荐
hai3152475431 小时前
九章空间几何对照直译法:通用芯片 / 定制专用芯片 编译适配规则
linux·汇编·bash
geovindu1 小时前
java:Abstract Factory Pattern
java·开发语言·后端·设计模式·抽象工厂模式·创建型模式
geovindu1 小时前
java: Factory Method Pattern
java·开发语言·后端·设计模式·工厂方法模式·创建型模式
程序员小八7772 小时前
如何自研一个全局异常处理器
java
风中的小甜瓜2 小时前
ASP.NET Page 那点事
java·前端·asp.net
m0_587098992 小时前
Qt,二进制文件读写建议
开发语言·qt
爱敲代码的小鱼2 小时前
springsecurity:
java·开发语言·数据库
豆沙小饼干2 小时前
MySQL 小白循序渐进实操路线(纯动手,跟着敲就能练,全程生活化案例:外卖平台)
java
秋田君2 小时前
QT_QT信号与槽机制
开发语言·数据库·qt