从0到1写RT_Thread内核 ——— 临界段保护的实现

  临界段通俗的解释就是一段不能被打断执行的代码,比如说再对内部FLASH进行写入时,可以加上临界段的保护,多线程对一个全局变量的操作时,加上临界段的保护可以避免一些意外的情况发生。比如这个线程在进行对此全局变量加操作,其他线程又在减操作。

  对临界段的打断都是中断的产生,无论是外部中断,还是线程切换(PenSV中断)。所以对临界段的保护一般都是在这段时间内进行关中断操作。

  以下为Cortex-M内核的快速关中断指令。

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

 Cortex-M内核之中有3个中断屏蔽寄存器,如下图所示

中断屏蔽寄存器 功能描述
PRIMASK 大小1bit。置位为1,关掉所有可屏蔽的异常,只有NMI(不可屏蔽中断)和硬件FAULT可以响应。默认为0
FAULIMASK 大小1bit。置位为1,只有NMI可以响应。默认为0
BASEPRI 定义了被屏蔽优先级的阈值。设定好以后,只有优先级比阈值高的可以响应。(优先级的数字越小,优先级越高)。0为默认值,不关闭任何中断。

以下为实现开关中断的汇编语言的实现

;/* 关中断
; * rt_base_t rt_hw_interrupt_disable();
; */
rt_hw_interrupt_disable    PROC
    EXPORT  rt_hw_interrupt_disable ;EXPORT关键字导出,让C文件调用此函数
    MRS     r0, PRIMASK    ;MRS 将特殊寄存器PRIMASK的值存储到r0
    CPSID   I
    BX      LR
    ENDP

;/* 开中断
; * void rt_hw_interrupt_enable(rt_base_t level);
; */
rt_hw_interrupt_enable    PROC
    EXPORT  rt_hw_interrupt_enable
    MSR     PRIMASK, r0  ;MRS 将通用寄存器r0的值存储到特殊寄存器PRIMASK
    BX      LR           ;子程序返回
    ENDP

这里面有一个小的技巧,代码中出现了MRS指令和MSR指令。通常按常理的情况下直接开关中断就可以了,为什么每次关中断的时候要将PRIMASK寄存器的值存储到r0,开中断时需要将r0寄存器的值存储到PRIMASK。这个是为临界段段嵌套而设计的一种做法。比如下面程序(示意程序)临界段嵌套:

; 开关中断函数的实现
;/*
; * void rt_hw_interrupt_disable();
; */
rt_hw_interrupt_disable PROC
EXPORT rt_hw_interrupt_disable
CPSID I
BX LR
ENDP

;/*
; * void rt_hw_interrupt_enable(void);
; */
rt_hw_interrupt_enable PROC
EXPORT rt_hw_interrupt_enable
CPSIE I
BX LR
ENDP

PRIMASK = 0; /* PRIMASK 初始值为 0,表示没有关中断 */

/* 临界段代码 */
{
    /* 临界段 1 开始 */
    rt_hw_interrupt_disable(); /* 关中断,PRIMASK = 1 */
    {
        /* 临界段 2 */
        rt_hw_interrupt_disable(); /* 关中断,PRIMASK = 1 */
       
        rt_hw_interrupt_enable(); /* 开中断,PRIMASK = 0 */ (注意)
    }
    /* 临界段 1 结束 */
    rt_hw_interrupt_enable(); /* 开中断,PRIMASK = 0 */
}

 分析以上程序,当第一次关闭中断时,PRIMASK=1,确实关闭中断了。此时有进行嵌套临界段保护,再一次关闭中断,PRIMASK=1。前面一切正常,但是当进行到开启内嵌中断时,此时PRIMASK = 0。开启中断成功,可是这只是开启的内嵌,却把全部中断都打开了,我们临界段1还没结束,所有的中断就已经打开了。注意:在开关中断中,没有保存特殊寄存器和读取特殊寄存器。

 

由此,采用了以下的方法来避免了这个临界段嵌套的问题,在中断开关中使用了MRS指令和MSR指令。

理解下面的程序时,需要注意一个基本的知识点: 当一个汇编函数在 C 文件中调用的时候,如果有一个形参,则执行的时候会将这个 形参传入到 CPU 寄存器 r0,如果有两个形参第二个则传入到 r1 。

;/* 关中断
; * rt_base_t rt_hw_interrupt_disable();
; */
rt_hw_interrupt_disable PROC
EXPORT rt_hw_interrupt_disable
MRS r0, PRIMASK ;将PRIMASK的值存储到R0
CPSID I
BX LR  ;这里会返回r0的值作为函数的返回值
ENDP

;/* 开中断
; * void rt_hw_interrupt_enable(rt_base_t level);
; */
rt_hw_interrupt_enable PROC
EXPORT rt_hw_interrupt_enable
MSR PRIMASK, r0  ;函数形参的值传入后自动保存到r0
BX LR
ENDP

PRIMASK = 0; /* PRIMASK 初始值为 0,表示没有关中断 */ 
rt_base_t level1; 
rt_base_t level2;

/* 临界段代码 */
{
    /* 临界段 1 开始  用level1 来接收返回值*/
    level1 = rt_hw_interrupt_disable(); /* 关中断,level1=0,PRIMASK=1 */ 
    {
        /* 临界段 2 */
        level2 = rt_hw_interrupt_disable(); /* 关中断,level2=1,PRIMASK=1 */ 
        {
        }
        rt_hw_interrupt_enable(level2); /* 开中断,level2=1,PRIMASK=1 */  
    }
    /* 临界段 1 结束 */
    rt_hw_interrupt_enable(level1); /* 开中断,level1=0,PRIMASK=0 */ 
}

                                                                                                以上内容来自于野火的<<RT_thread内核实现>>的学习与实践笔记记录。

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章