【学习日记】【FreeRTOS】调度器函数实现详解

写在前面

本文主要是对于 FreeRTOS 中调度器函数实现的详细解释,代码大部分参考了野火 FreeRTOS 教程配套源码,作了一小部分修改。

一、MSP 和 PSP

Cortex-M有两种栈空间,主堆栈和进程堆栈。

  • MSP 用于系统级别和中断处理的堆栈
    • MSP 用于保存中断发生时的堆栈状态以及在特殊操作(例如任务切换)期间的堆栈状态。MSP 在启动时会被设置为合适的内存地址,并在系统级代码运行期间始终保持不变。
  • PSP 用于任务级别的堆栈
    • 用于保存任务执行期间的局部变量、函数调用、参数等。在任务切换时,任务的 PSP 被保存,并加载下一个任务的 PSP。每个任务有自己独立的堆栈空间,并且在任务切换时,PSP 的值会发生变化。

FreeRTOS中:中断用MSP,中断以外用PSP。

二、调度器函数逻辑

在这里插入图片描述

三、调度器函数详解

1.vTaskStartScheduler()

  • 本函数为调度器的启动函数
  • pxCurrentTCB 是一个在 task.c 定义的全局指针,用于指向当前正在运行或者即将要运行的任务的任务控制块
  • 目前没有使用优先级,所以手动指定第一个运行的任务
  • 调用 xPortStartScheduler() 启动调度器
void vTaskStartScheduler( void )
{/* 手动指定第一个运行的任务 */pxCurrentTCB = &Task1TCB;/* 启动调度器 */if( xPortStartScheduler() != pdFALSE ){/* 调度器启动成功,则不会返回,即不会来到这里 */}
}

2.xPortStartScheduler()

  1. 配置PendSV 和 SysTick 的中断优先级为最低
  2. 调用函数 prvStartFirstTask()启动第一个任务
BaseType_t xPortStartScheduler( void )
{/*PendSV是一个用于低优先级任务切换的软件中断。通过触发PendSV中断,可以请求处理器在合适的时间切换到更高优先级的任务。PendSV中断具有最低的中断优先级,因此可以在其他中断处理完成后立即执行。*//* 配置PendSV 和 SysTick 的中断优先级为最低 */portNVIC_SYSPRI2_REG |= portNVIC_PENDSV_PRI;portNVIC_SYSPRI2_REG |= portNVIC_SYSTICK_PRI;/* 启动第一个任务,不再返回 */prvStartFirstTask();/* 不应该运行到这里 */return 0;
}

3.prvStartFirstTask()

  • 用于初始化启动第一个任务的环境,主要是重新设置MSP指针,并使能全局中断

  • 调度器启动函数xPortStartScheduler( void )调用:

  • prvStartFirstTask函数:

  1. PRESERVE8 指令保留 8 字节栈对齐
  2. 取出向量表起始地址对应的内容
  3. 使用向量表起始地址对应的内容设置主堆栈指针msp的值
  4. 使能全局中断
  5. 使用 dsb 和 isb 指令确保数据和指令同步
  6. 调用SVC去启动第一个任务
/** 参考资料《STM32F10xxx Cortex-M3 programming manual》4.4.3,百度搜索“PM0056”即可找到这个文档* 在Cortex-M中,内核外设SCB的地址范围为:0xE000ED00-0xE000ED3F* 0xE000ED008为SCB外设中SCB_VTOR这个寄存器的地址,里面存放的是向量表的起始地址,即MSP的地址*/
__asm void prvStartFirstTask( void )
{/*使用 PRESERVE8 指令保留 8 字节栈对齐*/PRESERVE8/* 在Cortex-M中,0xE000ED08是SCB_VTOR这个寄存器的地址,里面存放的是向量表的起始地址,即MSP的地址 */
//	向量表通常是从内部 FLASH 的起始地址开
//	始存放,那么可知 memory:0x00000000 处存放的就是 MSP 的值。ldr r0, =0xE000ED08ldr r0, [r0]	//把 0xE000ED08 处向量表起始地址取出ldr r0, [r0]	//取出向量表起始地址对应的内容/* 设置主堆栈指针msp的值 */msr msp, r0/* 使能全局中断 */cpsie i	//开中断 PRIMASK=0cpsie f	//开异常 FAULTMASK=0/*使用 dsb 和 isb 指令确保数据和指令同步*/
//1. dsb 指令:dsb 指令用于确保数据的同步。它会强制在 dsb 指令之
//	前的所有数据访问和加载操作完成,然后再继续执行 dsb 指令后面
//	的指令。这样可以确保所有数据操作在 dsb 指令之前都已经完成,
//	避免数据争用和不一致性的问题。
//2. isb 指令:isb 指令用于确保指令的同步。它会刷新处理器流水线中
//的指令,并确保在 isb 指令之前的所有指令都已经执行完毕,然后再继
//续执行 isb 指令后面的指令。这样可以确保流水线中的指令执行顺序与
//程序中的顺序一致,避免指令重排和乱序执行带来的问题。dsbisb/* 调用SVC去启动第一个任务 */
//	"Supervisor Call"(超级用户调用),
//	用于从用户模式(通常是应用程序运行的模式)
//	切换到特权模式(通常是操作系统内核运行的模式)
//	执行一段特权代码,以执行一些需要特权级别权限的操作或服务svc 0  //服务号 0表示 SVC 中断,接下来将会执行 SVC 中断服务函数nopnop
}
  • 关于Cortex-M中三个中断屏蔽寄存器
    在这里插入图片描述

