RTOS线程切换和线程栈
以CortexM RTThread为例线程栈初始化线程创建函数一个线程最重要的就是线程栈函数调用、局部变量分配都需要用到线程栈。在线程创建的时候线程栈的初始化尤为重要。rt_err_trt_thread_init(structrt_thread*thread,constchar*name,void(*entry)(void*parameter),void*parameter,void*stack_start,rt_uint32_tstack_size,rt_uint8_tpriority,rt_uint32_ttick)rt_thread_init是线程创建函数其中entry、parameter、stack_start、stack_size都跟线程栈初始化相关。entry线程入口函数可以理解成跟main函数类似的函数parameter线程的参数stack_start线程栈的起始地址stack_size线程栈的大小byte线程栈初始化函数在线程栈初始化函数中可以看到是如何初始化线程栈。structexception_stack_frame{rt_uint32_tr0;rt_uint32_tr1;rt_uint32_tr2;rt_uint32_tr3;rt_uint32_tr12;rt_uint32_tlr;rt_uint32_tpc;rt_uint32_tpsr;};structstack_frame{/* r4 ~ r11 register */rt_uint32_tr4;rt_uint32_tr5;rt_uint32_tr6;rt_uint32_tr7;rt_uint32_tr8;rt_uint32_tr9;rt_uint32_tr10;rt_uint32_tr11;structexception_stack_frameexception_stack_frame;};rt_uint8_t*rt_hw_stack_init(void*tentry,void*parameter,rt_uint8_t*stack_addr,void*texit){structstack_frame*stack_frame;rt_uint8_t*stk;unsignedlongi;/* step 1 */stkstack_addrsizeof(rt_uint32_t);stk(rt_uint8_t*)RT_ALIGN_DOWN((rt_uint32_t)stk,8);stk-sizeof(structstack_frame);stack_frame(structstack_frame*)stk;/* step 2 *//* init all register */for(i0;isizeof(structstack_frame)/sizeof(rt_uint32_t);i){((rt_uint32_t*)stack_frame)[i]0xdeadbeef;}/* step 3 */stack_frame-exception_stack_frame.r0(unsignedlong)parameter;/* r0 : argument */stack_frame-exception_stack_frame.r10;/* r1 */stack_frame-exception_stack_frame.r20;/* r2 */stack_frame-exception_stack_frame.r30;/* r3 */stack_frame-exception_stack_frame.r120;/* r12 */stack_frame-exception_stack_frame.lr(unsignedlong)texit;/* lr */stack_frame-exception_stack_frame.pc(unsignedlong)tentry;/* entry point, pc */stack_frame-exception_stack_frame.psr0x01000000L;/* PSR *//* step 4 *//* return tasks current stack address */returnstk;}在初始化线程栈前把线程的入口地址、线程参数、线程退出函数和线程的栈顶地址传进去thread-sp(void*)rt_hw_stack_init(thread-entry,thread-parameter,(rt_uint8_t*)((char*)thread-stack_addrthread-stack_size-sizeof(rt_ubase_t)),(void*)_thread_exit);在rt_hw_stack_init中主要有以下 4 步给线程栈预留 64 字节的初始帧。给 64 字节的初始帧初始化为0xdeadbeef。将线程参数、线程入口函数、线程退出函数赋值给寄存器。返回初始化完的线程栈地址。初始化完成的线程栈高地址栈顶 ┌────────────────────┐ ◄── stack_addr stack_size栈顶 │ │ 对齐预留区 ├────────────────────┤ ◄── stack_frame 顶部8 字节对齐 │ psr 0x01000000L │ ┐ │ pc tentry │ │ │ lr texit │ │ exception_stack_frame │ r12 0 │ │ 硬件异常返回帧32B │ r3 0 │ │ │ r2 0 │ │ │ r1 0 │ │ │ r0 parameter │ ┘ │ r11 0xdeadbeef │ ┐ │ r10 0xdeadbeef │ │ │ r9 0xdeadbeef │ │ │ r8 0xdeadbeef │ │ r4~r11 保存区32B │ r7 0xdeadbeef │ │ 软件手动恢复 │ r6 0xdeadbeef │ │ │ r5 0xdeadbeef │ │ │ r4 0xdeadbeef │ ┘ ├────────────────────┤ ◄── thread-sp返回值 stk │ │ │ ... │ │ │ └────────────────────┘ ◄── stack_addr栈底 低地址栈底sp 以下为剩余可用栈空间向下增长几点说明栈向下增长满递减栈sp指向r4stack_frame起始地址共 64 字节。低 32 字节是r4~r11Cortex-M3 硬件异常不会自动压这些寄存器需软件手动恢复填充0xdeadbeef便于调试时发现越界/未初始化。高 32 字节是exception_stack_frame成员顺序r0~r3, r12, lr, pc, psr正好匹配 Cortex-M3 硬件异常压栈格式。首次调度软件先LDMIA r0!, {r4-r11}恢复r4~r11sp上移到r0处再异常返回由硬件恢复r0~r3, r12, lr, pc, psr最终跳转到pc tentry且r0 parameter作为入口参数。psr 0x01000000L的 bit24 是 Thumb 状态位Cortex-M3 必须置 1。线程调度时线程栈的变化rt_hw_context_switch((rt_ubase_t)from_thread-sp,(rt_ubase_t)to_thread-sp);PendSV_Handler:/* disable interrupt to protect context switch */MRS R2,PRIMASK CPSID I/* get rt_thread_switch_interrupt_flag */LDR R0,rt_thread_switch_interrupt_flag LDR R1,[R0]CBZ R1,pendsv_exit/* pendsv already handled *//* clear rt_thread_switch_interrupt_flag to 0 */MOV R1,#0STR R1,[R0]/* 线程栈保存 */LDR R0,rt_interrupt_from_thread LDR R1,[R0]CBZ R1,switch_to_thread/* skip register save at the first time */MRS R1,PSP/* get from thread stack pointer */STMFD R1!,{R4-R11}/* push R4 - R11 register */LDR R0,[R0]STR R1,[R0]/* update from thread stack pointer *//* 线程栈恢复 */switch_to_thread:LDR R1,rt_interrupt_to_thread LDR R1,[R1]LDR R1,[R1]/* load thread stack pointer */LDMFD R1!,{R4-R11}/* pop R4 - R11 register */MSR PSP,R1/* update stack pointer */pendsv_exit:/* restore interrupt */MSR PRIMASK,R2 ORR LR,LR,#0x04BX LRPendSV_Handler是进行线程切换的函数主要做了保存现场和恢复现场两步线程栈保存LDR R0,rt_interrupt_from_thread// R0 变量 rt_interrupt_from_thread 的地址LDR R1,[R0]// R1 被切换线程 sp 字段的地址from_thread-spCBZ R1,switch_to_thread// 首次切换 from 为 NULL跳过保存MRS R1,PSP// R1 当前线程栈指针 PSPSTMFD R1!,{R4-R11}// 压栈 R4-R11R1 递减LDR R0,[R0]// R0 重新取出 from_thread-spR1 已被 PSP 覆盖STR R1,[R0]// from_thread-sp R1保存压栈后的新栈顶线程栈恢复LDR R1,rt_interrupt_to_thread// R1 变量 rt_interrupt_to_thread 的地址LDR R1,[R1]// R1 目标线程 sp 字段的地址to_thread-spLDR R1,[R1]// R1 目标线程栈指针to_thread-spLDMFD R1!,{R4-R11}// 弹出 R4-R11R1 递增MSR PSP,R1// PSP R1更新线程栈指针几点说明R0、R1、R2、R3、R12、LR、PC 和 PSR 在发生异常或中断时由硬件自动保存所以软件只需要保存/恢复 R4-R11。ORR LR, LR, #0x04把异常返回值EXC_RETURN的 bit2 置 1使异常返回时使用 PSP线程栈BX LR触发异常返回后硬件自动从 PSP 弹出 R0-R3、R12、LR、PC、PSR完成现场恢复。调度与恢复时的线程栈图保存现场from 线程被切出高地址栈顶 ┌────────┐ │ ... │ ◄── 局部变量等已用栈空间 ├────────┤ ◄── 被抢占前 PSP进入 PendSV 前 │ xpsr │ ┐ │ pc │ │ │ lr │ │ │ r12 │ │ ① 硬件自动压栈 │ r3 │ │ 异常返回帧 32B │ r2 │ │ │ r1 │ │ │ r0 │ ┘ │ r11 │ ┐ │ r10 │ │ │ r9 │ │ │ r8 │ │ ② 软件压栈 │ r7 │ │ R4-R11 32B │ r6 │ │ STMFD R1!,{R4-R11} │ r5 │ │ │ r4 │ ┘ ├────────┤ ◄── PSP保存后 from_thread-sp │ │ │ │ └────────┘ 低地址栈底剩余可用栈空间向下增长恢复现场to 线程被切入高地址栈顶 ┌────────┐ │ ... │ ◄── 局部变量等已用栈空间 ├────────┤ ◄── ③ 硬件弹异常帧后 PSP 回到这里 │ xpsr │ │ pc │ │ lr │ │ r12 │ │ r3 │ │ r2 │ │ r1 │ │ r0 │ ◄── ② LDMFD 弹完 R4-R11 后 PSP 到这里 │ r11 │ │ r10 │ │ r9 │ │ r8 │ │ r7 │ │ r6 │ │ r5 │ │ r4 │ ◄── ① 恢复前 PSP to_thread-sp ├────────┤ │ │ └────────┘ 低地址栈底保存现场分两步进入 PendSV 时硬件自动压栈异常返回帧R0-R3、R12、LR、PC、xPSR随后软件STMFD R1!,{R4-R11}压 R4-R11保存后from_thread-sp指向 R4。恢复现场是保存的逆过程分三步①LDMFD R1!,{R4-R11}弹出 R4-R11PSP 上移到 R0②MSR PSP, R1③BX LR由硬件弹出异常返回帧PSP 回到被抢占前位置线程继续运行。关键点保存现场后的栈帧结构下方 R4-R11、上方异常返回帧与线程初始化时的stack_frame完全一致所以首次调度能复用同一套恢复代码。

