FreeRTOS学习——Systick中断、SVC中断、PendSV中断

FreeRTOS学习——接口宏portmacro.h,仅用于记录自己阅读与学习源码
FreeRTOS Kernel V10.5.1
port :GCC/ARM_CM7

文章目录

  • Systick
    • 源码
    • 触发方式
  • SVC
    • 源码
    • 触发方式
  • PendSV
    • 源码
    • 触发方式
  • 相关汇编指令


Systick

源码

在Systick中断xPortSysTickHandler中,只做了一件事,那就是挂起PendSV中断。
所以其实切换任务是在PendSV中断执行的。

void xPortSysTickHandler( void )
{/* SysTick以最低的中断优先级运行 */portDISABLE_INTERRUPTS();{/* Increment the RTOS tick. */if( xTaskIncrementTick() != pdFALSE ){/* 上下文切换在 PendSV 中断 中执行* 挂起 PendSV 中断. */portNVIC_INT_CTRL_REG = portNVIC_PENDSVSET_BIT;}}portENABLE_INTERRUPTS();
}

触发方式

在开启调度器时,会调用定时器中断设置函数vPortSetupTimerInterrupt,以生成 tick 中断,如下:
vTaskStartScheduler →
xPortStartScheduler →
vPortSetupTimerInterrupt
配置系统的滴答定时器,以允许操作系统根据配置的频率生成定时中断,从而支持任务的调度和管理

__attribute__( ( weak ) ) void vPortSetupTimerInterrupt( void )
{/* 低功耗模式,这里先不做分析. */#if ( configUSE_TICKLESS_IDLE == 1 ){ulTimerCountsForOneTick = ( configSYSTICK_CLOCK_HZ / configTICK_RATE_HZ );xMaximumPossibleSuppressedTicks = portMAX_24_BIT_NUMBER / ulTimerCountsForOneTick;ulStoppedTimerCompensation = portMISSED_COUNTS_FACTOR / ( configCPU_CLOCK_HZ / configSYSTICK_CLOCK_HZ );}#endif /* configUSE_TICKLESS_IDLE *//* 停止和清除SysTick. */portNVIC_SYSTICK_CTRL_REG = 0UL;/*将控制寄存器设置为0,停止SysTick*/portNVIC_SYSTICK_CURRENT_VALUE_REG = 0UL;/*将当前值寄存器清零。*//* 配置SysTick. *//* 设置加载寄存器,将SysTick的计数重加载为目标计数值*/portNVIC_SYSTICK_LOAD_REG = ( configSYSTICK_CLOCK_HZ / configTICK_RATE_HZ ) - 1UL;/* 设置SysTick的控制寄存器,以启用时钟、启用中断和使能SysTick*/portNVIC_SYSTICK_CTRL_REG = ( portNVIC_SYSTICK_CLK_BIT_CONFIG | portNVIC_SYSTICK_INT_BIT | portNVIC_SYSTICK_ENABLE_BIT );
}

这几个值分别是

#define configSYSTICK_CLOCK_HZ             ( configCPU_CLOCK_HZ )
/* Ensure the SysTick is clocked at the same frequency as the core. */
#define portNVIC_SYSTICK_CLK_BIT_CONFIG    ( portNVIC_SYSTICK_CLK_BIT )#define configCPU_CLOCK_HZ						( SystemCoreClock )
#define configTICK_RATE_HZ						( 1000 )#define portNVIC_SYSTICK_CLK_BIT              ( 1UL << 2UL )
#define portNVIC_SYSTICK_INT_BIT              ( 1UL << 1UL )
#define portNVIC_SYSTICK_ENABLE_BIT           ( 1UL << 0UL )

然后就会按照设定的时间(默认1ms)周期性的触发Systick中断


SVC

源码

