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); //开中断

  1. 关中断函数

    ;/*
    ; * 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的状态值的目标是为了方便实现关闭中断操作的嵌套使用,后续会进一步展开讨论。

  2. 开中断函数

    ;/*
    ; * 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中出现的问题。

你可能感兴趣的:(嵌入式操作系统)