相关新闻

Windows CMD中Python多版本切换与管理实战指南

Windows CMD中Python多版本切换与管理实战指南

1. 项目概述:为什么我们需要在Windows CMD中切换Python版本?如果你在Windows上用Python做过稍微复杂一点的项目,或者尝试过运行不同来源的代码,大概率遇到过版本冲突的烦恼。比如,你刚用pip install装好一个包&#xf…

2026/8/17 12:13:49 阅读更多 →
Grok Bot插件生态解析:从核心架构到实战开发与集成

Grok Bot插件生态解析:从核心架构到实战开发与集成

在AI助手竞争日益激烈的今天,功能单一、场景受限的聊天机器人已难以满足开发者和用户的深度需求。Grok Bot 近期宣布支持超过150款插件,这一重大更新标志着其从一个单纯的对话模型,向一个开放、可扩展的AI应用平台迈出了关键一步。对于开发者…

2026/8/16 7:27:15 阅读更多 →
双键盘远程联机方案:用Parsec与AntiMicroX实现异地同屏游戏

双键盘远程联机方案:用Parsec与AntiMicroX实现异地同屏游戏

1. 项目概述:双键盘远程联机的核心价值与挑战想和朋友一起玩本地双人游戏,但一个在城东,一个在城西,怎么办?或者,你有一台性能强劲的台式机,但朋友只有一台轻薄本,如何一起畅玩那些不…