4.vPortSVCHandler()

  • 本函数为 SVC 的中断服务函数
  1. 加载 TCB 到 r0,以 r0 为基地址,将栈里面的内容加载到 r4~r11 寄存器
  2. 开启所有中断
  3. 设置 r14 寄存器,以使用 PSP 出栈,进入线程模式,返回 Thumb 状态
  4. 如果异常返回,则 bx r14 进入 Thumb 状态,并且栈中的剩下内容将会自动加载到CPU寄存器
//SVC中断函数
__asm void vPortSVCHandler( void )
{extern pxCurrentTCB;	//1. 加载要运行的 TCB 的指针PRESERVE8ldr	r3, =pxCurrentTCB	//2. 加载要运行的 TCB 的指针的地址到 r3ldr r1, [r3]			//3. 加载要运行的 TCB 的指针到 r1ldr r0, [r1]			//4. 加载 TCB 到 r0,目前 r0 的值等于第一个任务堆栈的栈顶ldmia r0!, {r4-r11}		//5. 以 r0 为基地址,将栈里面的内容加载到 r4~r11 寄存器,同时r0会递增msr psp, r0				//6. 将r0的值,即任务的栈指针更新到 pspisb						//7. 等待指令同步mov r0, #0 msr	basepri, r0         //8. 设置basepri寄存器的值为0,即所有的中断都没有被屏蔽orr r14, #0xd           //9. 当从SVC中断服务退出前,通过向r14寄存器最后4位按位或上0x0D,//   使得硬件在退出时使用进程堆栈指针PSP完成出栈操作并返回后进入线程模式、返回Thumb状态bx r14                  //10. 异常返回,这个时候栈中的剩下内容将会自动加载到CPU寄存器://    xPSR,PC(任务入口地址),R14,R12,R3,R2,R1,R0(任务的形参)//    同时PSP的值也将更新,即指向任务栈的栈顶
}

执行成功后,PSP 的指向(图片来自野火):
在这里插入图片描述

ARM 状态和 Thumb 状态详解

在 ARM 架构中,ARM 状态和 Thumb 状态是指处理器运行的不同工作模式。这些模式决定了处理器执行代码的指令集。

  • ARM 状态:
  1. 在 ARM 状态下,处理器执行 ARM 指令集。这些指令集是 32 位宽度的。
  2. ARM 状态提供了更高的代码密度和更强大的功能,可以执行更复杂的指令。
  3. ARM 状态下的指令集包括了更多的寄存器和更多的数据处理指令。
  4. ARM 指令使用的是 32 位的寄存器。
  5. 进入 ARM 状态可以使用跳转指令 bx。
  • Thumb 状态:
  1. 在 Thumb 状态下,处理器执行 Thumb 指令集。这些指令集是 16 位宽度的,它们可以通过压缩来提供更好的代码密度。
  2. Thumb 状态下的指令集相对于 ARM 状态来说更为紧凑,但功能上略有限制。
  3. Thumb 指令使用的是 16 位的寄存器,这些寄存器只能存放 16 位的数据。
  4. 进入 Thumb 状态可以使用跳转指令 bx。

后记

如果您觉得本文写得不错,可以点个赞激励一下作者!
如果您发现本文的问题,欢迎在评论区或者私信共同探讨!
共勉!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/61522.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Linux系统调试课:Linux Kernel Printk

