参考内容:《[野火]uCOS-III内核实现与应用开发实战指南——基于STM32》第 10 章。
临界段,又叫做临界区。对于多线程而言,它是一段不可分割、不可上下文切换的代码。对于 uCOS 而言,它是一段不可被中断的代码。临界段是不能被中断的,需要关中断或锁调度器(OSSched)以保护临界段。
什么情况下临界段代码会被打断?由刚才的描述可知,临界段被打断有两种情况:
因此,对临界段保护的实质就是控制中断的开启和关闭。
uCOS 定义了进入临界段的宏和退出临界段的宏,这两个宏分别实现了中断的开启和关闭。
还有一个宏,用于存储中断的状态:
在 CM 内核中,中断是通过 CPS 指令来控制的。
CPSID I ;PRIMASK=1,关中断
CPSIE I ;PRIMASK=0,开中断
CPSID F ;FAULTMASK=1,关异常
CPSIE F ;FAULTMASK=0,开异常
PRIMASK 和 FAULTMAST 是 CM 内核里面三个中断屏蔽寄存器中的两个,还有一个是 BASEPRI,最后一个用不到,就不进行介绍了。
因此,也可以通过 MSR 指令修改 PRIMASK(或 FAULTMASK)来开启或关闭中断:
MOVS R0, #1
MSR PRIMASK, R0 ; 将 1 写入 PRIMASK 禁止所有中断
MOVS R0, #0
MSR PRIMASK, R0 ; 将 0 写入 PRIMASK 使能中断
该函数完成的事情:
; CPU_SR CPU_SR_Save (void); (临界段关中断,R0 为返回值)
CPU_SR_Save
MRS R0, PRIMASK ; 将 PRIMASK 寄存器的值存入 R0 中
CPSID I ; 关中断
BX LR
该函数完成的事情:
; void CPU_SR_Restore (CPU_SR cpu_sr); (临界段开中断,R0 为形参)
CPU_SR_Restore
MSR PRIMASK, R0 ; 将 R0 的值存入 PRIMASK 寄存器中
BX LR
为什么开中断不直接使用 CPS 指令呢?待会在应用那节(2.3.2 节)你就会明白了。
最后,在 cpu.h 中,将开中断和关中断的函数封装成一个宏,方便调用。
/*********************************CPU寄存器数据类型定义*********************************/
typedef volatile CPU_INT32U CPU_REG32;
typedef CPU_REG32 CPU_SR;
/*********************************临界段定义*********************************/
#define CPU_SR_ALLOC() CPU_SR cpu_sr = (CPU_SR)0 // 用于存放中断状态
#define CPU_INT_DIS() do { cpu_sr = CPU_SR_Save(); } while(0) // 关闭中断,存储中断状态
#define CPU_INT_EN() do { CPU_SR_Restore(cpu_sr); } while(0) // 恢复中断状态
#define CPU_CRITICAL_ENTER() do { CPU_INT_DIS(); } while(0)
#define CPU_CRITICAL_EXIT() do { CPU_INT_EN(); } while(0)
/*********************************函数声明(cpu_a.asm)*********************************/
void CPU_IntDis (void);
void CPU_IntEn (void);
CPU_SR CPU_SR_Save (void);
void CPU_SR_Restore (CPU_SR cpu_sr);
如果有这么一段临界段代码:
/* 临界段代码保护 */
{
/* 临界段开始 */
{
/* 执行临界段代码,不可中断 */
}
/* 临界段结束 */
}
那么使用以上宏定义的格式为:
/* 临界段代码保护 */
{
CPU_SR_ALLOC(); /* cpu_sr = 0 */
CPU_INT_DIS(); /* 关中断 */
/* 临界段开始 */
{
/* 执行临界段代码,不可中断 */
}
/* 临界段结束 */
CPU_INT_EN(); /* 开中断 */
}
若将其展开,则变成:
/* 临界段代码保护 */
{
CPU_SR cpu_sr = (CPU_SR)0; /* (a) 定义一个变量,用于存放中断状态,初始化 cpu_sr = 0 */
cpu_sr = CPU_SR_Save(); /* (b) cpu_sr 保存当前中断状态,然后关中断 */
/* 临界段开始 */
{
/* 执行临界段代码,不可中断 */
}
/* 临界段结束 */
CPU_SR_Restore(cpu_sr); /* (c) cpu_sr 写入中断状态,恢复之前的中断状态 */
}
这个过程如下:
如果是两层临界段代码的嵌套:
/* 临界段代码保护 */
{
/* 临界段 1 开始 */
{
/* 临界段 2 开始 */
{
/* 执行临界段代码,不可中断 */
}
/* 临界段 2 结束 */
}
/* 临界段 1 结束 */
}
进入临界段 1 前,需要关闭中断;进入临界段 2 前,也需要关闭中断,那么使用宏定义的格式为:
/* 临界段代码保护 */
{
CPU_SR_ALLOC(); /* cpu_sr = 0 */
CPU_INT_DIS(); /* 关中断(临界段 1) */
/* 临界段 1 开始 */
{
CPU_SR_ALLOC(); /* cpu_sr = 0 */
CPU_INT_DIS(); /* 关中断(临界段 2) */
/* 临界段 2 开始 */
{
/* 执行临界段代码,不可中断 */
}
/* 临界段 2 结束 */
CPU_INT_EN(); /* 开中断(临界段 2) */
}
/* 临界段 1 结束 */
CPU_INT_EN(); /* 开中断(临界段 1) */
}
展开宏定义,代码可等效为:
/* 临界段代码保护 */
{
CPU_SR cpu_sr1 = (CPU_SR)0; /* (a) 定义一个变量,用于存放中断状态,初始化 cpu_sr1 = 0 */
cpu_sr1 = CPU_SR_Save(); /* (b) cpu_sr1 保存当前中断状态,然后关中断 */
/* 临界段 1 开始 */
{
CPU_SR cpu_sr2 = (CPU_SR)0; /* (c) 定义一个变量,用于存放中断状态,初始化 cpu_sr2 = 0 */
cpu_sr2 = CPU_SR_Save(); /* (d) cpu_sr2 保存当前中断状态,然后关中断 */
/* 临界段 2 开始 */
{
/* 执行临界段代码,不可中断 */
}
/* 临界段 2 结束 */
CPU_SR_Restore(cpu_sr2); /* (e) cpu_sr2 写入中断状态,恢复之前的中断状态 */
}
/* 临界段 1 结束 */
CPU_SR_Restore(cpu_sr1); /* (f) cpu_sr1 写入中断状态,恢复之前的中断状态 */
}
这个过程如下:
本部分先忽略,因为还不是我重点关注的部分。待有时间再来研究。