2026/8/17 10:46:11 阅读更多 →

最新新闻

Linux软硬链接深度解析:从inode原理到实战应用

Linux软硬链接深度解析:从inode原理到实战应用

1. 项目概述:从“快捷方式”到“分身术”的底层逻辑 在Linux世界里,文件管理是每个用户和开发者绕不开的基本功。你可能已经熟练使用 ls 、 cp 、 mv 这些命令,但当你第一次在 /usr/bin 目录下看到 python 指向 python3.8 &#…

2026/8/17 14:10:55 阅读更多 →
HTML、Python、C++核心符号详解:从语法到实战避坑指南

HTML、Python、C++核心符号详解:从语法到实战避坑指南

1. 为什么你需要一份“符号”参考手册? 干了这么多年开发,我电脑里一直存着一个叫“符号速查.txt”的文件。每次新学一门语言,或者隔段时间再捡起C写点东西,总会遇到那么一两个符号,明明眼熟,但就是想不起来…

2026/8/17 14:10:55 阅读更多 →
DOS环境下使用MATS工具诊断Nvidia老显卡显存故障

DOS环境下使用MATS工具诊断Nvidia老显卡显存故障

1. 项目概述:在DOS环境下用MATS为老显卡“把脉”如果你手头还有一块像GT210、GT430、GT620这样的Nvidia老显卡,开机花屏、黑屏,或者干脆点不亮,先别急着扔。在Windows下,驱动报错、系统蓝屏,我们很难判断是…

