FreeRTOS-07任务切换 对vPortSVCHandler和xPortPendSVHandler的理解

任务的基本单位是TCB块,相当于任务的身份证,其基本成员包括栈指针,栈所在地址,链表节点地址,如下:

FreeRTOS.h

typedef struct tskTaskControlBlock
{
	volatile StackType_t    *pxTopOfStack;    /* 栈顶 */

	ListItem_t			    xStateListItem;   /* 任务节点 */
    
    StackType_t             *pxStack;         /* 任务栈起始地址 */
	                                          /* 任务名称,字符串形式 */
	char                    pcTaskName[ configMAX_TASK_NAME_LEN ];  
} tskTCB;
typedef tskTCB TCB_t;

vPortSVCHandler和xPortPendSVHandler讲解

FreeRTOS启动调度器时,将调用prvStartFirstTask启动第一个任务:

/*
 * 参考资料《STM32F10xxx Cortex-M3 programming manual》4.4.3,百度搜索“PM0056”即可找到这个文档
 * 在Cortex-M中,内核外设SCB的地址范围为:0xE000ED00-0xE000ED3F
 * 0xE000ED008为SCB外设中SCB_VTOR这个寄存器的地址,里面存放的是向量表的起始地址,即MSP的地址
 */
__asm void prvStartFirstTask( void )
{
	PRESERVE8

	/* 在Cortex-M中,0xE000ED08是SCB_VTOR这个寄存器的地址,
       里面存放的是向量表的起始地址,即MSP的地址 */
	ldr r0, =0xE000ED08
	ldr r0, [r0]
	ldr r0, [r0]

	/* 设置主堆栈指针msp的值 */
	msr msp, r0
    
	/* 使能全局中断 */
	cpsie i
	cpsie f
	dsb
	isb
	
    /* 调用SVC去启动第一个任务 */
    /*产生系统调用,服务号 0表示 SVC 中断,接下来将会执行 SVC 中
断服务函数*/
	svc 0  
	nop
	nop
}

vPortSVCHandler

第一个任务的加载通过触发SVC中断实现, SVC 中断要想被成功响应,其函数名必须与向量表注册的名称一致,在启动文件的向量表中,SVC 的中断服务函数注册的名称是 SVC_Handler,但是在 FreeRTOS 中,官方版本写的是 vPortSVCHandler(),为了能够顺利的响应 SVC 中断,FreeRTOS通过在FreeRTOSConfig.h 中添加添加宏定义的方法来修改函数名,对于PendSV和SysTick的处理同理。

FreeRTOSConfig.h

/* Definitions that map the FreeRTOS port interrupt handlers to their CMSIS
   standard names. */
#define vPortSVCHandler    SVC_Handler
#define xPortPendSVHandler PendSV_Handler

/* IMPORTANT: FreeRTOS is using the SysTick as internal time base, thus make sure the system and peripherials are
              using a different time base (TIM based for example).
 */
#define xPortSysTickHandler SysTick_Handler

vPortSVCHandler()函数开始真正启动第一个任务,不再返回,实现如下

port.c

__asm void vPortSVCHandler( void )
{
    extern pxCurrentTCB;
	PRESERVE8

	/* Get the location of the current TCB. */
	ldr	r3, =pxCurrentTCB	//r3=&pxCurrentTCB,即r3指向当前执行任务的TCB指针所在地址
	ldr r1, [r3]			//r1=*r3=pxCurrentTCB,既让r1指向当前任务的TCB
	ldr r0, [r1]			//r0=*r1=pxTopOfStack,即让r0执行当前任务栈顶
	/* Pop the core registers. */
    //将当前任务栈内容pop,保存入cpu寄存器,注意序号小的寄存器会先被pop,所以pop顺序:r4...r10,r11
    //调用最后一行bx r14时,剩下的 xPSR、PC、LR、R12、R3 - R0会自动出栈
	ldmia r0!, {r4-r11} 
	msr psp, r0		// 将当前任务栈顶赋给psp, 即psp = pxTopOfStack,执行后效果如图1所示			
	isb				// 指令同步隔离,确保之前的指令都已执行完毕
        
    // r0清0,用于关中断
    //设置 basepri 寄存器的值为 0,即关闭所有中断。basepri 是一个中断屏蔽寄存器,大于等于此寄存器值的中断都将被屏蔽,但如果设置成0,则不关闭任何中断
	mov r0, #0		
	msr	basepri, r0
    
    //任务上下文加载完毕,中断执行结束,返回用户线程
    //在ARM中,使用r14来保存子程序的返回地址(即上一个程序的地址),执行后效果如图2所示
	bx r14
}

