首先我们先了解一下临界段相关的函数:uCOS中临界段相关函数如下,除了宏定义 #define CPU_SR_ALLOC() CPU_SR cpu_sr = (CPU_SR)0 以外,主要包括两个函数:关中断和开中断。
/* 每次进入临界段时,都要在函数中调用宏CPU_SR_ALLOC()。其实从这里可以看出就是在函数中定义了
一个CPU_SR类型的局部变量cpu_sr*/
#define CPU_SR_ALLOC() CPU_SR cpu_sr = (CPU_SR)0
/*进入临界段可以简单的理解为开关中断*/
#define CPU_CRITICAL_ENTER() do { CPU_INT_DIS(); } while (0) //关中断
#define CPU_CRITICAL_EXIT() do { CPU_INT_EN(); } while (0) //开中断
/*uCOS常常把函数用typedef或者#define重定义为一个描述函数功能的名字,以便用户使用。
所以我们继续查看CPU_INT_DIS()和CPU_INT_EN()实现源码*/
#define CPU_INT_DIS() do { cpu_sr = CPU_SR_Save(); } while (0)
#define CPU_INT_EN() do { CPU_SR_Restore(cpu_sr); } while (0)
/*CPU_SR_Save()和CPU_SR_Restore()是用汇编语言写的,之所以用汇编是因为Cortex内核提供了
PRIMASK寄存器,使用 CPSID I 指令就能立即关闭中断。PRIMASK是只有 1 个位的寄存器。
当它置 1 时,就关掉所有可屏蔽的异常,只剩下 NMI和硬 fault 可以响应。它的缺省值是 0,
表示没有关中断。*/
CPU_SR_Save
MRS R0, PRIMASK ; 关闭中断,只剩下 NMI和硬 fault 可以响应
CPSID I
BX LR
CPU_SR_Restore
MSR PRIMASK, R0
BX LR
绕了一大转,其实进入临界段后的两个开关中断函数就是两个用汇编语言,通过修改PRIMASK寄存器值实现的。其中MRS R0, PRIMASK是将PRIMASK寄存器的值赋给R0寄存器。MSR PRIMASK, R0 是把R0寄存器的值赋给PRIMASK寄存器。这里R0是用来参数传递的。子函数通过R0寄存器将返回值传递给父函数,这里MRS R0, PRIMASK,R0的值在BX LR指令执行时返回。父函数通过R0传递子函数的入口参数,这里MSR PRIMASK, R0 就是把从父函数传递过来的参数送给PRIMASK寄存器。
所以为什么需要CPU_SR_ALLOC()?因为如果用常规的开关中断,即使用指令CPSID I关闭中断,使用指令CPSIE I开启中断;在嵌套临界段时,比如一重嵌套:关中断1--临界段--关中断2--临界段--开中断2--临界段--开中断1,在开关中断1中嵌套有开关中断2;我们想要的效果是在开中断1之前都不能打开中断,但是开中断2就已经使用指令打开了中断,那么就起不到保护临界段代码的作用。所以为了解决在嵌套时不会有开中断的情况发生,开关中断函数才有传参和返回的操作,这样CPU_SR_Restore()开中断其实就是恢复关中断之前的中断状态,并不是真正开中断,从而解决临界段嵌套开中断的问题。
那么使用有入口参数的开中断函数和有返回值的关中断函数是如何改变这种情况的?过程如下:
/*以一重嵌套为例*/
CPU_SR_ALLOC(); //进入临界段,就是CPU_SR cpu_sr = (CPU_SR)0;
/*关中断1,就是cpu_sr = CPU_SR_Save();看CPU_SR_Save()源代码,因为PRIMASK初始值尾0,
所以R0是0;然后在CPU_SR_Save()中调用了CPSID I关闭中断;最后返回R0的值,
所以cpu_sr是等于0*/
CPU_CRITICAL_ENTER();
{
临界段代码
/*关中断2,cpu_sr = CPU_SR_Save();通过上面分析已经知道返回的是PRIMASK寄存器的值,
因为关中断1中已经把寄存器值变为1;所以这里cpu_sr=1*/
CPU_CRITICAL_ENTER();
{
临界段代码
}
/*开中断2,就是CPU_SR_Restore(cpu_sr);看CPU_SR_Restore源码,首先将参数cpu_sr存入R0中,
然后赋值给PRIMASK寄存器,因为此时cpu_sr=1,所以PRIMASK值为1;所以不会在这里就开启中断*/
CPU_CRITICAL_EXIT(); //开中断2
临界段代码
}
/*开中断1,就是CPU_SR_Restore(cpu_sr);这里需要手动写一个cpu_sr=0,不然cpu_sr=1,调用
CPU_CRITICAL_EXIT()传给PRIMASK值永远都是1;那就永远不会开启中断*/
cpu_sr = 0;
CPU_CRITICAL_EXIT(); //开中断1
总结一下:就是在C中调用汇编语言编写的开中断函数,需要传递一个参数;调用关中断函数时,需要定义一个变量接收汇编关中断函数的返回值。因为我们一般都是每个临界段开始时关中断;临界段结束时开中断;有多少个临界段,就有多少个开关中断;嵌套临界段也不例外。所以在频繁使用开关中断时,需要保存程序现在中断的开关情况。所以这里保存的其实就是表示中断状态的寄存器PRIMASK。关中断需要有变量接收返回值,开中断需要把这个值当作参数传给函数,定义一个局部变量用来接收返回和传参就能避免临界段嵌套出错。