2026/8/17 14:09:54 阅读更多 →
组蛋白乳酸化:连接细胞代谢与基因表达的新型表观修饰机制

组蛋白乳酸化:连接细胞代谢与基因表达的新型表观修饰机制

1. 从“代谢废物”到“表观新星”:乳酸化修饰的认知颠覆 如果你在五年前告诉我,细胞代谢产生的“废物”乳酸,竟然能像磷酸化、乙酰化一样,精准地调控基因表达,我大概率会一笑置之。毕竟,在经典的生物化学教…

2026/8/17 14:09:54 阅读更多 →
TTS芯片工程实践:从选型、驱动到乱码问题解决

TTS芯片工程实践:从选型、驱动到乱码问题解决

1. 从“会说话”到“说好话”:TTS芯片的工程化之旅几年前,我接手一个智能家居中控项目,需要让设备用清晰、自然的人声播报“室内温度26度”。最初图省事,用了某款MCU自带的PWM蜂鸣器模拟语音,结果出来的声音活像上世纪…

2026/8/17 14:09:54 阅读更多 →
告别蓝屏与DLL错误:详解Windows系统自带修复工具SFC与DISM

告别蓝屏与DLL错误:详解Windows系统自带修复工具SFC与DISM

1. 从一次蓝屏说起:为什么DLL文件如此重要?那天下午,我正在赶一份报告,电脑突然毫无征兆地蓝屏了,屏幕上赫然显示着“api-ms-win-crt-runtime-l1-1-0.dll丢失或损坏”。相信很多朋友都遇到过类似的场景,无论…

2026/8/17 14:09:54 阅读更多 →

日新闻

LabVIEW异步调用实战:从原理到生产者消费者模式,解决界面卡顿与并行处理难题

LabVIEW异步调用实战:从原理到生产者消费者模式,解决界面卡顿与并行处理难题

1. 项目概述:为什么异步调用是LabVIEW进阶的必修课? 如果你用LabVIEW做过稍微复杂点的项目,尤其是涉及界面响应、多任务并行或者硬件IO等待的场景,大概率遇到过这样的窘境:前面板点个按钮,整个程序就“卡死…

2026/8/17 0:00:08 阅读更多 →
LabVIEW异步调用实战:解决界面卡顿与并行处理难题

LabVIEW异步调用实战:解决界面卡顿与并行处理难题

1. 项目概述:为什么异步调用是LabVIEW进阶的必经之路如果你在LabVIEW里写过稍微复杂点的程序,尤其是涉及到界面响应、多任务并行或者硬件IO等待,大概率会遇到一个头疼的问题:程序“卡”住了。前面板点不动,进度条不更新…

2026/8/17 0:00:08 阅读更多 →
飞书局域网文件传输实战:3种方案实现高速点对点传输