这里代码中的pxCurrentTCB是当前执行的任务指针,定义在task.c

/* 当前正在运行的任务的任务控制块指针,默认初始化为NULL */
TCB_t * volatile pxCurrentTCB = NULL;

图1, msr psp, r0执行后效果:

image-20231218231604044

图2,bx r14执行后效果, 这个时候出栈使用的是 PSP 指针,自动将栈中的剩下内容加载到 CPU 寄存器: xPSR,PC(任务入口地址),R14,R12,R3,R2,R1,R0(任务的形参)同时 PSP 的值也将更新,即指向任务栈的栈顶

image-20231218232726184

这里要注意一下LDR的使用。LDR有指令和伪指令两种用法,一定要区分开。

区别一
ldr r3, = 变量
ldr r3, = 标号
ldr r3, = 立即数

区别二
ldr r3, = 立即数
ldr r3, 立即数 , 就是把立即数这个地址中的值存放到r0中

步骤:

pxCurrentTCB 是一个指针变量,指向当前运行任务的TCB。
首先将pxCurrentTCB 的 地址赋给r3,即 r3 = & pxCurrentTCB ;
然后把pxCurrentTCB 的值赋值给r1,即r1 = pxCurrentTCB 。
最后pxCurrentTCB所指的TCB的第一个成员变量(任务堆栈地址)赋给r0,即r1 = [r3] = *pxCurrentTCB= pxCurrentTCB->pxTopOfStack
把人为入栈的寄存器r4 - r11手动出栈,剩下的 xPSR、PC、LR、R12、R3 - R0会自动出栈。
把出栈完成之后的栈顶地址赋给psp,供任务使用。
这里要注意的就是第一句,是把pxCurrentTCB 的地址赋值给r3,而不是把他的值赋值r3。

xPortPendSVHandler

__asm void xPortPendSVHandler( void )
{
	extern uxCriticalNesting;
	extern pxCurrentTCB;
	extern vTaskSwitchContext;

	PRESERVE8
        
	/*r0=psp, 进入PendSV中断时,上个任务环境即		     
	xPSR,PC,R14,R12,R3,R2,R1,R0这些将自动保存入任务栈,
	剩下R4-R11需要手动保存,同时PSP将自动更新(在更新之前 PSP 指向任务栈的栈顶),
	此时 PSP是"上文"任务的堆栈指针,具体指向见图3*/
	mrs r0, psp 
	isb			//确保之前指令已执行(为什么mrs或者msr执行完之后就要接一个isb或者dsb?不知道)
	/* Get the location of the current TCB. */
	ldr	r3, =pxCurrentTCB	//r3=&pxCurrentTCB
	ldr	r2, [r3]			//r2=*r3=pxCurrentTCB

	/* Save the core registers. */
	stmdb r0!, {r4-r11}	//将cpu寄存器保存入"上文"任务栈,注意push总是先push序号大的,因此push顺序:r11,r10....r4

	/* Save the new top of stack into the first member of the TCB. */
	str r0, [r2]	//*r2=r0 => pxTopOfStack=p0, 更新"上文"任务的栈顶

	stmdb sp!, {r0, r3}	//入栈栈顶指针和pxCurrentTCB,这个栈的指针是MSP,注意顺序:r3,r0
    /* 至此,上下文切换的"上文"环境保存完成 */
    
    //关中断,高于configMAX_SYSCALL_INTERRUPT_PRIORITY的中断都将被屏蔽,configMAX_SYSCALL_INTERRUPT_PRIORITY的值在FreeRTOSConfig.h定义
	mov r0, #configMAX_SYSCALL_INTERRUPT_PRIORITY
	msr basepri, r0
        
	dsb	//数据隔离,同步之前对msr的操作
	isb	//指令隔离,确保之前所有指令已执行完毕,之后的指令使用的是正确的basepri配置
        
	bl vTaskSwitchContext	//跳转到vTaskSwitchContext函数去执行,pxCurrentTCB将被更改指向下一个任务
    //开中断
	mov r0, #0
	msr basepri, r0
   
	ldmia sp!, {r0, r3}	//从MSP栈加载r0和r3,此时r3已经指向新任务pxCurrentTCB的地址值,注意pop顺序:r0,r3
	
    /* 以下为上下文切换的"下文"环境切换 */
	/* The first item in pxCurrentTCB is the task top of stack. */
	ldr r1, [r3]	//r1=*r3=pxCurrentTCB,即新任务的TCB
	ldr r0, [r1]	//r0=*r1=pxTopOfStack,即新任务的栈顶指针

	/* Pop the core registers. */
	ldmia r0!, {r4-r11}	//将新任务的任务栈数据加载入cpu寄存器r4-r11
    
    /* 更新psp的值,等PendSV退出时,会以psp作为基地址,将任务栈中剩下的内容自动加载到CPU寄存器 */
    /* 剩下的内容包括: xPSR、PC、LR、r12、r3、r2、r1、r0 */
	msr psp, r0
	isb
	bx r14	//中断结束返回
}

