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

news2024/9/23 1:41:34

写在前面

本文主要是对于 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, =0xE000ED08
	ldr r0, [r0]	//把 0xE000ED08 处向量表起始地址取出
	ldr r0, [r0]	//取出向量表起始地址对应的内容

	/* 设置主堆栈指针msp的值 */
	msr msp, r0
    
	/* 使能全局中断 */
	cpsie i	//开中断 PRIMASK=0
	cpsie f	//开异常 FAULTMASK=0
	
	/*使用 dsb 和 isb 指令确保数据和指令同步*/
//1. dsb 指令:dsb 指令用于确保数据的同步。它会强制在 dsb 指令之
//	前的所有数据访问和加载操作完成,然后再继续执行 dsb 指令后面
//	的指令。这样可以确保所有数据操作在 dsb 指令之前都已经完成,
//	避免数据争用和不一致性的问题。
//2. isb 指令:isb 指令用于确保指令的同步。它会刷新处理器流水线中
//的指令,并确保在 isb 指令之前的所有指令都已经执行完毕,然后再继
//续执行 isb 指令后面的指令。这样可以确保流水线中的指令执行顺序与
//程序中的顺序一致,避免指令重排和乱序执行带来的问题。
	dsb
	isb
	
    /* 调用SVC去启动第一个任务 */
//	"Supervisor Call"(超级用户调用),
//	用于从用户模式(通常是应用程序运行的模式)
//	切换到特权模式(通常是操作系统内核运行的模式)
//	执行一段特权代码,以执行一些需要特权级别权限的操作或服务
	svc 0  //服务号 0表示 SVC 中断,接下来将会执行 SVC 中断服务函数
	
	nop
	nop
}
  • 关于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 的指针
    
    PRESERVE8

	ldr	r3, =pxCurrentTCB	//2. 加载要运行的 TCB 的指针的地址到 r3
	ldr r1, [r3]			//3. 加载要运行的 TCB 的指针到 r1
	ldr r0, [r1]			//4. 加载 TCB 到 r0,目前 r0 的值等于第一个任务堆栈的栈顶
	ldmia r0!, {r4-r11}		//5. 以 r0 为基地址,将栈里面的内容加载到 r4~r11 寄存器,同时r0会递增
	msr psp, r0				//6. 将r0的值,即任务的栈指针更新到 psp
	isb						//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.coloradmin.cn/o/855316.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

一文看懂Apipost接口自动化使用方法

随着项目研发进程的不断推进,软件功能不断增多,对于软件测试的要求也越来越高。为了提高测试效率和减少测试成本,许多软件测试团队借助于自动化测试工具来优化测试流程。Apipost也提供了自动化测试工具,在本文中,我们将…

Android Studio System.out.println()中文乱码

第一步: 打开studio64.exe.vmoptions加入-Dfile.encodingUTF-8 第二步: File-Settings-Editor-File Encodings 把所有的编码格式改为UTF-8 尝试跑一下代码,如果还不行,重启IDE 再试试。

利用DNS隧道构建隐蔽CC信道

背景介绍 无论是高级持续性威胁(APT)、僵尸网络(Botnet),还是勒索软件、后门等,命令与控制信道(C &C)都是其重要组成部分,尤其是APT和僵尸网络中的C&C信道决定了其威胁程度。学术界和工业界就C&C方面的研究已逐渐深入…

vue3:新特性

一、react和vue的主要区别 (1)数据更新上: 1、 react 采用 fiber架构 ,使用 链表 表示 DOM 结构可以在 diff 时随时中断和继续,利用requestIdleCallback 在空闲时 diff ,防止数据量大 diff 时间长导致卡顿…

Python 之禅

Python 社区的理念都包含在 Tim Peters 撰写的 “Python 之禅” 中 在 Windows 平台的 cmd 命令中打开 python,输入 import this,就能看到 Python 之禅: 翻译: Tim Peters 的 python 之禅Beautiful is better than ugly. # 优美胜于丑陋&am…

生成树协议