飞书局域网文件传输实战:3种方案实现高速点对点传输

1. 项目概述:为什么要在局域网内用飞书传文件? 飞书作为一款主流的协同办公套件,其核心功能是围绕云端协作设计的。无论是文档、表格还是文件,通常的分享逻辑都是“上传到云端 -> 生成链接 -> 分享给同事”。这个流程在互联…

2026/8/17 0:00:08 阅读更多 →

周新闻

基于阿里云与通义千问(Qwen)构建AI应用:从模型调用到生产部署的完整实践指南

基于阿里云与通义千问(Qwen)构建AI应用:从模型调用到生产部署的完整实践指南

如果你是一名开发者,最近可能已经感受到了AI大模型正在从“玩具”变成“生产力工具”的强烈信号。从代码补全到智能Agent,从本地部署到云端API,我们正处在一个技术栈快速重构的节点。然而,面对层出不穷的模型、框架和工具&#xf…

2026/8/17 2:58:27 阅读更多 →
工业通信系统底层逻辑:04 反射——高频能量撞墙之后会发生什么?

工业通信系统底层逻辑:04 反射——高频能量撞墙之后会发生什么?

第四篇:反射——高频能量撞墙之后会发生什么? —— 你以为信号已经过去了,其实它正在回来打你 老Q的现场笔记 第五季,我们正式进入工业神经系统层。这里不再是单个设备的战斗,而是整个工厂“经脉”层面的秩序之战。从这一篇开始,你将第一次看清:看似简单的信号传播,背…

2026/8/17 2:58:30 阅读更多 →
【文章复现】非线性值迭代自适应动态规划(ADP):离散时间非线性系统的策略迭代自适应动态规划算法研究附Matlab代码

【文章复现】非线性值迭代自适应动态规划(ADP):离散时间非线性系统的策略迭代自适应动态规划算法研究附Matlab代码

✅作者简介:热爱科研的Matlab仿真开发者,擅长毕业设计辅导、数学建模、数据处理、建模仿真、程序设计、完整代码获取、论文复现及科研仿真。🍎 往期回顾关注个人主页:Matlab科研工作室👇 关注我领取海量matlab电子书和…

2026/8/17 2:58:32 阅读更多 →

月新闻

免费解锁百度网盘SVIP加速:macOS用户必备的下载提速终极指南

免费解锁百度网盘SVIP加速:macOS用户必备的下载提速终极指南

免费解锁百度网盘SVIP加速:macOS用户必备的下载提速终极指南 【免费下载链接】BaiduNetdiskPlugin-macOS For macOS.百度网盘 破解SVIP、下载速度限制~ 项目地址: https://gitcode.com/gh_mirrors/ba/BaiduNetdiskPlugin-macOS 还在为百度网盘macOS版的龟速下…

2026/8/16 6:00:23 阅读更多 →
终极ncmdump指南:3分钟实现网易云NCM音乐解密与格式转换

终极ncmdump指南:3分钟实现网易云NCM音乐解密与格式转换

终极ncmdump指南:3分钟实现网易云NCM音乐解密与格式转换 【免费下载链接】ncmdump 项目地址: https://gitcode.com/gh_mirrors/ncmd/ncmdump 还在为网易云音乐下载的NCM格式文件无法在其他播放器播放而烦恼吗?ncmdump解密工具帮你轻松解决这个困…

2026/8/16 6:00:24 阅读更多 →
HarmonyOS 应用开发《掌上英语》第81篇: 智能体卡片:为英语学习 App 打造桌面级学习助手

HarmonyOS 应用开发《掌上英语》第81篇: 智能体卡片:为英语学习 App 打造桌面级学习助手

AgentCard 智能体卡片:为英语学习 App 打造桌面级学习助手适用平台:HarmonyOS 7.0 (API 26 Beta)一、引言 HarmonyOS 7.0(API 26 Beta)新增了 AgentCard 智能体卡片能力,这是继 HMAF(鸿蒙智能体框架&#x…

2026/8/16 6:00:27 阅读更多 →