图3,mrs r0, psp 执行后

image-20231218233650182

总结

其实看懂代码之后就知道上下文切换的实质是将哪个任务栈的数据加载入cpu寄存器中,新任务加载时,旧任务的数据将由cpu寄存器通过汇编代码加载回其任务栈,然后屏蔽中断,调用切换函数修改当前指向的TCB,再恢复中断,通过新任务的TCB将新任务的任务栈数据加载入cpu, 自此切换完成。

这里最难的理解点就是vPortSVCHandler和xPortPendSVHandler的代码了,需要对Cotrex内核和arm汇编有基本的了解,可以看下Cortex-M3权威指南的第三(了解寄存器和栈)和第四章(arm指令集)入门,我这里卡了很久。

参考:

【FreeRTOS】xPortPendSVHandler任务切换代码分析

https://blog.csdn.net/tao475824827/article/details/105622087

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

前缀和数组、差分数组、树状数组在Leetcode中的应用

文章目录 前缀和数组、差分数组、树状数组知识简单回顾Leetcode 1109. 航班预订统计Leetcode 307. 区域和检索-数组可修改LeetCode 面试题10.10. 数字流的秩LeetCode 1310. 子数组异或查询LeetCode 1409. 查询带键的排列 前缀和数组、差分数组、树状数组知识简单回顾 之前的文…

揭开`this`的神秘面纱:探索 JavaScript 中的上下文密钥(上)

🤍 前端开发工程师(主业)、技术博主(副业)、已过CET6 🍨 阿珊和她的猫_CSDN个人主页 🕠 牛客高级专题作者、在牛客打造高质量专栏《前端面试必备》 🍚 蓝桥云课签约作者、已在蓝桥云…

c语言编写http服务器(Linux下运行)

参考文章&#xff1a;https://blog.csdn.net/baixingyubxy/article/details/125964986?spm1001.2014.3001.5506 上面是详细讲解&#xff0c;我这篇是总结了他的代码&#xff0c;因为他没给整体代码 所有代码&#xff1a; #include <stdio.h> #include <stdlib.h&g…

Python Django Jet:优化 Django 后台管理

更多资料获取 &#x1f4da; 个人网站&#xff1a;ipengtao.com 大家好&#xff0c;今天分享 Python 中的 Django Jet 库。 Github项目地址&#xff1a;https://github.com/geex-arts/django-jet Django Jet 是一个强大的 Django 后台管理界面扩展&#xff0c;旨在提供更现代…

人工智能125个常用名词解释

1 什么是人工智能 人工智能&#xff08;Artificial Intelligence&#xff0c;简称AI&#xff09;是指计算机系统通过模拟人类的思维和行为来完成特定任务的技术和方法。人工智能的研究涉及多个学科&#xff0c;包括计算机科学、数学、心理学、哲学等领域。 人工智能可以被分为…

SVM —— 理论推导

SVM 支持向量线性可分最大间隔超平面最大间隔超平面的推导支持向量分类间隔的推导最优化问题 对偶问题拉格朗日乘子法强对偶性 SVM 优化软间隔解决问题优化目标及求解 核函数线性不可分核函数的作用常见核函数 SVM 算法优缺点 支持向量机&#xff08;Support Vector Machine&am…

Collecting package metadata (current_repodata.json): failed(解决方案)

如果有重装过anaconda&#xff0c;在C盘的用户目录下&#xff0c;会有一个名叫.condarc的文件会自动生成。 当使用conda install和conda create命令会出现下面的问题&#xff1a;Collecting package metadata (current_repodata.json): failed 解决方案&#xff1a; 1.打开Anac…

Leetcod面试经典150题刷题记录 —— 双指针篇

双指针篇 1. 验证回文串Python3 2. 判断子序列Python3双指针 3. 两数之和 II - 输入有序数组Python3 4. 盛最多水的容器Python3双指针 5. 三数之和 1. 验证回文串 题目链接&#xff1a;验证回文串 - leetcode 题目描述&#xff1a; 如果在将所有大写字符转换为小写字符、并移除…

Spring Cloud + Vue前后端分离-第6章 通用代码生成器开发

