死锁场景:
A任务已经获取了资源a,B任务已经获取了资源b,且这时A任务正在等待B任务释放资源b,而B任务正在等待A任务释放资源a;
资源冲突场景:
A任务正在写某个缓冲区(buffer),还没写完发生了任务切换,切换到B任务;B任务执行该缓冲区的读操作,读完后任务切换到A任务;这样B任务读取的数据一部分是新的一部分是旧的,这可能会引起B任务执行异常。
解决资源冲突的常用方法就是原子操作,即:任意任务从获取资源到释放资源是一个完整的操作,过程中不被中断;原子操作的实现方案一般有:关调度、关中断;然而原子操作比较影响OS实时性,更常用的技术是:互斥技术。
最好的解决资源冲突方法是:通过设计尽量不共享资源,每个资源由单一任务访问。
OS中原子操作的实现方案一般有:关调度、关中断;
关中断:
taskENTER_CRITICAL(); taskEXIT_CRITICAL();
关调度:
void vTaskSuspendAll( void ); BaseType_t xTaskResumeAll( void );
关中断的本质是关系统调用中断,即中断号大于等于configMAX_SYSCALL_INTERRUPT_PRIORITY的中断,因此该临界区的执行时间必须尽量短,否则会反过来影响中断响应时间;关调度的本质是让调度器不再执行任务切换,如果一个临界区太长,可以考虑用关调度方式;由于唤醒调度比较久,所以关调度方案也不是随便用的。
特别注意:关中断、关调度器期间不可以使用OS的API函数。
关中断代码:
#define taskENTER_CRITICAL() portENTER_CRITICAL()
#define taskENTER_CRITICAL_FROM_ISR() portSET_INTERRUPT_MASK_FROM_ISR()
#define portSET_INTERRUPT_MASK_FROM_ISR() ulPortRaiseBASEPRI()
#define portENTER_CRITICAL() vPortEnterCritical()
void vPortEnterCritical( void )
{
portDISABLE_INTERRUPTS();
uxCriticalNesting++;
/* This is not the interrupt safe version of the enter critical function so
assert() if it is being called from an interrupt context. Only API
functions that end in "FromISR" can be used in an interrupt. Only assert if
the critical nesting count is 1 to protect against recursive calls if the
assert function also uses a critical section. */
if( uxCriticalNesting == 1 )
{
configASSERT( ( portNVIC_INT_CTRL_REG & portVECTACTIVE_MASK ) == 0 );
}
}
#define portDISABLE_INTERRUPTS() vPortRaiseBASEPRI()
static portFORCE_INLINE void vPortRaiseBASEPRI( void )
{
uint32_t ulNewBASEPRI = configMAX_SYSCALL_INTERRUPT_PRIORITY;
__asm
{
/* Set BASEPRI to the max syscall priority to effect a critical
section. */
msr basepri, ulNewBASEPRI
dsb
isb
}
}
开中断代码:
#define taskEXIT_CRITICAL() portEXIT_CRITICAL()
#define taskEXIT_CRITICAL_FROM_ISR( x ) portCLEAR_INTERRUPT_MASK_FROM_ISR( x )
#define portEXIT_CRITICAL() vPortExitCritical()
#define portCLEAR_INTERRUPT_MASK_FROM_ISR(x) vPortSetBASEPRI(x)
void vPortExitCritical( void )
{
configASSERT( uxCriticalNesting );
uxCriticalNesting--;
if( uxCriticalNesting == 0 )
{
portENABLE_INTERRUPTS();
}
}
#define portENABLE_INTERRUPTS() vPortSetBASEPRI( 0 )
static portFORCE_INLINE void vPortSetBASEPRI( uint32_t ulBASEPRI )
{
__asm
{
/* Barrier instructions are not used as this function is only used to
lower the BASEPRI value. */
msr basepri, ulBASEPRI
}
}
可见关中断的本质是配置中断屏蔽寄存器basepri,将优先级不高于configMAX_SYSCALL_INTERRUPT_PRIORITY的中断屏蔽,而高于该值得中断优先级较高,且不允许调用OS相关的API。
关调度代码:
void vTaskSuspendAll( void )
{
/* A critical section is not required as the variable is of type
BaseType_t. Please read Richard Barry's reply in the following link to a
post in the FreeRTOS support forum before reporting this as a bug! -
http://goo.gl/wu4acr */
/* portSOFRWARE_BARRIER() is only implemented for emulated/simulated ports that
do not otherwise exhibit real time behaviour. */
portSOFTWARE_BARRIER();
/* The scheduler is suspended if uxSchedulerSuspended is non-zero. An increment
is used to allow calls to vTaskSuspendAll() to nest. */
++uxSchedulerSuspended;
/* Enforces ordering for ports and optimised compilers that may otherwise place
the above increment elsewhere. */
portMEMORY_BARRIER();
}
portSOFTWARE_BARRIER();与portMEMORY_BARRIER();两个函数实际没有实现,是空操作,不用理会;
开调度代码:
BaseType_t xTaskResumeAll( void )
{
……
taskENTER_CRITICAL();
{
--uxSchedulerSuspended;
if( uxSchedulerSuspended == ( UBaseType_t ) pdFALSE )
{
……
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
taskEXIT_CRITICAL();
……
}
关调度的本质就是uxSchedulerSuspended加1;开调度的本质是uxSchedulerSuspended减1。
如果确定某条语句的执行只由一条汇编语句即可完成,那么该语句也可认为是原子操作,如下C代码:
114: TestParam * p = GetTestParam();
115:
116: Param1.value1= p->Set.test1;
117: Param2.value2= p->Set.test2;
转换成汇编代码后为:
114: TestParam * p = GetTestParam();
115:
0x08008CB0 F7F9FD06 BL.W 0x080026C0 GetTestParam
0x08008CB4 4604 MOV r4,r0
116: Param1.value1= p->Set.test1;
0x08008CB6 8860 LDRH r0,[r4,#0x02]
0x08008CB8 4925 LDR r1,[pc,#148] ; @0x08008D50
0x08008CBA 8008 STRH r0,[r1,#0x00]
117: Param2.value2= p->Set.test2;
0x08008CBC 88A0 LDRH r0,[r4,#0x04]
0x08008CBE 4925 LDR r1,[pc,#148] ; @0x08008D54
0x08008CC0 8008 STRH r0,[r1,#0x00]
可以看出"LDRH r0,[r4,#0x02]"读取变量test1,只用了一条语句;“STRH r0,[r1,#0x00]”将test1值给到value1,也只用了一句。对于类似本例没有上下文要求的场合,这种原子操作也非常方便。本例只是针对16bit数据读写操作,如果是64bit的可能就不能这么玩了,因为我们单片机目前没有64bit的指令,都是拆分成32bit指令执行的,这样一条64bit的读写操作将至少分两步走,一旦被中断,就不再是原子操作了。
互斥技术常用的有两种:互斥信号量、二值信号量。
// 创建互斥量
QueueHandle_t xQueueCreateMutex( const uint8_t ucQueueType );
QueueHandle_t xQueueCreateMutexStatic( const uint8_t ucQueueType, StaticQueue_t *pxStaticQueue );
BaseType_t xQueueSemaphoreTake( QueueHandle_t xQueue, TickType_t xTicksToWait );
从形式上看,两者相同:都是在临界区前获取信号量,导致其它任务获取不成功而阻塞;当前任务处理完临界区后释放信号量,然后其它任务才可以解除该阻塞。不同的在于内部处理机制,其中最大的区别就是互斥量内部提供了优先级继承机制,用于对抗优先级反转问题:A、B、C任务优先级依次为1/2/3,C任务因A任务先把资源占用而阻塞了(这是最普通的优先级反转问题),A任务在释放资源前又被B任务抢占了(严重的优先级反转问题)。
优先级继承机制:高优先级C任务因低优先级A任务先把资源占用而阻塞了,同时将A任务的优先级临时改为C任务的优先级,待A任务释放资源后,其优先级还原。