void vPortSVCHandler( void )
{__asm volatile (                           /* volatile表示不能移除或重排序这段代码. */"	ldr	r3, pxCurrentTCBConst2		\n"/* 将pxCurrentTCB的地址加载到r3中. */"	ldr r1, [r3]					\n"/* 从r3指向的位置读取当前任务控制块(TCB)的地址并将其存储在r1中. */"	ldr r0, [r1]					\n"/* 从r1指向的位置读取数据,即当前任务控制块中的第一个元素,所以r0中保存的就是当前任务栈的栈顶地址. */"	ldmia r0!, {r4-r11, r14}		\n"/* 栈中弹出寄存器r4到r11和r14(链接寄存器),这里使用ldmia指令(加载多寄存器),!表示更新指针r0,指向下一个数据. */"	msr psp, r0						\n"/* 将恢复后的任务栈指针r0存入psp,这样就恢复了当前任务的执行上下文. */"	isb								\n"/* 指令同步屏障,确保之前的指令在接下来的指令执行之前完成*/"	mov r0, #0 						\n"/* 将寄存器r0清零,准备设置中断优先级屏蔽*/"	msr	basepri, r0					\n"/* 将basepri设置为0,意味着没有设置优先级屏蔽,允许所有中断*/"	bx r14							\n"/* 通过bx指令跳转到r14寄存器中保存的地址,通常是返回到被中断的任务执行*/"									\n""	.align 4						\n"/* 确保下一条指令或数据在4字节边界对齐*/"pxCurrentTCBConst2: .word pxCurrentTCB				\n"/* 定义一个标签pxCurrentTCBConst2,并将pxCurrentTCB的地址存储到这个标签中,以便在前面的ldr指令中使用*/);
}
  1. 获取当前任务控制块pxCurrentTCB的栈顶地址
  2. 将r4-r11, r14手动出栈
  3. 将当前栈顶指针存入psp
  4. 开中断

ldr r3, pxCurrentTCBConst2:将pxCurrentTCBConst2的值(即pxCurrentTCB的地址)加载到寄存器r3中。
ldr r1, [r3]:从r3指向的内存地址加载当前任务控制块(TCB)的地址到r1中。
ldr r0, [r1]:从r1指向的TCB地址加载当前任务栈的栈顶地址到r0中。
ldmia r0!, {r4-r11, r14}:从r0指向的栈中弹出寄存器r4到r11和r14,!表示更新r0的值,指向下一个数据。
msr psp, r0:将恢复后的任务栈指针r0存入PSP(进程栈指针)。
mov r0, #0:将寄存器r0清零。
msr basepri, r0:将basepri寄存器设置为0,允许所有中断。
bx r14:跳转到返回地址。
pxCurrentTCBConst2: .word pxCurrentTCB:定义标签pxCurrentTCBConst2并将pxCurrentTCB的地址存储在该位置

触发方式

SVC(Supervisor Call)中断是通过软件触发的中断

SVC指令:软件通过执行 SVC 指令显式地请求中断,svc 0
当SVC指令被执行后,处理器会根据中断向量表中的信息跳转到相应的SVC中断处理函数,执行SVC指令时,处理器会自动保存当前的上下文,并将处理器的模式切换为特权模式,从而允许执行受限的操作

在开启调度器时,会调用开始第一个任务函数,如下
vTaskStartScheduler →
xPortStartScheduler →
prvPortStartFirstTask

static void prvPortStartFirstTask( void )
{/* 开始第一个任务。这也清除了指示FPU正在使用的位,以防FPU在调度器启动之前被使用,* 否则会导致SVC堆栈中不必要的空间留下,以延迟保存FPU寄存器。 */__asm volatile (" ldr r0, =0xE000ED08 	\n"/* 加载NVIC偏移寄存器的地址到r0寄存器,这个寄存器用于访问系统控制块(SCB)的基地址. */" ldr r0, [r0] 			\n"" ldr r0, [r0] 			\n"/* 读取两次,从SCB中加载系统栈指针(MSP)的值到r0寄存器*/" msr msp, r0			\n"/* 将r0中的值设置为主栈指针(MSP),这标志着栈的开始. */" mov r0, #0			\n"/* 清除控制寄存器中指示FPU正在使用的位,以确保不保留之前的状态. */" msr control, r0		\n"" cpsie i				\n"/* 开中断. */" cpsie f				\n"" dsb					\n"" isb					\n"" svc 0					\n"/* 触发SVC异常. */" nop					\n"" .ltorg				\n");
}
  1. 获取系统堆栈指针MSP
  2. 清除FPU正在使用的位
  3. 开中断
  4. 触发SVC异常

