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/10/9 8:26:51 阅读更多 →
Grok Bot插件生态解析:从核心架构到实战开发与集成

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

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

2026/10/14 8:22:41 阅读更多 →
双键盘远程联机方案:用Parsec与AntiMicroX实现异地同屏游戏

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

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

2026/10/6 3:57:42 阅读更多 →

最新新闻

幼儿园国际象棋比赛全攻略:从赛事设计到教学实操的完整指南

幼儿园国际象棋比赛全攻略:从赛事设计到教学实操的完整指南

1. 一场幼儿园棋赛背后,藏着多少被低估的教育设计第一次看到“幼儿园办国际象棋比赛”这个事,我脑子里冒出来的第一个念头是:三四岁的孩子坐得住吗?棋子会不会被当成积木搭着玩?但仔细看完整个活动的组织逻辑之后&…

2026/10/15 8:22:31 阅读更多 →
OpenLayers地图开发避坑指南:坐标系、图层栈与离线部署

OpenLayers地图开发避坑指南:坐标系、图层栈与离线部署

简介:本资源为 OpenLayers 地图开发必备的前端基础文件包,面向 WebGIS 初学者、前端开发者及地理信息相关课程学习者,解决快速集成开源地图能力时缺少核心依赖的问题。压缩包共 3 个文件(152KB),含核心 Jav…

2026/10/15 8:22:31 阅读更多 →
幼儿园国际象棋比赛怎么搞?从赛制设计到现场实操的完整复盘

幼儿园国际象棋比赛怎么搞?从赛制设计到现场实操的完整复盘

1. 一场幼儿园棋赛背后的教育设计逻辑1.1 为什么选国际象棋而不是其他棋类接到这个项目标题的时候,我第一反应是:幼儿园搞国际象棋比赛,这个选型挺有意思。干了这么多年一线教育项目,我见过太多幼儿园做活动是"什么热闹做什么…

2026/10/15 8:22:31 阅读更多 →
可靠评估对抗鲁棒性:免调参多样性攻击集成方法

可靠评估对抗鲁棒性:免调参多样性攻击集成方法

1. 项目概述:为什么“可靠评估对抗鲁棒性”这件事比想象中更难你有没有遇到过这种情况:模型在标准测试集上准确率98%,可只要加一丁点人眼根本看不出的扰动——比如把某张猫图的像素值整体调高0.002,它就坚定地把猫判成烤面包机&am…

2026/10/15 8:22:31 阅读更多 →
前端导出Excel/CSV实战:方案选型、编码陷阱与性能优化

前端导出Excel/CSV实战:方案选型、编码陷阱与性能优化

1. 为什么前端导出这件事值得认真对待但凡做过管理后台、数据看板或者报表类项目的同行,大概率都碰到过同一个需求:页面上辛辛苦苦渲染出来的表格,业务方一句话——“能不能加个导出按钮,我要Excel”。听起来简单,真动…

2026/10/15 8:22:31 阅读更多 →
七层封装引发的支付回调事故:过度设计如何安全拆解?

七层封装引发的支付回调事故:过度设计如何安全拆解?

1. 凌晨两点的报警:一次由"优雅设计"引发的线上事故先交代一下背景,免得大家以为我要讲什么架子上的空道理。前阵子公司某个核心支付项目出了一个线上事故,直接触发告警群炸锅的那种。当时我人在家里,正准备关灯睡觉&am…

2026/10/15 8:21:31 阅读更多 →

日新闻

Docker Compose 容器化部署 ChirpStack LoRaWAN 网络服务器实战

Docker Compose 容器化部署 ChirpStack LoRaWAN 网络服务器实战

1. 为什么选择容器化方式部署 LoRa 网络服务LoRa 物联网项目落地时,网络服务器是整个链路的核心枢纽。它负责接收网关上报的数据包、完成去重与解密、再把有效载荷分发给上层应用。早期大家习惯把网络服务器直接装在物理机上,依赖一堆系统库和特定版本的…

2026/10/15 0:00:46 阅读更多 →
共享LLM服务下的跨模型自动扩缩容:Kubernetes实战与避坑指南

共享LLM服务下的跨模型自动扩缩容:Kubernetes实战与避坑指南

1. 共享推理服务里,扩缩容到底难在哪做过大模型推理服务部署的人,大概都经历过这样的场景:一个团队共用一套推理集群,A 业务跑的是 7B 的小模型,B 业务跑的是 72B 的大模型,C 业务白天流量高、晚上几乎归零…

2026/10/15 0:00:46 阅读更多 →
Systemd 启动阶段根文件系统只读之谜:fstab 与 remount-fs 服务深度解析

Systemd 启动阶段根文件系统只读之谜:fstab 与 remount-fs 服务深度解析

1. 引子:一次内核恐慌引发的追查先从一个真实场景说起。某次我给一台老旧的嵌入式设备折腾系统裁减,为了把启动时间压进两秒以内,我手贱地把一串自认为“没用”的 systemd 服务给 disable 了,其中就包括systemd-remount-fs.servic…

2026/10/15 0:00:46 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/14 3:30:28 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/14 16:30:29 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/14 3:29:58 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/15 0:14:36 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/14 22:45:47 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/14 7:53:34 阅读更多 →