🚀返回专栏总目录 文章目录 0、printk 说明1、printk 日志等级设置2、屏蔽等级日志控制机制3、printk打印常用方式4、printk打印格式0、printk 说明 在开发Linux device Driver或者跟踪调试内核行为的时候经常要通过Log API来trace整个过程,Kernel API printk()是整个Kern…

idea 加入 .so文件

背景 做项目的时候,遇到需要查看native 方法 涉及到c源码的查看,因此需要加载.so文件去查看。 操作 idea-file-project structure 找到lib,把你的.so文件添加进来就可以啦 然后你就可以查看对应的源码了。

利用ChatGPT绘制思维导图——以新能源汽车竞品分析报告为例

随着人们对环境保护的日益关注和传统燃油汽车的限制,全球范围内对新能源汽车的需求不断增长。新能源汽车市场的激烈竞争使得了解各个竞品的特点和优劣成为关键。然而,针对这一领域的详尽竞品分析却常常需要大量时间和精力。 在此背景下,人工智…

Windows下运行Tomcat服务时报GC Overhead Limit Exceeded

根本原因是在新建Tomcat作为Windows服务时,系统默认设置的堆内存太小了,我们打开/bin/service.bat文件,将如下图所示的默认值改大一些就好了 if "%JvmMs%" "" set JvmMs512 if "%JvmMx%" "" set J…

移动端的Flex布局

目录 引入 一、传统布局与flex布局 传统性 flex布局 二、felx的特点 三、flex布局父项的常见属性 四、flex布局子项的常见方向 总结 引入 flex 是 flexible Box的缩写,意为“弹性布局”,用来为盒状模型提供最大的灵活性,任何一个容器…

【Linux:线程池】

文章目录 1 线程池概念2 第一个版本的线程池3 第二个版本的线程池4 第三个版本的线程池5 STL中的容器以及智能指针的线程安全问题6 其他常见的各种锁7 读者写者问题(了解) 1 线程池概念 一种线程使用模式。线程过多会带来调度开销,进而影响缓存局部性和整体性能。而…

物联网与5G引领智慧城市时代的到来

智慧城市需要依赖于多种技术,这些技术的应用将城市转变为高效、智能、可持续发展的现代化城市。智慧城市是基于信息技术、物联网和大数据等先进技术的融合,旨在提升城市的运行效率、资源利用效率和居民生活质量。以下是智慧城市需要依赖的主要技术&#…

sql高频面试题-连续完成两个指定动作的用户统计

用户行为分析 业务背景 某购物APP最近上线了一个新功能,用户签到后可以跳转到大转盘抽奖,抽奖获得的奖金可以抵消购物的费用,以此来培养用户使用app的习惯。 数据表介绍 现有一张用户行为表action_log,主要字段如下&#xff0c…

Elasticsearch同时使用should和must

问题及解决方法 must和should组合查询,should失效。使用must嵌套查询,将should组成的bool查询包含在其中一个must查询中。 SearchRequest request new SearchRequest(); request.indices("function_log");SearchSourceBuilder sourceBuilde…

MFC第二十九天 CView类的分支(以及其派生类的功能)、MFC六大关键技术

文章目录 CView类的分支CEditViewCHtmlViewMainFrm.h CMainFrame 类的接口CMainView .h CListCtrl与CListView的创建原理 CTreeViewCTreeCtrl类简介CTreeCtrl类的原理以及常用功能 MFC六大关键技术视图和带分割栏的框架开发与消息路由CLeftView.cppCRightView.hCRightView.cppC…

分布式链路追踪概述

分布式链路追踪概述 文章目录 分布式链路追踪概述1.分布式链路追踪概述1.1.什么是 Tracing1.2.为什么需要Distributed Tracing 2.Google Dapper2.1.Dapper的分布式跟踪2.1.1.跟踪树和span2.1.2.Annotation2.1.3.采样率 3.OpenTracing3.1.发展历史3.2.数据模型 4.java探针技术-j…

现代C++中的从头开始深度学习【2/8】:张量编程

一、说明 初学者文本:此文本需要入门级编程背景和对机器学习的基本了解。张量是在深度学习算法中表示数据的主要方式。它们广泛用于在算法执行期间实现输入、输出、参数和内部状态。 在这个故事中,我们将学习如何使用特征张量 API 来开发我们的C算法。具…