PendSV

源码

void xPortPendSVHandler( void )
{/* This is a naked function. */__asm volatile("	mrs r0, psp							\n"/* 读取当前进程的栈指针(Process Stack Pointer),将其值存入寄存器 r0*/"	isb									\n"/* 指令同步屏障*/"										\n""	ldr	r3, pxCurrentTCBConst			\n"/* 将pxCurrentTCB的地址加载到寄存器 r3. */"	ldr	r2, [r3]						\n"/* 从r3指向的位置读取当前任务控制块(TCB)的地址并将其存储在r2中. */"										\n""	tst r14, #0x10						\n"/* 测试链接寄存器 r14 的第 4 位,判断当前任务是否使用浮点单元(FPU). */"	it eq								\n"/* 如果第 4 位为 1(即使用 FPU 上下文),则执行接下来的语句*/"	vstmdbeq r0!, {s16-s31}				\n"/* 如果使用 FPU 上下文,则将 FPU 的高寄存器(s16 到 s31)压入栈中*/"										\n""	stmdb r0!, {r4-r11, r14}			\n"/* 将寄存器 r4 到 r11 及 r14 压入栈中. */"	str r0, [r2]						\n"/* 将新的栈顶指针值保存到 TCB 中. */"										\n""	stmdb sp!, {r0, r3}					\n"/* 将目前的栈指针和 TCB 地址压入主栈,以便恢复,这里入栈是 MSP*/"	mov r0, %0 							\n"/* 将最大可用优先级(configMAX_SYSCALL_INTERRUPT_PRIORITY)加载到 r0 中*/"	cpsid i								\n"/* 禁止中断. */"	msr basepri, r0						\n"/* 设置优先级屏蔽寄存器(Base Priority),关中断*/"	dsb									\n"/* 数据同步屏障*/"	isb									\n"/* 指令同步屏障*/"	cpsie i								\n"/* 启用中断 */"	bl vTaskSwitchContext				\n"/* 调用任务切换的上下文切换函数*/"	mov r0, #0							\n""	msr basepri, r0						\n"/* 恢复 Base Priority 为 0,开中断*/"	ldmia sp!, {r0, r3}					\n"/* 从主栈中恢复之前保存的 r0 和 r3*/"										\n""	ldr r1, [r3]						\n"/* 从r3指向的位置读取当前任务控制块(已经更新过了)的地址并将其存储在r1中. */"	ldr r0, [r1]						\n"/* 从r1指向的位置读取数据,即当前任务控制块中的第一个元素,所以r0中保存的就是当前任务栈的栈顶地址*/"										\n""	ldmia r0!, {r4-r11, r14}			\n"/* 从栈中弹出保存的核心寄存器r4-r11 和 r14. */"										\n""	tst r14, #0x10						\n"/* 再次判断是否使用 FPU 上下文. */"	it eq								\n"/* 如果使用 FPU,上下文则执行下一条指令*/"	vldmiaeq r0!, {s16-s31}				\n"/* 如果使用 FPU,则从栈中弹出高寄存器(s16 到 s31)*/"										\n""	msr psp, r0							\n"/* 将恢复后的栈指针值写入PSP,以便切换到新的任务的栈中. */"	isb									\n"/* 指令同步屏障*/"										\n"#ifdef WORKAROUND_PMU_CM001 /* XMC4000特定勘误表解决方法. 这里不用管*/#if WORKAROUND_PMU_CM001 == 1"			push { r14 }				\n""			pop { pc }					\n"#endif#endif"										\n""	bx r14								\n"/* 返回到调用该处理程序的位置*/"										\n"/* 将后面的数据对齐到 4 字节*/"	.align 4							\n""pxCurrentTCBConst: .word pxCurrentTCB	\n"::"i" ( configMAX_SYSCALL_INTERRUPT_PRIORITY ));
}

在PendSV中干了两件事
一是保存当前任务的现场

  1. 获取当前任务控制块pxCurrentTCB的栈顶地址
  2. 将核心寄存器入栈(其他寄存器在进入中断之前已经自动入栈)
  3. 保存栈顶指针

二是恢复最新任务的现场

  1. 调用任务切换的上下文切换函数,然后pxCurrentTCBConst中指向的值就已经变成了新的TCB的地址,但是pxCurrentTCBConst本身的内存地址是不变得
  2. 获取最新任务控制块pxCurrentTCB的栈顶地址
  3. 将核心寄存器出栈(其他寄存器在退出中断之后会自动出栈)
  4. 将栈顶指针值写入PSP

这样就完成了上下文的切换, 在进入中断前后,堆栈指针的变化是:

  1. 进入中断前,使用PSP,PSP指向旧任务的栈顶
  2. 在中断中,使用MSP
  3. 退出中断后,使用PSP,PSP指向新任务的栈顶

任务切换函数vTaskSwitchContext →
taskSELECT_HIGHEST_PRIORITY_TASK →
listGET_OWNER_OF_NEXT_ENTRY
经过此函数后,pxCurrentTCBConst已经指向了新的TCB

触发方式

1 在Systick handle中触发

/* 上下文切换在 PendSV 中断 中执行* 挂起 PendSV 中断. */portNVIC_INT_CTRL_REG = portNVIC_PENDSVSET_BIT;

2 taskYIELD();
通过调用taskYIELD来触发

#define portYIELD()                                 \{                                                   \/* Set a PendSV to request a context switch. */ \portNVIC_INT_CTRL_REG = portNVIC_PENDSVSET_BIT; \\/* Barriers are normally not required but do ensure the code is completely \* within the specified behaviour for the architecture. */ \__asm volatile ( "dsb" ::: "memory" );                     \__asm volatile ( "isb" );                                  \}

相关汇编指令

  • ldr:加载寄存器指令,用于从内存中加载数据到寄存器中。
  • ldmia:加载多寄存器指令,可以一次性将多个内存单元的数据加载到多个寄存器中。
  • msr:移动至特权寄存器的指令,用于设置某些特定的寄存器。
  • isb:指令同步屏障,用于确保指令执行的顺序,确保之前的指令在接下来的指令执行之前完成
  • dsb:数据同步屏障,该指令确保所有之前的读写操作在继续执行后续指令之前完成
  • mov:数据传送指令,用于将一个常数值或寄存器的值移动到另一个寄存器。
  • bx:分支和交换指令,用于跳转到r14寄存器中保存的地址。
  • .align:伪指令,用于确保下一条指令或数据在指定的字节边界对齐。
  • .word:伪指令,定义一个字(4字节)并将值存储到该位置。
  • nop用于在代码中占用一个执行周期,但不进行任何操作,常常针对时序和代码结构进行调整
  • .ltorg用于生成字面量池,为代码中使用的常量提供存储空间。通过将常量放置在易于访问的位置,它提高了程序的整体性能。
  • 开关中断指令
    CPSID I ;PRIMASK=1 ;关中断 (硬件错误异常 不关)
    CPSIE I ;PRIMASK=0 ;开中断
    CPSID F ;FAULTMASK=1 ;关异常(硬件错误异常 也关)
    CPSIE F ;FAULTMASK=0 ;开异常

本人菜鸟,欢迎大佬们甄误

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

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

相关文章

快手旗下——Kolors模型部署与使用指南

以下是按照要求重写后的 Kolors 模型部署与使用指南&#xff0c;文章风格偏技术性&#xff0c;但保持简洁和易懂的特点&#xff1a; Kolors 模型部署与使用指南 一、Kolors 简介 Kolors 是由快手 Kolors 团队开发的文本到图像生成模型&#xff0c;基于大规模的潜在扩散技术。…

vue-animate-onscroll动画库(可来回触发动画)

效果展示 ①触发一次动画 触发一次 ②触发多次动画 触发多次 1.什么是vue-animate-onscroll 它是一个 Vue 插件&#xff0c;用于在滚动时触发动画效果。它可以帮助开发者在用户滚动页面时&#xff0c;逐渐展示元素&#xff0c;增强用户体验。基本用法是通过在元素上添加特定的指…

Soul APP创始人张璐团队探讨新世代婚恋观:基于兴趣爱好的“轻相亲”正逐渐流行

近年来,随着社会经济的快速发展和文化观念的不断演变,婚恋观念正在经历显著变化。为深入了解当代年轻人对婚恋的态度与趋势,Soul APP创始人张璐团队与上海大学社会学青年研究团队合作,联合发布了《2024年青年婚恋观念及趋势调查报告》(以下简称“报告”)。该报告基于Soul APP用…

qml PathView入门

PathView是一个用于在用户界面中沿着定义的路径显示和滚动项目的视图组件。它提供了丰富的定制选项&#xff0c;允许开发者创建复杂的动画效果和自定义的滚动行为&#xff0c;特别适用于需要展示非线性排列项目的场景&#xff0c;如图片轮播、自定义滚动菜单等。 一、主要属性 …

[教程]如何在iPhone上启用中国移动/联通/电信RCS消息

目前 苹果已经在 iOS 18 中带来 RCS 富媒体消息的支持&#xff0c;该消息基于网络传递&#xff0c;用户可以通过 RCS 免费将消息发送到其他 iPhone 或 Android 设备。在苹果面向测试版用户推出的 iOS 18.1 Beta 版中&#xff0c;中国网络运营商包括中国移动、中国联通、中国电信…

JavaSE - 面向对象编程05

01 正则表达式 【1】概念&#xff1a;正则表达式是由一些特定字符组成的&#xff0c;代表的是一个规则。 【2】可以用来做什么&#xff1f; ① 用于校验数据格式的合法性 ② 用于在文本中爬取满足要求的内容 ③ 用于String类的replace方法&#xff0c;split方法的替换和分割 …

【学习笔记】Linux系统基础知识3 —— cd命令详解

一、前期准备 1.已经正确安装并成功进入Linux系统 说明&#xff1a;本实验采用的 Redhat 系统&#xff08;因系统不一致&#xff0c;可能部分显示存在差异&#xff09; 二、学习内容 提示&#xff1a;学习Linux系统基础命令 cd 命令详解 1、cd命令 1. 功能说明 cd 命令用…

Simple Calculator(算法初阶,代码基础,“纯”手撕)

简单计算器&#xff1a;仅适用无括号加减乘除&#xff0c;算法初阶&#xff0c;代码基础&#xff0c;不调库或模块“纯”手撕。 (笔记模板由python脚本于2024年09月22日 12:08:02创建&#xff0c;本篇笔记适合喜欢用python解决实际问题的coder翻阅) 【学习的细节是欢悦的历程】…

Qt中多语言的操作(以QtCreator为例)

1、首先&#xff0c;我们在代码中与文本相关的且需要支持多语言的地方&#xff0c;用tr来包含多语言key&#xff08;多语言key是我们自己定义的&#xff09;&#xff0c;如下 //举例 QPushButton* btnnew QPushButton(this); btn->move(20,20); btn->resize(100,50); //…

在 deepin 上除了 Steam,还能怎么玩游戏?

查看原文 前段时间&#xff0c;很多朋友在 deepin 23 上实现了《黑神话&#xff1a;悟空》的通关&#xff0c;那么除了通过 Steam 玩 Windows 游戏之外&#xff0c;还有其他可以使用的游戏平台吗&#xff1f; 回答&#xff0c;当然是可以哒&#xff01; 游戏平台介绍 今天介…

RHCSA认证-Linux(RHel9)-Linux入门

文章目录 概要一、创建、查看和编辑⽂本1.1 输出重定向1.2 vim编辑器1.3 shell 变量1.5 获取帮助 二、管理本地用户和组2.1 描述用户2.2 切换用户和赋权2.3 用户管理2.4 用户组管理2.5 密码策略 三、控制文件访问3.1 列出文件和文件权限3.2 更改文件权限和拥有者3.3 控制默认权…

昆明理工大学MBA工商管理上课方式

--昆工MBA考研、管理与经济学院、125100工商管理、125602项目管理、199管理类综合能力、F009 政治、F008政治项目管理概论

有关在.Net Core中以TEXT类型将Json格式字段存到数据库的学习

导言 在写个值日接口时发现值日表中的值日时段是可以分多段的&#xff0c;想了想可以使用Json类型来存&#xff0c;不过之前没接触过在后端操作Json格式存到数据库的情况&#xff0c;之后学也了解到了一下方法来实现&#xff0c;故记录一下。 过程 从前端到后端再到数据库的 JS…

千亿大数据平台——MySQL大数据优化

一、自增量的作用 1. 唯一性标识 - 为表中的每一行数据提供一个唯一的、自动生成的标识符&#xff0c;确保数据的唯一性和准确性。 2. 简化数据关联 - 在多个表之间进行关联操作时&#xff0c;自增量字段可以作为便捷的关联键&#xff0c;方便建立和维护表之间的关系…

C#和数据库高级:虚方法

文章目录 一、抽象方法和抽象类中的思考1.1、回顾抽象方法的特点1.2、针对抽象方法问题的引出 二、虚方法的使用步骤2.1、虚方法重写方法的调用2.2、系统自带的虚方法2.3、重写Equals方法2.4、虚方法和抽象方法的比较 三、虚方法和抽象方法的联系3.1、ToString()方法的应用 一、…

字母与符号检测系统源码分享

字母与符号检测检测系统源码分享 [一条龙教学YOLOV8标注好的数据集一键训练_70全套改进创新点发刊_Web前端展示] 1.研究背景与意义 项目参考AAAI Association for the Advancement of Artificial Intelligence 项目来源AACV Association for the Advancement of Computer V…

ChatGLM-6B-部署与使用

✨ Blog’s 主页: 白乐天_ξ( ✿&#xff1e;◡❛) &#x1f308; 个人Motto&#xff1a;他强任他强&#xff0c;清风拂山冈&#xff01; &#x1f4ab; 欢迎来到我的学习笔记&#xff01; 什么是ChatGLM-6B 一、简介 ChatGLM-6B 是由清华大学知识工程实验室&#xff08;KEG&…

Python:百度贴吧实现自动化签到

早知道&#xff0c;还是python。 Github项目仓库在这。 相关API 签到贴吧列表 签到分为两个接口&#xff0c;PC端签到一次经验2&#xff0c;而移动端签到则是一次经验6。该用哪个接口已经很明显了。不过这里还是列出PC端的签到API。 # PC端签到接口 # sign_url "https:…

时序预测 | Python实现KAN+LSTM时间序列预测

时序预测 | Python实现KAN+LSTM时间序列预测 目录 时序预测 | Python实现KAN+LSTM时间序列预测预测效果基本介绍程序设计预测效果 基本介绍 时序预测 | KAN+LSTM时间序列预测(Python) KAN作为这两年最新提出的机制,目前很少人用,很适合作为时间序列预测的创新点,可以结合…

python全栈开发《37.列表(元组)的count函数》

元组的count函数的功能和用法与列表的count函数完全一致。 1.count的功能 返回当前列表中某个成员的个数。 2.count的用法 fruits [苹果,西瓜,水蜜桃,西瓜,雪梨] count fruits.count(西瓜) print(count) 运行结果&#xff1a; 2 注意&#xff1a;列表的内置函数count拿到需要…