FreeRTOS任务切换学习

FreeRTOS任务切换学习

所谓任务切换,就是CPU寄存器的切换。假设当由任务A切换到任务B时,主要分为两步:
1:需暂停任务A的执行,并将此时任务A的寄存器保存到任务堆栈,这个过程叫做保存现场;
2:将任务B的各个寄存器值(被存于任务堆栈中)恢复到CPU寄存器中,这个过程叫做恢复现场;
对任务A保存现场,对任务B恢复现场,这个整体的过程称之为:上下文切换。下面要补充几个知识,以便更好理解任务切换。

PendSV异常

PendSV(可挂起的系统调用)异常对 OS 操作非常重要,其优先级可以通过编程设置。可以通过将中断控制和状态寄存器 ICSR 的 bit28,也就是 PendSV 的挂起位置 1 来触发 PendSV 中断。与 SVC 异常不同,它是不精确的,因此它的挂起状态可在更高优先级异常处理内设置,且
会在高优先级处理完成后执行。若将 PendSV 设置为最低的异常优先级,可以让 PendSV 异常处理在所有其他中断处理完成后执行,
下面我们直接来边解读程序边理解实现任务切换的过程:

__asm void xPortPendSVHandler( void )
{extern uxCriticalNesting;extern pxCurrentTCB;extern vTaskSwitchContext;/* *INDENT-OFF* */PRESERVE8mrs r0, psp//读取进程栈指针,保存在寄存器 R0 里面。isbldr r3, =pxCurrentTCB /* 得到正在运行指向任务控制块的指针的地址。*/ldr r2, [ r3 ]//得到任务控制块的地址stmdb r0 !, { r4 - r11 } /* 保存从R4到R11寄存器的值*/str r0, [ r2 ] /* 将此时的栈顶指针保存到任务控制块中的首个元素 */stmdb sp !, { r3, r14 }mov r0, #configMAX_SYSCALL_INTERRUPT_PRIORITYmsr basepri, r0dsbisbbl vTaskSwitchContextmov r0, #0msr basepri, r0ldmia sp !, { r3, r14 }ldr r1, [ r3 ]ldr r0, [ r1 ] /* The first item in pxCurrentTCB is the task top of stack. */ldmia r0 !, { r4 - r11 } /* Pop the registers and the critical nesting count. */msr psp, r0isbbx r14nop
/* *INDENT-ON* */
}

xPortPendSVHandler首先我们要明白这个函数PendSV中断。中断中使用的是MSP指针,中断外使用的是PSP指针。具体可以在手册Cortext-M3手册中找到:
在这里插入图片描述
所以自动压栈都是用的PSP指针。并且完成自动压栈后PSP指针指向的位置如下图所示:
在这里插入图片描述
mrs r0, psp所以此时r0寄存器保存的此时指针的位置。
stmdb r0 !, { r4 - r11 }从r0指针指向的位置手动压栈将寄存器R4-R11寄存器的值保存起来。此时r0指针指向的地址如图中所示:

在这里插入图片描述
str r0, [ r2 ] 将此时的栈顶指针保存到任务控制块中的首个元素。以便后面从压栈后的最新指针出开始出栈。
stmdb sp !, { r3, r14 }R14 是连接寄存器(LR)。在一个汇编程序中,你可以把它写作 both LR 和 R14。LR 用于在调用子程序时存储返回地址,R3为任务控制块的地址,为了防止 R3 和 R14 的值被改写,所以这里临时将 R3和 R14 的值先压栈。
mov r0, #configMAX_SYSCALL_INTERRUPT_PRIORITY msr basepri, r0开启临界区,也就是关闭中断。
bl vTaskSwitchContext调用这个函数得到下一个要运行的任务。下面具体来看一下这个函数是如何实现的:

