1.最常用的独占共享资源和创建临界区的方法有4种:关中断、禁止任务调度、使用信号量、使用互斥型信号量。
一般推荐使用互斥信号量。
在访问数据量极少的时候,可以使用关中断的方式。
在访问数据量少且资源同步是在任务与任务之间的时候,可以使用关调度的方式。
2.关中断/开中断
(1)在使用关中断/开中断时,必须先调用宏CPU_SR_ALLOC()。它的作用是分配存储空间来存储当前CPU的中断状态。
(2)关中断/开中断属于CPU相关的函数,所以它们的定义位于cpu.h中,319行 - 320行
#define CPU_CRITICAL_ENTER() do { CPU_INT_DIS(); } while (0) // 关中断
#define CPU_CRITICAL_EXIT() do { CPU_INT_EN(); } while (0) // 开中断
#define CPU_INT_DIS() do { cpu_sr = CPU_SR_Save(); } while (0) // 保存CPU的状态,并关中断
#define CPU_INT_EN() do { CPU_SR_Restore(cpu_sr); } while (0) // 恢复CPU的状态
// 以下是汇编代码
CPU_SR_Save
MRS R0, PRIMASK // 保存CPU的状态到R0中--子函数返回时,R0存放返回值
CPSID I // 关闭中断
BX LR
CPU_SR_Restore
MSR PRIMASK, R0 // 把R0的值复制到CPU的状态寄存器中--函数调用时,R0存放第一个参数
BX LR
(3)需要注意的是关中断/开中断是一个任务和一个中断服务程序共享变量或者数据结构的唯一方法
3.给调度器上锁/解锁
(1)给调度器上锁/解锁,可以防止两个以上的任务出现竞争。但如果没有关闭中断,一旦中断发生,即便在临界区内,中断服务程序也会立即执行。
(2)给调度器上锁使用OSSchedLock()函数实现,解锁使用OSSchedUnlock()函数实现,它们的定义位于os_core.c中。
// 调度器最多可以
void OSSchedLock (OS_ERR *p_err) // 在os_core.c中 441行 - 448行
{
CPU_SR_ALLOC(); // 宏,声明存储空间,用来存储当前CPU的中断状态
#ifdef OS_SAFETY_CRITICAL // ???
if (p_err == (OS_ERR *)0) {
OS_SAFETY_CRITICAL_EXCEPTION();
return;
}
#endif
#if OS_CFG_CALLED_FROM_ISR_CHK_EN > 0u
if (OSIntNestingCtr > (OS_NESTING_CTR)0) { // 不允许在ISR中调用
*p_err = OS_ERR_SCHED_LOCK_ISR;
return;
}
#endif
if (OSRunning != OS_STATE_OS_RUNNING) { // 确保操作是在运行状态
*p_err = OS_ERR_OS_NOT_RUNNING;
return;
}
// OSSchedLockNestingCtr最大值为250,也就是说OSSchedLock()最多可以嵌套250次
if (OSSchedLockNestingCtr >= (OS_NESTING_CTR)250u) { // 预防OSSchedLockNestingCtr溢出
*p_err = OS_ERR_LOCK_NESTING_OVF;
return;
}
CPU_CRITICAL_ENTER(); // 进入临界区
OSSchedLockNestingCtr++; // 嵌套层数加1
#if OS_CFG_SCHED_LOCK_TIME_MEAS_EN > 0u // 允许测量调度器锁定时间
OS_SchedLockTimeMeasStart();
#endif
CPU_CRITICAL_EXIT(); // 退出临界区
*p_err = OS_ERR_NONE;
}
void OSSchedUnlock (OS_ERR *p_err) // 在os_core.c中 472行 - 517行
{
CPU_SR_ALLOC(); // 宏,声明存储空间,用来存储当前CPU的中断状态
#ifdef OS_SAFETY_CRITICAL // ???
if (p_err == (OS_ERR *)0) {
OS_SAFETY_CRITICAL_EXCEPTION();
return;
}
#endif
#if OS_CFG_CALLED_FROM_ISR_CHK_EN > 0u
if (OSIntNestingCtr > (OS_NESTING_CTR)0) { // 不允许在ISR中调用
*p_err = OS_ERR_SCHED_UNLOCK_ISR;
return;
}
#endif
if (OSRunning != OS_STATE_OS_RUNNING) { // 确保操作正在运行
*p_err = OS_ERR_OS_NOT_RUNNING;
return;
}
if (OSSchedLockNestingCtr == (OS_NESTING_CTR)0) {// 查看调度器是否已锁定
*p_err = OS_ERR_SCHED_NOT_LOCKED;
return;
}
CPU_CRITICAL_ENTER(); // 进入临界区
OSSchedLockNestingCtr--; // 嵌套层数减1
if (OSSchedLockNestingCtr > (OS_NESTING_CTR)0) { // 调度器仍然锁定
CPU_CRITICAL_EXIT(); // 退出临界区
*p_err = OS_ERR_SCHED_LOCKED;
return;
}
#if OS_CFG_SCHED_LOCK_TIME_MEAS_EN > 0u // 允许测量任务调度的锁定时间
OS_SchedLockTimeMeasStop();
#endif
CPU_CRITICAL_EXIT(); // 退出临界区
OSSched(); // 执行任务调度
*p_err = OS_ERR_NONE;
}
(3)在调度器上锁时,uC/OS-III会禁止用户进行阻塞调用。
4.信号量
(1)信号量通常分为两种:二进制信号量与计数型信号量。
(2)建议在使用信号量时,可以把信号量放入到函数中包起来。
(3)一般建议用信号量来访问IO设备,而不用它来访问存储单元;请求和释放信号量的过程是很费时的,因此在处理简单的、CPU能在很短时间内完成的操作时,使用关中断的方式更合理。
(4)信号量的数据结构类型为OS_SEM,它的定义位于os.h中。
typedef struct os_sem OS_SEM; // 631行
struct os_sem { // 858行 - 869行
OS_OBJ_TYPE Type; // 类型 Semaphore */
CPU_CHAR *NamePtr; /* Should be set to OS_OBJ_TYPE_SEM */
OS_PEND_LIST PendList; /* Pointer to Semaphore Name (NUL terminated ASCII) */
#if OS_CFG_DBG_EN > 0u
OS_SEM *DbgPrevPtr;
OS_SEM *DbgNextPtr;
CPU_CHAR *DbgNamePtr;
#endif
OS_SEM_CTR Ctr; /* List of tasks waiting on event flag group */
CPU_TS TS;
};