Spring Cloud Vue前后端分离-第6章 通用代码生成器开发 6-1 代码生成器原理介绍 1.增加generator模块&#xff0c;用于代码生成 2.集成freemarker 通用代码生成器开发 FreeMarker 是一款模版引擎&#xff0c;通过模板生成文件&#xff0c;包括html页面&#xff0c;excel …

基于vue+element-plus+echarts制作动态绘图页面(柱状图,饼图和折线图)

前言 我们知道echarts是一个非常强大的绘图库&#xff0c;基于这个库&#xff0c;我们可以绘制出精美的图表。对于一张图来说&#xff0c;其实比较重要的就是配置项&#xff0c;填入不同的配置内容就可以呈现出不同的效果。 当然配置项中除了样式之外&#xff0c;最重要的就是…

腾讯云debian服务器的连接与初始化

目录 1. 远程连接2. 软件下载3. 设置开机自启动 1. 远程连接 腾讯云给的服务器在安装好系统之后&#xff0c;只需要在防火墙里面添加一个白名单&#xff08;ip 或者域名&#xff09;就能访问了。 防火墙添加本机WLAN的IPv4白名单&#xff0c;本地用一个远程工具连接&#xff…

C++设计模式之——命令模式

命令模式 概念创建步骤示例示例一代码实现运行结果 示例二代码实现运行结果 示例三示例代码运行结果 示例四代码实现运行结果 应用场景 概念 命令模式是一种行为型设计模式&#xff0c;它允许将请求封装为一个对象&#xff0c;从而使得可以参数化客户端请求、将请求排队或者记…

npm login报错:Public registration is not allowed

npm login报错:Public registration is not allowed 1.出现场景2.解决 1.出现场景 npm login登录时,出现 2.解决 将自己的npm镜像源改为npm的https://registry.npmjs.org/这个&#xff0c;解决&#xff01;

安防视频云平台/可视化监控云平台EasyCVR获取设备录像失败,该如何解决?

视频云存储/安防监控EasyCVR视频汇聚平台基于云边端智能协同&#xff0c;支持海量视频的轻量化接入与汇聚、转码与处理、全网智能分发、视频集中存储等。GB28181音视频流媒体视频平台EasyCVR拓展性强&#xff0c;视频能力丰富&#xff0c;具体可实现视频监控直播、视频轮播、视…

打响指针的第一枪:指针家族

前言 指针其实是我们学习C语言中最难的知识点&#xff0c;很多人在学习指针的时候会被绕晕&#xff0c;包括博主也是&#xff0c;当初百思不得其解&#xff0c;脑袋都要冒烟了&#xff0c;本来打算在学习指针的时候就写一篇博客&#xff0c;但是当初自己的能力还是没有办法去完…

harmonyOS 自定义组件基础演示讲解

上文 HarmonyOS组件属性控制 链式编程格式推荐我们讲了一些系统组件 可以传入一些事件和参数 来达到一些不同的效果 其实 我们还可以用自己写的组件 那么 组件这么写&#xff1f; 其实 我们的 page 内部结果 就是一个组件 harmonyOS的概念 万物皆组件 那么 我们就可以在他下面…

低代码软件开发的革命

一、前言 如果一个概念能在科技圈火起来&#xff0c;它往往兼具字面简明和内涵丰富的特征&#xff0c;并具有某种重塑产业格局的潜力。低代码&#xff08;Low Code&#xff09;就是这样一个典型。顾名思义&#xff0c;低代码是指少用代码&#xff0c;甚至不用代码&#xff0c;仅…

自动化测试 (五) 读写64位操作系统的注册表

自动化测试经常需要修改注册表 很多系统的设置&#xff08;比如&#xff1a;IE的设置&#xff09;都是存在注册表中。 桌面应用程序的设置也是存在注册表中。 所以做自动化测试的时候&#xff0c;经常需要去修改注册表 Windows注册表简介 注册表编辑器在 C:\Windows\regedit…

Netty入门基础知识

简介 Netty是一款高性能java网络编程框架&#xff0c;被广泛应用在中间件、直播、社交、游戏等领域。Netty对java NIO进行高级封装&#xff0c;简化了网络应用的开发过程。 stream与channel stream不会自动缓冲数据&#xff0c;channel会利用系统提供的发送缓冲区&#xff0c;接…

科创金融的向善力量:浙商银行多措并举赋能科创企业,打造科技金融服务生态圈

近日&#xff0c;浙商银行科技金融服务发布会在杭州举行。 发布会以“智汇科创&#xff0c;善行未来”为主题&#xff0c;围绕科技金融服务“向善”新生态&#xff0c;浙商银行重磅推出科创企业全图景服务方案&#xff0c;正式发布科创积分贷&#xff0c;与浙江大学联合发布人…