FreeRTOS ------ prvStartFirstTask 和 vPortSVCHandler
FreeRTOS 第一次启动任务切换前,需要调用函数 vPortStartFirstTask,初始化 MSP,然后触发 SVC 的中断函数 vPortSVCHandler,中断函数执行出栈后就跳转到第一个任务的入口函数。
之后任务切换通过中断函数 xPortPendSVHandler,此函数会执行入栈、选择下一个要运行的任务、出栈。
vPortStartFirstTask /* Use the NVIC offset register to locate the stack. */
// cortex-M3 硬件中,0xE000ED08 地址处为VTOR(向量表偏移量)寄存器,存储向量表起始地址
ldr r0, =0xE000ED08 //将 0xE000ED08 加载到 R0 ldr r0, [r0] //将 0xE000ED08 地址中的值,也就是向量表的实际地址加载到 R0 ldr r0, [r0] //根据向量表实际存储地址,取出向量表中的第一项,向量表第一项存储主堆栈指针 MSP 的初始值 /* Set the msp back to the start of the stack. */ msr msp, r0 //将 MSP 的初始值写入 MSP 中 /* Call SVC to start the first task, ensuring interrupts are enabled. */ cpsie i cpsie f dsb isb svc 0 //调用 SVC 启动第一个任务 END
PRESERVE8 用于 8 字节对齐;
取 MSP 的初始值的思路是先根据向量表的位置寄存器 VTOR (0xE000ED08) 来获取向量表存储的地址;再根据向量表存储的地址,来访问第一个元素,也就是初始的 MSP;
Cortex-M3 处理器,上电默认进入线程的特权模式,使用 MSP 作为栈指针,从上电跑到这里,经过一系列的函数调用,出栈,入栈,MSP 自然已经不是最开始的初始化的位置,这里通过 MSR 重新初始化 MSP,岂不是栈内容都没了么?是的,因为这是一条不归路,代码跑到这里,首先不会返回,之前压栈的内容再也不会用到,所以破坏之前的堆栈也没关系。
调用 svc 并传入系统调用号为 0 手动拉 SVC 中断
vPortSVCHandler: /* Get the location of the current TCB. */ ldr r3, =pxCurrentTCB ldr r1, [r3] ldr r0, [r1] //获取 TCB 的第一个成员,即当前任务堆栈栈顶 pxTopOfstack /* Pop the core registers. */ ldmia r0!, {r4-r11} msr psp, r0 isb mov r0, #0 msr basepri, r0 orr r14, r14, #13 // 只有这个函数才需要这句话,xPortPendSVHandler中断函数不需要;vPortSVCHandler中断函数里的r14是进入中断前处理器根据之前的运行环境更新的
// 进入前使用的是MSP,handler模式,而进入xPortPendSVHandler前使用的是PSP,线程模式,所以不需要修改
bx r14
pxCurrentTCB 指向的是最高优先级的 Ready 状态的任务指针;根据 pxCurrentTCB 获取到对应 TCB 的地址;然后获取第一个成员变量,也就是当前栈顶地址 pxTopOfStack;这个值在任务分配的时候,就已经计算好,并且模拟的 Cortex-M3 的异常入栈顺序,手动入栈了;
使用 LDMIA 指令,以 pxTopOfStack 开始顺序出栈,先出 R4~R11(在创建任务的时候,最后入栈的就是这些个),同时 R0 递增;
将此刻的 R0 赋值给 PSP(因为出栈的时候,处理器会按照入栈的顺序去取 xPSR、PC、LR、R12、R3、R2、R1、R0,而这些寄存器在我们创建任务的时候已经手动压栈)
将 BASEPRI 寄存器赋值为 0,也就是允许任何中断
ORR 指令是按位或,所以 ORR R14, #0xd 相当于 R14 |= 0xd;这个操作也和体系架构相关,R14 是链接寄存器 LR,在 ISR 中(此刻我们在 SVC 的 ISR 中),它记录了异常返回值 EXC_RETURN
因为当前在 ISR 中还是使用的 MSP,启动任务后,我们期望在任务执行过程中,处于线程模式,并使用 PSP(前面几行已经给 PSP 赋值了),所以我们需要将 LR 设计成为 0xFFFF_FFFD,让处理器知道返回的时候呢,使用线程模式+PSP堆栈;
最后执行 bx R14,告诉处理器 ISR 完成,需要返回,此刻处理器便会使用 PSP 做为栈指针,进行出栈操作,将xPSR、PC、LR、R12、R3~R0 出栈,初始化的时候,PC 被我们赋值成为了执行任务的函数的入口,所以呢,就正常跳入到了优先级最高的 Ready 状态的第一个任务的入口函数了;
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· 写一个简单的SQL生成工具
· AI 智能体引爆开源社区「GitHub 热点速览」
· C#/.NET/.NET Core技术前沿周刊 | 第 29 期(2025年3.1-3.9)
2018-06-06 枚举 enum 成员变量初始化
2017-06-06 学习计划
2017-06-06 STM8L - 低功耗编程注意
2017-06-06 过去式后面的ed读法的区别