freertos临界段保护

freertos临界段保护

    • 中断的基础知识
    • cortex-m里面开中断、关中断指令
    • 关中断和开中断
    • 进入临界段和退出临界段

中断的基础知识

嵌套:

嵌套向量中断控制器 NVIC(Nested Vectored Interrupt Controller与内核是紧耦合的。提供如下的功能:可嵌套中断支持、向量中断支持、动态优先级调整支持、中断延迟大大缩短、 中断可屏蔽。

所有的外部中断和绝大多数系统异常均支持可嵌套中断。异常都可以被赋予不同的优先级,当前优先级被存储在 xPSR的专用字段中。当一个异常发生时,硬件自动比较该异常的优先级和当前的异常优先级,如果发现该异常的优先级更高,处理器就会中断当前的中断服务例程(或者是普通程序),而服务新来的异常(立即抢占)。

如果优先级组设置使得中断嵌套层次很深,要确认主堆栈空间足够用。 异常服务程序总是使用MSP,主堆栈的容量应是嵌套最深时需要的量。


优先级:

CM3 支持中断嵌套,使得高优先级异常会抢占(preempt)低优先级异常。

有3个系统异常:复位,NMI 以及硬 fault,它们有固定的优先级,并且优先级号是负数,高于所有其它异常。所有其它异常的优先级都是可编程的(但不能编程为负数)。

CM3 支持3个固定的高优先级和多达256级的可编程优先级,并且支持128级抢占。但是大多数CM3芯片实际上支持的优先级数会更少如8级、16级、32级等。

裁掉表达优先级的几个低端有效位,从而让优先级数减少。如果使用更多的位来表达优先级,优先级数增加,需要的门也更多,带来更多的成本和功耗。

使用3个位来表达优先级,优先级配置寄存器的结构如下图所示,能够使用的8个优先级为:0x00(最高),0x20,0x40,0x60,0x80, 0xA0,0xC0,0xE0。

在这里插入图片描述

为了使抢占机能变得更可控,CM3 把 256 级优先级按位分成高低两段,分别是抢占优先级和亚优先级。抢占优先级决定了抢占行为。当抢占优先级相同的异常有不止一个悬起时,就优先响应亚优先级最高的异常(亚优先级处理内务)。

优先级分组规定:亚优先级至少是1个位。所以抢占优先级最多是7个位,最多只有 128 级抢占的现象。

下图是只使用 3 个位来表达优先级,从bit5处分组,得到4级抢占优先级,每个抢占优先级的内部有2个亚优先级。

在这里插入图片描述

下图是3 位优先级,从比特1处分组,(虽然[4:0]未使用,却允许从它们中分组)。

在这里插入图片描述

应用程序中断及复位控制寄存器(AIRCR)(地址:0xE000_ED00)如下。

在这里插入图片描述


中断的悬起与解悬:

中断发生时,正在处理同级或高优先级异常,或者被掩蔽,则中断不能立即得到响应。此时中断被悬起。

可以通过中断设置悬起寄存器(SETPEND)、中断悬起清除寄存器(CLRPEND)读取中断的悬起状态,还可以写它们来手工悬起中断。


咬尾中断Tail‐Chaining:

处理器在响应某异常时,如果又发生其优先级高的异常,当前异常被阻塞,转而执行优先级高的异常。那么异常执行返回后,系统处理悬起的异常时,如果先POP再把POP出的再PUSH回去,这就是浪费CPU时间。

所以CM3不POP这些寄存器,而是继续使用上一个异常已经PUSH好的成果。如下图所示。

在这里插入图片描述


晚到的高优先级异常:

入栈的阶段,尚未执行其服务例程时,如果此时收到了高优先级异常的请求,入栈后,将执行高优先级异常的服务例程。如果高优先级异常来得太晚,以至于已经执行了前一个异常的指令,则按普通的抢占处理,这会需要更多的处理器时间和额外32字节的堆栈空间。高优先级异常执行完毕后,以咬尾中断方式执行之前被抢占的异常。

cortex-m里面开中断、关中断指令

临界段:一段在执行的时候不能被中断的代码段(必须完整运行、不能被打断的代码段)。一般是对全局变量操作时候,用到临界段。当一个任务在访问某个全局变量时,如果被其他中断打断,改变了该全局变量,再回到上个任务时,全局变量已经不是当时的它了,这种情况可能会导致不可意料的后果。

临界段被打断的情况:系统调度(最终也是产生PendSV中断);外部中断。

freertos进入临界段代码时需要关闭中断,处理完临界段代码再打开中断。

首先看下面的代码。

__asm void prvStartFirstTask( void )
{PRESERVE8/* 在Cortex-M中,0xE000ED08是SCB_VTOR这个寄存器的地址,里面存放的是向量表的起始地址,即MSP的地址 */ldr r0, =0xE000ED08ldr r0, [r0]ldr r0, [r0]/* 设置主堆栈指针msp的值 */msr msp, r0/* 使能全局中断 */cpsie icpsie fdsbisb/* 调用SVC去启动第一个任务 */svc 0  nopnop
}
__asm void vPortSVCHandler( void )
{extern pxCurrentTCB;PRESERVE8ldr	r3, =pxCurrentTCB	/* 加载pxCurrentTCB的地址到r3 */ldr r1, [r3]			/* 加载pxCurrentTCB到r1 */ldr r0, [r1]			/* 加载pxCurrentTCB指向的值到r0,目前r0的值等于第一个任务堆栈的栈顶 */ldmia r0!, {r4-r11}		/* 以r0为基地址,将栈里面的内容加载到r4~r11寄存器,同时r0会递增 */msr psp, r0				/* 将r0的值,即任务的栈指针更新到psp */isbmov r0, #0              /* 设置r0的值为0 */msr	basepri, r0         /* 设置basepri寄存器的值为0,即所有的中断都没有被屏蔽 */orr r14, #0xd           bx r14                  
}
__asm void xPortPendSVHandler( void )
{extern pxCurrentTCB;extern vTaskSwitchContext;PRESERVE8/* 当进入PendSVC Handler时,上一个任务运行的环境即:xPSR,PC(任务入口地址),R14,R12,R3,R2,R1,R0(任务的形参)这些CPU寄存器的值会自动保存到任务的栈中,剩下的r4~r11需要手动保存 *//* 获取任务栈指针到r0 */mrs r0, pspisbldr	r3, =pxCurrentTCB		/* 加载pxCurrentTCB的地址到r3 */ldr	r2, [r3]                /* 加载pxCurrentTCB到r2 */stmdb r0!, {r4-r11}			/* 将CPU寄存器r4~r11的值存储到r0指向的地址 */str r0, [r2]             /* 将任务栈的新的栈顶指针存储到当前任务TCB的第一个成员,即栈顶指针 */ stmdb sp!, {r3, r14}        /* 将R3和R14临时压入堆栈 */mov r0, #configMAX_SYSCALL_INTERRUPT_PRIORITY    /* 进入临界段 */msr basepri, r0dsbisbbl vTaskSwitchContext       /* 调用函数vTaskSwitchContext,寻找新的任务运行,通过使变量pxCurrentTCB指向新的任务来实现任务切换 */ mov r0, #0                  /* 退出临界段 */msr basepri, r0ldmia sp!, {r3, r14}        /* 恢复r3和r14 */ldr r1, [r3]ldr r0, [r1] 				/* 当前激活的任务TCB第一项保存了任务堆栈的栈顶,现在栈顶值存入R0*/ldmia r0!, {r4-r11}			/* 出栈 */msr psp, r0isbbx r14                      nop
}

上面这些代码,可以看到有下面这些指令。

cpsie i
cpsie f
msr	basepri, r0

为了快速地开关中断,CM3 专门设置了 CPS 指令,有 4 种用法。

CPSID I ;PRIMASK=1, ;关中断
CPSIE I ;PRIMASK=0, ;开中断
CPSID F ;FAULTMASK=1, ;关异常
CPSIE F ;FAULTMASK=0 ;开异常

可以看到,上面指令还是控制的PRIMASK和FAULTMASK寄存器。

如下图所示,可以通过CPS 指令打开全局中断或者关闭全局中断。

在这里插入图片描述

basepri是中断屏蔽寄存器,下面这个设置,优先级大于等于11的中断都将被屏蔽。相当于关中断进入临界段。

mov r0, #configMAX_SYSCALL_INTERRUPT_PRIORITY 
msr basepri, r0
/*
#define configMAX_SYSCALL_INTERRUPT_PRIORITY 	191   /* 高四位有效,即等于0xb0,或者是11 */
191转成二进制就是11000000,高四位就是1100
*/

下面这个代码:优先级高于0的中断被屏蔽,相当于是开中断退出临界段。

mov r0, #0                  /* 退出临界段 */
msr basepri, r0

关中断和开中断

下面这个代码,带返回值的意思是:往BASEPRI写入新的值的时候,先将BASEPRI的值保存起来,更新完BASEPRI的值的时候,将之前保存好的BASEPRI的值返回,返回的值作为形参传入开中断函数。

不带返回值的意思是:在往 BASEPRI 写入新的值的时候,不用先将 BASEPRI 的值保存起来, 不用管当前的中断状态是怎么样的,既然不用管当前的中断状态,也就意味着这样的函数不能在中断里面调用。

/*portmacro.h*//*不带返回值的关中断函数,不能嵌套,不能在中断中使用*/
#define portDISABLE_INTERRUPTS()				vPortRaiseBASEPRI()
/*不带中断保护的开中断函数*/
#define portENABLE_INTERRUPTS()					vPortSetBASEPRI( 0 )/*带返回值的关中断函数,可以嵌套,可以在中断里面使用*/
#define portSET_INTERRUPT_MASK_FROM_ISR()		ulPortRaiseBASEPRI()
/*带中断保护的开中断函数*/
#define portCLEAR_INTERRUPT_MASK_FROM_ISR(x)	vPortSetBASEPRI(x)#define configMAX_SYSCALL_INTERRUPT_PRIORITY 	191   /* 高四位有效,即等于0xb0,或者是11 *//*不带返回值的关中断函数*/
static portFORCE_INLINE void vPortRaiseBASEPRI( void )
{
uint32_t ulNewBASEPRI = configMAX_SYSCALL_INTERRUPT_PRIORITY;__asm{/* Set BASEPRI to the max syscall priority to effect a criticalsection. */msr basepri, ulNewBASEPRIdsbisb}
}
/*带返回值的关中断函数*/
static portFORCE_INLINE uint32_t ulPortRaiseBASEPRI( void )
{
uint32_t ulReturn, ulNewBASEPRI = configMAX_SYSCALL_INTERRUPT_PRIORITY;__asm{/* Set BASEPRI to the max syscall priority to effect a criticalsection. */mrs ulReturn, baseprimsr basepri, ulNewBASEPRIdsbisb}return ulReturn;
}
/*不带中断保护的开中断函数和带中断保护的开中断函数,区别在于参数的值*/
static portFORCE_INLINE void vPortSetBASEPRI( uint32_t ulBASEPRI )
{__asm{/* Barrier instructions are not used as this function is only used tolower the BASEPRI value. */msr basepri, ulBASEPRI}
}

进入临界段和退出临界段

对于不带中断保护情况,vPortEnterCritical函数里面的uxCriticalNesting是一个全局变量,记录临界段嵌套次数,vPortExitCritical函数每次将uxCriticalNesting减一,只有当uxCriticalNesting = 0才会调用portENABLE_INTERRUPTS函数使能中断。这样的话,在有多个临界段代码的时候,不会因为某一个临界段代码的退出而打断其他临界段的保护,只有所有的临界段代码都退出后,才会使能中断。

带中断保护的,主要就是往BASEPRI写入新的值的时候,先将BASEPRI的值保存起来,更新完BASEPRI的值的时候,将之前保存好的BASEPRI的值返回,返回的值作为形参传入开中断函数。

/*进入临界段,不带中断保护*/
#define taskENTER_CRITICAL()		       portENTER_CRITICAL()
/*退出临界段,不带中断保护*/
#define taskEXIT_CRITICAL()			       portEXIT_CRITICAL()/*进入临界段,带中断保护,可以嵌套*/
#define taskENTER_CRITICAL_FROM_ISR()      portSET_INTERRUPT_MASK_FROM_ISR()
/*退出临界段,带中断保护,可以嵌套*/
#define taskEXIT_CRITICAL_FROM_ISR( x )    portCLEAR_INTERRUPT_MASK_FROM_ISR( x )/*进入临界段,不带中断保护*/
#define portENTER_CRITICAL()					vPortEnterCritical()
/*退出临界段,不带中断保护*/
#define portEXIT_CRITICAL()						vPortExitCritical()/*进入临界段,带中断保护,可以嵌套*/
#define portSET_INTERRUPT_MASK_FROM_ISR()		ulPortRaiseBASEPRI()
/*退出临界段,带中断保护,可以嵌套*/
#define portCLEAR_INTERRUPT_MASK_FROM_ISR(x)	vPortSetBASEPRI(x)/*进入临界段,不带中断保护*/
void vPortEnterCritical( void )
{/*不带返回值的关中断函数,不能嵌套,不能在中断中使用*/portDISABLE_INTERRUPTS();uxCriticalNesting++;if( uxCriticalNesting == 1 ){configASSERT( ( portNVIC_INT_CTRL_REG & portVECTACTIVE_MASK ) == 0 );}
}
/*退出临界段,不带中断保护*/
void vPortExitCritical( void )
{configASSERT( uxCriticalNesting );uxCriticalNesting--;if( uxCriticalNesting == 0 ){/*不带中断保护的开中断函数*/portENABLE_INTERRUPTS();}
}/*进入临界段,带中断保护,可以嵌套*/
static portFORCE_INLINE uint32_t ulPortRaiseBASEPRI( void )
{
uint32_t ulReturn, ulNewBASEPRI = configMAX_SYSCALL_INTERRUPT_PRIORITY;__asm{/* Set BASEPRI to the max syscall priority to effect a criticalsection. */mrs ulReturn, baseprimsr basepri, ulNewBASEPRIdsbisb}return ulReturn;
}
/*退出临界段,带中断保护,可以嵌套*/
static portFORCE_INLINE void vPortSetBASEPRI( uint32_t ulBASEPRI )
{__asm{/* Barrier instructions are not used as this function is only used tolower the BASEPRI value. */msr basepri, ulBASEPRI}
}
/*临界段代码的应用场合*/
/* 在中断场合,临界段可以嵌套 */
{uint32_t ulReturn;/* 进入临界段,临界段可以嵌套 */ulReturn = taskENTER_CRITICAL_FROM_ISR();/* 临界段代码 *//* 退出临界段 */taskEXIT_CRITICAL_FROM_ISR( ulReturn );
}/* 在非中断场合,临界段不能嵌套 */
{/* 进入临界段 */taskENTER_CRITICAL();/* 临界段代码 *//* 退出临界段*/taskEXIT_CRITICAL();
}

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

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

相关文章

改进版的CBOW模型

复习 首先复习一下之前的CBOW笔记。 采用推理的方法认知单词、CBOW模型这里面主要是: CBOW模型的核心思路:给出周围的单词(上下文)时,预测目标词处会出现什么单词。 要用神经网络处理单词,需要先将单词…

freertos空闲任务、阻塞延时

freertos空闲任务、阻塞延时空闲任务阻塞延时SysTick实验现象阻塞态:如果一个任务当前正在等待某个外部事件,则称它处于阻塞态。 rtos中的延时叫阻塞延时,即任务需要延时的时候,会放弃CPU的使用权,进入阻塞状态。在任务阻塞的这段…

树莓派配置

树莓派配置1.安装操作系统2.修改镜像源3.配置VNC连接1.安装操作系统 安装操作系统,首先在官网下载https://www.raspberrypi.com/software/operating-systems/ 下载出来的解压后是一个光盘映像文件,接下来就需要把这个文件写入到树莓派的tf卡里。 安装树…

远程桌面连机器人

这里需要PC和机器人处于同一局域网下。 PC使用xhell连接机器人,通过 ifconfig 查看机器人的无线 IP 地址。 然后退出xshell,打开NoMachine软件进行远程桌面连接。输入机器人无线ip地址。 输入用户名和密码 然后就远程连接上了 现在开一个终端如下所示&…

RNNLM

RNNLM 基于RNN的语言模型称为RNNLM(Language Model)。 Embedding 层:将单词ID转化为单词的分布式表示(单词向量)。 RNN层:向下一层(上方)输出隐藏状态,同时也向下一时刻的RNN层(右边)输出隐藏状态。 对于“you say …

使用c#制作赛尔号登录器

使用c#制作赛尔号登录器 需求: 打开赛尔号官网,发现我的chrome浏览器无法运行Flash。这是因为Adobe 公司放弃了对 Flash Player 的支持。 那么如果我想要玩游戏,又不想下载别的浏览器,只好自己写一个登陆器了。 创建项目 首先…

普通RNN的缺陷—梯度消失和梯度爆炸

之前的RNN,无法很好地学习到时序数据的长期依赖关系。因为BPTT会发生梯度消失和梯度爆炸的问题。 RNN梯度消失和爆炸 对于RNN来说,输入时序数据xt时,RNN 层输出ht。这个ht称为RNN 层的隐藏状态,它记录过去的信息。 语言模型的任…

LSTM的结构

RNN和LSTM 简略表示RNN层:长方形节点中包含了矩阵乘积、偏置的和、tanh函数的变换。将下面这个公式表示成一个tanh节点。 LSTM:Long Short-Term Memory(长短期记忆),长时间维持短期记忆。 LSTM与RNN的接口(输入输出)…

STM32 USART 补充

串口通讯的数据包:发送设备通过自身的TXD接口传输到接收设备的RXD接口。 串口通讯的协议层中,规定了数据包的内容,由起始位、主体数据、校验位、停止位组成,通讯双方的数据包格式要约定一致才能正常收发数据。 异步通讯&#xf…

ROS TF变换

静态坐标转换:机器人本体中心到雷达中心的转换。因为激光雷达可能没安装到机器人的中心。 动态坐标转换:机器人中心和里程计坐标的变换。机器人从起点出发后,里程计坐标相对于本体就会产生一个偏移,这个偏移随着机器人的运动不断…

ROS底盘控制节点 源码分析

先在机器人端通过launch文件启动底盘控制。 robot:~$ roslaunch base_control base_control.launch ... logging to /home/jym/.ros/log/3e52acda-914a-11ec-beaa-ac8247315e93/roslaunch-robot-8759.log Checking log directory for disk usage. This may take a while. Pres…

ROS + OpenCV

视觉节点测试 先进行一些测试。并记录数据。 圆的是节点,方的是话题。 1.robot_camera.launch robot:~$ roslaunch robot_vision robot_camera.launch ... logging to /home/jym/.ros/log/bff715b6-9201-11ec-b271-ac8247315e93/roslaunch-robot-8830.log Check…

ROS+雷达 运行数据记录

先测试一下雷达,记录数据。方便接下来分析源码。 1.roslaunch robot_navigation lidar.launch robot:~$ roslaunch robot_navigation lidar.launch ... logging to /home/jym/.ros/log/7136849a-92cc-11ec-acff-ac8247315e93/roslaunch-robot-9556.log Checking l…

ROS 找C++算法源码的方法

在gmapping的launch文件中看到,type“slam_gmapping”,这里的slam_gmapping是c编译后的可执行文件。 如果想要修改gmapping算法,就需要找到slam_gmapping的c源码。 但是这是用apt下载的包,是二进制类型的,没有下载出…

ros 雷达 slam 导航 文件分析

ros 雷达 slam 导航 文件分析robot_slam_laser.launchrobot_lidar.launchlidar.launchraplidar.launchkarto.launchgmapping.launchcartographer.launchrobot_navigation.launchmap.yamlmap.pgmamcl_params.yamlmove_base.launchcostmap_common_params.yamllocal_costmap_param…

Apprentissage du français partie 1

Apprentissage du franais partie 1 键盘转换图: 字母:26个 元音字母:a、e、i、o、u、y b浊辅音(声带)-p清辅音 d-t 音符 音符:改变字母发音。 :闭音符 [e] :开音符 /ε/ :长音符 /ε/…

stm32基本定时器

定时器分类 stm32f1系列,8个定时器,基本定时器(TIM6,7)、通用定时器(TIM2,3,4,5)、高级定时器(TIM1,8)。 基本定时器:16位,只能向上计数的定时器,只能定时,没有外部IO 通用定时器:16位&#…

stm32高级定时器 基础知识

stm32高级定时器 高级定时器时基单元: 包含一个16位自动重装载寄存器 ARR 一个16位的计数器CNT,可向上/下计数 一个16位可编程预分频器PSC,预分频器时钟源有多种可选,有内部的时钟、外部时钟。 一个8位的重复计数器 RCR&…

stm32 PWM互补输出

stm32高级定时器例子—stm32 PWM互补输出 定时器初始化结构体 TIM_TimeBaseInitTypeDef 时基结构体,用于定时器基础参数设置,与TIM_TimeBaseInit函数配合使用,完成配置。 typedef struct { TIM_Prescaler /*定时器预分频器设置&…

stm32 输入捕获 测量脉宽

选用通用定时器TIM5的CH1。 PA0接一个按键,默认接GND,当按键按下时,IO口被拉高,此时,可利用定时器的输入捕获功能,测量按键按下的这段高电平的时间。 宏定义方便程序升级、移植,举个例子&#…