临界段通俗的解释就是一段不能被打断执行的代码,比如说再对内部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内核实现>>的学习与实践笔记记录。