void vTaskSwitchContext( void )
{if( uxSchedulerSuspended != ( UBaseType_t ) pdFALSE ){/* The scheduler is currently suspended - do not allow a context* switch. */xYieldPending = pdTRUE;}else{xYieldPending = pdFALSE;traceTASK_SWITCHED_OUT();#if ( configGENERATE_RUN_TIME_STATS == 1 ){#ifdef portALT_GET_RUN_TIME_COUNTER_VALUEportALT_GET_RUN_TIME_COUNTER_VALUE( ulTotalRunTime );#elseulTotalRunTime = portGET_RUN_TIME_COUNTER_VALUE();#endifif( ulTotalRunTime > ulTaskSwitchedInTime ){pxCurrentTCB->ulRunTimeCounter += ( ulTotalRunTime - ulTaskSwitchedInTime );}else{mtCOVERAGE_TEST_MARKER();}ulTaskSwitchedInTime = ulTotalRunTime;}#endif /* configGENERATE_RUN_TIME_STATS *//* Check for stack overflow, if configured. */taskCHECK_FOR_STACK_OVERFLOW();/* Before the currently running task is switched out, save its errno. */#if ( configUSE_POSIX_ERRNO == 1 ){pxCurrentTCB->iTaskErrno = FreeRTOS_errno;}#endif/* Select a new task to run using either the generic C or port* optimised asm code. */taskSELECT_HIGHEST_PRIORITY_TASK(); /*lint !e9079 void * is used as this macro is used with timers and co-routines too.  Alignment is known to be fine as the type of the pointer stored and retrieved is the same. */traceTASK_SWITCHED_IN();/* After the new task is switched in, update the global errno. */#if ( configUSE_POSIX_ERRNO == 1 ){FreeRTOS_errno = pxCurrentTCB->iTaskErrno;}#endif#if ( ( configUSE_NEWLIB_REENTRANT == 1 ) || ( configUSE_C_RUNTIME_TLS_SUPPORT == 1 ) ){/* Switch C-Runtime's TLS Block to point to the TLS* Block specific to this task. */configSET_TLS_BLOCK( pxCurrentTCB->xTLSBlock );}#endif}
}

taskSELECT_HIGHEST_PRIORITY_TASK()这个函数实现找到任务优先级最高的那个,具体实现如下:

 #define taskSELECT_HIGHEST_PRIORITY_TASK()                                                  \{                                                                                           \UBaseType_t uxTopPriority;                                                              \\/* Find the highest priority list that contains ready tasks. */                         \portGET_HIGHEST_PRIORITY( uxTopPriority, uxTopReadyPriority );                          \configASSERT( listCURRENT_LIST_LENGTH( &( pxReadyTasksLists[ uxTopPriority ] ) ) > 0 ); \listGET_OWNER_OF_NEXT_ENTRY( pxCurrentTCB, &( pxReadyTasksLists[ uxTopPriority ] ) );   \} /* taskSELECT_HIGHEST_PRIORITY_TASK() */

里面的实现主要又有2个函数,分别是portGET_HIGHEST_PRIORITY( uxTopPriority, uxTopReadyPriority )listGET_OWNER_OF_NEXT_ENTRY( pxCurrentTCB, &( pxReadyTasksLists[ uxTopPriority ] ) )

#define portGET_HIGHEST_PRIORITY( uxTopPriority, uxReadyPriorities )    uxTopPriority = ( 31UL - ( uint32_t ) __clz( ( uxReadyPriorities ) ) )

这个函数获取最高优先级是采用硬件的方法。也就是前导置零指令,这里也需要前面的一个知识点。在这里插入图片描述
就绪表分为多个优先级,就绪表的每个优先级可以容纳多个任务。每个就绪列表都是一个结构体。想要了解这部分可以看之前写的列表和列表项的知识:列表和列表项的知识回顾
在这里插入图片描述
listGET_OWNER_OF_NEXT_ENTRY( pxCurrentTCB, &( pxReadyTasksLists[ uxTopPriority ] ) )这个函数实现如下所示:

#define listGET_OWNER_OF_NEXT_ENTRY( pxTCB, pxList )                                           \{                                                                                          \List_t * const pxConstList = ( pxList );                                               \/* 指向List_t类型的常量指针pxConstList,并将其初始化为pxList的值。 */               \/* we don't return the marker used at the end of the list.  */                         \( pxConstList )->pxIndex = ( pxConstList )->pxIndex->pxNext;                           \if( ( void * ) ( pxConstList )->pxIndex == ( void * ) &( ( pxConstList )->xListEnd ) ) \{                                                                                      \( pxConstList )->pxIndex = ( pxConstList )->pxIndex->pxNext;                       \}                                                                                      \( pxTCB ) = ( pxConstList )->pxIndex->pvOwner;                                         \}

当列表中仅有一个任务时候,过程如下图所示:刚开始pxindex指向的是末尾列表项。( pxConstList )->pxIndex = ( pxConstList )->pxIndex->pxNext; 这句代码将指针指向列表项1。

 if( ( void * ) ( pxConstList )->pxIndex == ( void * ) &( ( pxConstList )->xListEnd ) ) \{                                                                                      \( pxConstList )->pxIndex = ( pxConstList )->pxIndex->pxNext;                       \}  

if判断作用是用来略过末尾列表项的作用。( pxTCB ) = ( pxConstList )->pxIndex->pvOwner; 这句代码作用指向包含此列表项的对象的指针。通常,这个指针指向一个任务控制块(TCB),但也可以指向其他使用列表项的数据结构。这实现了对象和其所属列表项之间的双向链接。
所以此时就得到该任务的任务控制块的地址。
在这里插入图片描述
当该就绪列表有多个任务时候,就要时间片流转了。这部分知识等到学到的时候继续补充。
在这里插入图片描述

mov r0, #0 msr basepri, r0 接下来分析继续执行的汇编代码。这2句汇编代码打开中断。退出临界区。
ldmia sp !, { r3, r14 }恢复寄存器 R3 和 R14 的值。注意,此时 pxCurrentTCB 的值已经改变了,所以读取 R3 所保存的地址处的数据就会发现其值改变了,成为了下一个要运行的任务的任务控制块的地址。
ldr r1, [ r3 ] ldr r0, [ r1 ] 因为R3所保存的是将要运行任务的任务控制块地址。所以r1中得到这个任务控制块,r0在得到栈顶指针。此时:
在这里插入图片描述
栈顶指针指向的位置如上图红色箭头所示:
ldmia r0 !, { r4 - r11 }将栈保存的值加载R4-R11寄存器中。也就是即将运行的任务的现场。
msr psp, r0更新进程栈指针 PSP 的值。此时R0指向的值为:
在这里插入图片描述
然后之后bx r14跳转到要执行的函数。因为R14保存函数返回的地址。执行此行代码以后硬件自动恢复寄存器 R0~R3、R12、LR、PC 和 xPSR 的值,确定异常返回以后应该进入处理器模式还是进程模式,使用主栈指针(MSP)还是进程栈指针(PSP)。很明显这里会进入进程模式,并且使用进程栈指针(PSP),寄存器 PC 值会被恢复为即将运行的任务的任务函数,新的任务开始运行!至此,任务切换成功。

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

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

相关文章

【无标题】系统思考—心智模式

“直到你使无意识变为有意识,它将指导你的生活并且你会称之为命运。”—卡尔荣格 心智模式深藏于我们内心之中,它潜移默化地影响着我们对世界的理解和判断。往往这些影响是如此隐蔽,以至于我们自己都未必察觉到是什么在驱动我们的选择、决策…

【Entity Framework】聊聊EF中键

【Entity Framework】聊聊EF中键 文章目录 【Entity Framework】聊聊EF中键一、概述二、配置主键2.1 约定配置主键2.2 单个属性配置为实体主键2.3 组合主键 三、主键名称四、键类型和值五、备用键 一、概述 键用作每个实体实例的唯一标识符。EF中的大多数实体都有一个键&#…

线程池阻塞队列的选择

一、背景 想起前两年被问到阻塞队列怎么选,有界是必然的, ArrayBlockingQueue、LinkedBlockingQueue怎么选呢。 二、打开源码看看 ArrayBlockingQueue arrayBlockingQueue new ArrayBlockingQueue(3);LinkedBlockingQueue linkedBlockingQueue new Lin…

VPP 负载均衡测试代码

1. 均衡的测试思想和流程说明。 先说一下理论, 然后后边才知道 代码逻辑。 调试了两天,这个代码终于通了。 由于时间关系, 画了一个粗略的图。另外这个代码只是流程通了,不过要帮助理解负载均衡我认为已经足够了。 下面是windo…

QT Creator概览

🐌博主主页:🐌​倔强的大蜗牛🐌​ 📚专栏分类:QT❤️感谢大家点赞👍收藏⭐评论✍️ 目录 一、Qt Creator 概览 ①:菜单栏 ②:模式选择 ③:构建套件选择器…

STL--容器

vector(向量) 优点:支持随机访问,所以查询效率高;尾部插入和删除操作效率高。 缺点:向非尾部插入或删除元素时,由于可能需要移动内存中的元素,效率较低;空间大小动态增长…

【Keil5-Debug和发布版本配置】

Keil5-Debug和发布版本配置 ■ Keil5-Debug和发布版本配置■ 一:sct文件 sct文件配置■ 二:发布版本不需要在 C/C,Asm,Linker,中添加 CMDDEBUG 宏定义。■ 三:Debug版本需要在Linker添加 --pd"-DCMDDE…

2024年华为OD机试真题-智能驾驶-Java-OD统一考试(C卷)

题目描述: 有一辆汽车需要从 m*n 的地图的左上角(起点)开往地图的右下角(终点),去往每一个地区都需要消耗一定的油量,加油站可进行加油 请你计算汽车确保从起点到达终点时所需的最少初始油量说明: (1) 智能汽车可以上下左右四个方向移动1 (2) 地图上的数字取值是 0或-1 或者…

【MySQL数据库 | 第二十四篇】Limit语句的性能问题和调优策略

前言: MySQL作为最流行的关系型数据库管理系统之一,被广泛应用于各种规模和类型的应用程序中。其强大的功能和灵活的查询语言使得开发人员能够高效地执行各种数据操作和分析。 然而,在处理大量数据或复杂查询时,一些开发人员可能…

Linux网卡:连接虚拟与现实的桥梁

在介绍Linux网卡之前,让我们先迈入时光机🕰️,回到1980年代末期,互联网正在逐步从一个科研网络向公众网络转变,Linux——一个自由和开源的操作系统诞生了🐧。Linux的出现,对于计算机科学领域来说…

非线性滤波相位解缠算法

相位解缠是InSAR数据处理流程中较为关键的步骤,同时也是地表高程模型重建 过程中的主要误差来源之一。迄今为止,针对干涉图的相位解缠问题,已经提出了各 种各样的相位解缠算法,这些算法大致可以分为以下几类:①路径跟踪…

DSP笔记13-时间基准子模块Time base(TB)比较子模块Counter cpmpare(CC)

时间基准子模块Time base(TB) 同步,计数 CTR计数寄存器 PRD周期寄存器 CMP比较寄存器,占空比 EPWMA, EPWMB,两个比较寄存器,但只有以及计数寄存器以及一个周期寄存器 计数模式 计数时钟TBCLK HSPCLKDIVx x0,分…

《QT实用小工具·二十三》 Ntp校时类

1、概述 源码放在文章末尾 该项目实现了 Ntp校时类 ,包含如下功能: 可设置Ntp服务器IP地址。 推荐用默认的阿里云时间服务器 ntp1.aliyun.com 收到时间信号发出。 时间精确到秒。 下面是demo演示: 项目部分代码如下: #if…

解决Jenkins中Node和Yarn路径问题:‘No such file or directory‘

在Jenkins中使用Node.js和Yarn时,可能会遇到 No such file or directory 错误。这通常是因为Jenkins构建环境没有正确设置PATH环境变量,导致它无法找到Node.js或Yarn的执行文件。以下是几种解决这个问题的方法,确保您的CI/CD流程顺利运行。 …

在线视频下载工具lux(原annie)安装及使用教程

安装教程 下载ffmpeg,参考这篇文章:Python——Windows下载ffmpeg由于博主的系统为windows,所以选择不安装lux,直接下载.exe文件,进入lux的github网站后,选择右侧的Releases,下载下图的windows …

渗透测试常见端口总结

21端口渗透剖析 FTP通常用作对远程服务器进行管理,典型应用就是对web系统进行管理。一旦FTP密码泄露就直接威胁web系统安全,甚至黑客通过提权可以直接控制服务器。这里剖析渗透FTP服务器的几种方法。 基础爆破:FTP爆破工具很多,…

SpringBoot删除菜品模块开发(SpringMVC分割参数、事务管理、异常处理、批量删除)

需求分析与设计 一:产品原型 在菜品列表页面,每个菜品后面对应的操作分别为修改、删除、停售,可通过删除功能完成对菜品及相关的数据进行删除。 删除菜品原型: 业务规则: 可以一次删除一个菜品,也可以批…

【proteus仿真】定时器0中断实验

#include <REGX51.H> sbit LED1P2^0; void delay(unsigned int ten_s) {while(ten_s--); } void inittime0(void) {TMOD0x01;//选择为定时器0模式&#xff0c;工作方式1TH00xFC;//初值65536-100064536FC18H,定时1msTL00x18;ET01;//打开定时器0中断允许EA1;//打开总中断TR…

取消请求实战

关于前端取消请求&#xff0c;大多数文章都是new AbortController() 然后将signal放到请求中&#xff0c;然后执行abort取消&#xff0c;这种文章怎么好意思拿出来啊&#xff0c;mmmmini版的官方文档罢了。 首先要明确我们想要的效果是什么&#xff0c;可以对比一下toast.show…

InternLM2 技术报告——社区翻译版

InternLM2技术报告于近日正式对外发布&#xff0c;不少社区大佬自发地参与到报告的翻译与解读中来。本文为社区用户翻译的InternLM2技术报告原文&#xff0c;分享给大家~ 论文地址&#xff1a; https://arxiv.org/pdf/2403.17297.pdf 摘要 大型语言模型&#xff08;LLMs&…