RTTheard学习笔记-全局中断及临界操作分析
本文将以Cortx-M3为例说明RTThread开关中断的过程以及进入临界区的相关操作:
1、全局中断
对于Cortx-M3系列MCU ,RTThread是通过操作,中断屏蔽寄存器PRIMASK实现开关中断操作的,《Cortex-M3权威指南》中对PRIMASK寄存器有描述如下:
这个是只有1位的存储器,当它置1时,就关闭所有可屏蔽的异常,只剩下NMI和fault可以响应。它的缺省值是0,表示没有关中断。
由此可见只要对PRIMASK置1就可实现关中断操作,对PRIMASK清0就可实现开中断操作;为了快速的开关中断CM3专业设置了一条CPS指令实现相应的操作
CPSID I ; PRIMASK =1 关中断
CPSIE I ; PRIMASK =0 开中断
下面重点讨论一下RTThread中开关中断的具体实现过程,RTThread开关中断函数都是通过汇编函数来实现的,实际操作的时候是在C中调用相应的汇编函数来完成开关中断操作,开关中断函数都在rthw.h里面
rt_base_t rt_hw_interrupt_disable(void); //关中断
void rt_hw_interrupt_enable(rt_base_t level); //开中断
-
关中断函数
;/* ; * rt_base_t rt_hw_interrupt_disable(); ; */ rt_hw_interrupt_disable PROC (1-1) EXPORT rt_hw_interrupt_disable (2) MRS r0, PRIMASK (3) CPSID I (4) BX LR (5) ENDP (1-2)-
(1-1)PROC 是定义子程序的伪指令,位于子程序的开始位置,必须和ENDP成对出现。
-
(1-2)ENDP 位于子程序的末尾,必须和PROC成对出现。
-
使用 EXPORT 关键字导出标号 rt_hw_interrupt_disable,使其具有全 局熟悉,在外部头文件声明后(在 rthw.h 中声明) ,就可以在 C 文件中调用。
-
(3 )通过 MRS 指令将特殊寄存器 PRIMASK 寄存器的值存储到通用寄 存器 r0。当在 C 中调用汇编的子程
序返回时,会将 r0 作为函数的返回值。比如在C中调用 rt_hw_interrupt_disable() 的时候,操作如下
value = rt_hw_interrupt_disable() ;那么此时value存储的就是r0 寄存器的值,也就是 PRIMASK 的值 。
MRS 和 MSR
这两条指令是访问特殊功能寄存器的“绿色通道” ——当然必须在特权级下,除 APSR 外。 指令语法如下:
MRS , ;加载特殊功能寄存器的值到 Rn
MSR , ;存储 Rn 的值到特殊功能寄存器 SReg
AAPCS (ARM架构过程调用标准 ) 中给出给出了参数和返回值传递规则:对于简单情况,输入参数由r0-r4分别记录第1到第4个参数,当输入参数超过4个时候就需要借助堆栈来保存参数。函数的返回值通常保存在r0中,若返回值为64位,r1也用来保存返回值。
-
(4) 闭中断,即使用 CPS 指令将 PRIMASK 寄存器的值置 1 。
-
(5)函数返回。
总结:综上rt_hw_interrupt_disable函数实际上完成了两项操作,第一关管局中断即置1 PRIMASK寄存器;第二返回关闭中断之前 PRIMASK寄存器的状态。这里返回关闭前PRIMASK的状态值的目标是为了方便实现关闭中断操作的嵌套使用,后续会进一步展开讨论。
-
-
开中断函数
;/* ; * void rt_hw_interrupt_enable(rt_base_t level); ; */ rt_hw_interrupt_enable PROC EXPORT rt_hw_interrupt_enable MSR PRIMASK, r0 (1) BX LR (2) ENDP- (1) 更新r0的值到PRIMASK,此处r0保存的其实是实参level的值
- (2) 函数返回
总结:rt_hw_interrupt_enable主要功能是根据传入参数level的状态来设置PRIMASK的值,并不是单纯的开中断操作,实际应用的时候level一般是进入临界段之前保存的 PRIMASK 的值 。
2、临界区操作
进入临界区我们一般需要操作全局变量,RTThread是通过关全局中断和开全局中断来实现的。
示例1:
……
//进入临界区
rt_hw_interrupt_disable();//关中断
……
rt_hw_interrupt_enable(0);//开中断
//退出临界区c
……
上面示例展示了进出临界区的一种处理方法,实际上我们一般不这么操作。上述示例在临界区没有嵌套的情况下没有问题,一旦临界区有嵌套就会出现问题。
示例2:
……
//进入临界区1
rt_hw_interrupt_disable();//(1)关中断
……
//进入临界区2
rt_hw_interrupt_disable();//(2)关中断
……
rt_hw_interrupt_enable(0);//(3)开中断
//退出临界区2
……
rt_hw_interrupt_enable(0);//(4)开中断
//退出临界区1
……
示例2中存在的问题就是在第(1)步 的时候关闭了中断,在第(3)部的时候开启了中断。这样的结果跟我们的意愿是不相符的,我们期望到第(4)部的时候才能开中断。
对于前面第1部分RTThread中开全局关中断的实现起初我有些疑惑,为什么不直接用CPSID I 和CPSIE I 指令直接实现,还要绕那么多弯子。在后续临界区嵌套操作的时候才彻底明白这样设计的精髓。如果直接用CPS实现全局开中关中断的话,就会在临界区嵌套的时候就会出现示例2的现象。下面我们看看RTThread“绕了一圈”后是如何解决这个问题的。
示例3:
……
rt_base_t level1;
rt_base_t level2;
//进入临界区1
level1 = rt_hw_interrupt_disable();//(1)关中断 level1=0,PRIMASK=1 全局中断关闭
……
//进入临界区2
rt_hw_interrupt_disable(level1);//(2)关中断 level2=1,PRIMASK=1 全局中断关闭
……
level2 = rt_hw_interrupt_enable();//(3)开中断 level2=1,PRIMASK=1 全局中断关闭
//退出临界区2
……
rt_hw_interrupt_enable(level2);//(4)开中断 level1=0,PRIMASK=0 全局中断打开
//退出临界区1
……
按照示例3这种操作方法便可以解决示例2中出现的问题。