文章目录 STP冗余交换网络为什么存在广播风暴?广播的危害?交换环路的危害? 工作机制BPDU什么是最好的bpduBPDU触发机制 STP选举步骤配置协议分析字段分析开销模式端口状态 故障类型根桥故障直连故障间接故障 (链路中间可能有HUB&a…

vuejs 设计与实现 - 渲染器的设计

渲染器与响应式系统的结合 本节,我们暂时将渲染器限定在 DOM 平台。既然渲染器用来渲染真实 DOM 元素,那么严格来说,下面的函数就是一个合格的渲染器: // 渲染器: function renderer(domString, container) {container.innerHTM…

中级课程-SSRF(CSRF进阶)

文章目录 成因危害挖掘 成因 危害 挖掘

JUC并发编程之线程锁(一)

目录 1.ReentrantLock(互斥锁) 2.ReentRantReaderWriterLock(互斥读写锁) 3.StampedLock(无障碍锁) 4.Condition(自定义锁) 5.LockSupport 问题引出: 由于传统的线程控制需要用到同步机制Sy…

LeetCode--HOT100题(23)

目录 题目描述:206. 反转链表(简单)题目接口解题思路代码 PS: 题目描述:206. 反转链表(简单) 给你单链表的头节点 head ,请你反转链表,并返回反转后的链表。 LeetCode做题链接&…

剑指offer56-II.数组中数字出现的次数II

第一种方法非常简单,就是用一个HashMap,key是数组中元素的值,value是出现的次数,所以遍历一遍数组,如果map中没有这个元素就把它put进去value设为1,否则value加1,然后遍历一遍map,如…

tensorflow 1.14 的 demo 02 —— tensorboard 远程访问

tensorflow 1.14.0, 提供远程访问 tensorboard 服务的方法 第一步生成 events 文件: 在上一篇demo的基础上加了一句,如下, tf.summary.FileWriter("./tmp/summary", graphsess1.graph) hello_tensorboard_remote.py …

Celery嵌入工程的使用

文章目录 1.config 1.1 通过app.conf进行配置1.2 通过app.conf.update进行配置1.3 通过配置文件进行配置1.4 通过配置类的方式进行配置2.任务相关 2.1 任务基类(base)2.2 任务名称(name)2.3 任务请求(request)2.4 任务重试(retry) 2.4.1 指定最大重试次数2.4.2 设置重试间隔时间…

基础排障实验

排障实验要求: 确保重启后服务能正常访问。确保在客户机上,应用能够使用http://www.jxjz.com:8081访问。确保DNS能够解析邮件服务器mail.jxjz.com。确保DHCP能够保留地址192.168.100.200/24给MAC为ff-0a-ac-44-33-22的主机。确保client客户机能正常的分…

【mars3d - 报错】使用mars3d加载时的一些报错和不生效问题

在使用过程中遇到过很多报错,不管大的还是小的,在这里总结下,应该会持续更新; 1、设置贴地之后报错 可能是因为 arcType:Cesium.arcType.NONE 与 clampToGround:true 是相互冲突的,两个都设置就…

Ubuntu常用压缩指令总结

一、tar tar是Linux系统中最常用的压缩工具之一,它的一个优点是它可以保留文件的权限和所有权信息。tar可以创建.tar文件(通常称为"tarball"),或者与gzip或bzip2等工具结合使用来创建.tar.gz或.tar.bz2文件。gzip工具的…

考研算法第40天:众数 【模拟,简单题】

题目 本题收获 又是一道比较简单的模拟题,就不说解题思路了,说一下中间遇到的问题吧,就是说cin输入它是碰到空格就停止输入的,详细的看下面这篇博客对于cin提取输入流遇到空格的问题_while(cin) 空格_就是那个党伟的博客-CSDN博…

【动态规划刷题 6】 删除并获得点数 粉刷房子

740. 删除并获得点数 给你一个整数数组 nums ,你可以对它进行一些操作。 每次操作中,选择任意一个 nums[i] ,删除它并获得 nums[i] 的点数。之后,你必须删除 所有 等于 nums[i] - 1 和 nums[i] 1 的元素。 开始你拥有 0 个点数。…

JavaScript实践:用Canvas开发一个可配置的大转盘抽奖功能

🏆作者简介,黑夜开发者,全栈领域新星创作者✌,阿里云社区专家博主,2023年6月csdn上海赛道top4。 🏆数年电商行业从业经验,历任核心研发工程师,项目技术负责人。 🏆本文已…

基于Qlearning强化学习的路径规划算法matlab仿真

目录 1.算法运行效果图预览 2.算法运行软件版本 3.部分核心程序 4.算法理论概述 4.1 Q值更新规则 4.2 基于Q-learning的路径规划算法设计 4.3 Q-learning路径规划流程 5.算法完整程序工程 1.算法运行效果图预览 2.算法运行软件版本 MATLAB2022A 3.部分核心程序 ..…