FreeRTOS学习九(锁机制)
在RTOS中,增加了多种锁机制。有调度锁、中断锁、任务锁和互斥锁。
在执行代码时,有的代码开始执行,是不允许被打断的。这部分的代码也叫作临界段代码。为了确保这些代码不被中断而增加了临界区的概念。所谓的临界区保护重要流程在执行的时候不会被其他事情打断。等流程运行结束后,再将程序重新恢复到原来的状态。这种临界区保护的做法也叫锁机制。在裸机系统中,只能通过开关中断来实现锁机制。在RTOS中,增加了多种锁机制。有调度锁、中断锁、任务锁和互斥锁。
中断锁
顾名思义,中断锁的意思是防止程序进行中断切换,即不允许被其他中断打断。FreeRTOS中没有专门的中断锁函数,但是有临界区保护的代码。
重要函数如下:
#define portDISABLE_INTERRUPTS() vPortRaiseBASEPRI() //进临界区
#define portENABLE_INTERRUPTS() vPortSetBASEPRI( 0 ) //退出临界区
#define portENTER_CRITICAL() vPortEnterCritical()//进临界区
#define portEXIT_CRITICAL() vPortExitCritical()//退出临界区
#define portSET_INTERRUPT_MASK_FROM_ISR() ulPortRaiseBASEPRI()//进临界区
#define portCLEAR_INTERRUPT_MASK_FROM_ISR(x) vPortSetBASEPRI(x)//退出临界区
以上这几个函数都是关于中断锁的控制。portENTER_CRITICAL()函数调用vPortEnterCritical()函数,进入该函数。
void vPortEnterCritical( void )
{
/* The interrupt should not be masked when this API is 1st called.
in order to check the error usage, typical case is:
hal_nvic_save_and_set_interrupt_mask()
xQueueGenericSend() // Calling an FreeRTOS API function from within a critical section
hal_nvic_restore_interrupt_mask()
*/
configASSERT(uxCriticalNesting || !__get_BASEPRI());
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 );
}
}
发现该函数还是调用了portDISABLE_INTERRUPTS();函数。但是不同的是,在该函数中有变量uxCriticalNesting,该变量的作用是什么呢?看vPortExitCritical()函数
void vPortExitCritical( void )
{
configASSERT( uxCriticalNesting );
uxCriticalNesting--;
if( uxCriticalNesting == 0 )
{
portENABLE_INTERRUPTS();
}
}
函数中对uxCriticalNesting变量进行了判断,只有该变量为0时,才会执行退出临界区的操作。所以,综上所述,portENTER_CRITICAL()和portEXIT_CRITICAL()函数必须是成对调用。而portDISABLE_INTERRUPTS()和portENABLE_INTERRUPTS()是可以不成对出现的。
portSET_INTERRUPT_MASK_FROM_ISR()函数也是关闭中断
static portFORCE_INLINE uint32_t ulPortRaiseBASEPRI( void )
{
uint32_t ulReturn, ulNewBASEPRI = configMAX_SYSCALL_INTERRUPT_PRIORITY;
__asm
{
/* Set BASEPRI to the max syscall priority to effect a critical
section. */
mrs ulReturn, basepri
msr basepri, ulNewBASEPRI
dsb
isb
}
return ulReturn;
}
从源码中可以看到该函数在关闭中断后,将原来的寄存器值返回。那么可以通过portCLEAR_INTERRUPT_MASK_FROM_ISR函数来恢复之前的状态。
这里介绍一下basepri寄存器。如果向basepri寄存器写0的话,就会停止中断屏蔽,如果要屏蔽优先级不高于xx的中断,则可以将该优先级传给这个参数。例如上边的代码中将configMAX_SYSCALL_INTERRUPT_PRIORITY传递给了basepri寄存器,configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY值为4.则表示屏幕优先级不高于4的中断。即屏蔽PenSV和systick中断。
#define configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY 0x4
调度锁
调度锁的意思是防止程序进行上下文切换。当有一些数据是不允许上下文切换的话,就可以通过调度锁来保证。
重要的函数如下:
vTaskSuspendAll( void ) 开启调度锁(不允许调度)
xTaskResumeAll(void) 关闭调度锁(允许调度)
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 */
++uxSchedulerSuspended;
}
可以看到该函数并没有执行其他的操作,只是将uxSchedulerSuspended计数值+1。那这个变量究竟是什么呢?
在看上下文切换函数uxSchedulerSuspended
void vTaskSwitchContext( void )
{
if( uxSchedulerSuspended != ( UBaseType_t ) pdFALSE )
{
/* The scheduler is currently suspended - do not allow a context
switch. */
xYieldPending = pdTRUE;
}
该函数在进行上下文切换的时候会先检查一下该变量,如果该变量为非零。则表示当前不允许进行任务切换。
BaseType_t xTaskResumeAll( void )
{
TCB_t *pxTCB = NULL;
BaseType_t xAlreadyYielded = pdFALSE;
/* If uxSchedulerSuspended is zero then this function does not match a
previous call to vTaskSuspendAll(). */
configASSERT( uxSchedulerSuspended );
/* It is possible that an ISR caused a task to be removed from an event
list while the scheduler was suspended. If this was the case then the
removed task will have been added to the xPendingReadyList. Once the
scheduler has been resumed it is safe to move all the pending ready
tasks from this list into their appropriate ready list. */
taskENTER_CRITICAL();
{
--uxSchedulerSuspended;
在该函数中,会将该变量的计数值减一。值得注意的是,在进行变量减一的操作前调用了taskENTER_CRITICAL()函数。看定义如下:
#define taskENTER_CRITICAL() portENTER_CRITICAL()
所以,该函数是调用了临界区保护。就是说在操作uxSchedulerSuspended参数的时候先进行了临界区保护,等操作结束后,在退出临界区。
任务锁
为了防止当前任务的执行被其他高优先级的任务打断而提供的锁机制就是任务锁。FreeRTOS没有专门的任何锁函数,但是可以通过已有的功能来实现。
1.通过给调度器加锁
利用FreeRTOS的调度锁功能给调度器加锁的话,将关闭任务切换功能,从而高优先级任务也就无法抢占低优先级任务的执行。同时高优先级任务也是无法向低优先级任务切换的。另外,调度锁只是禁止了调度器工作,并没有关闭任何中断。
2.通过关闭任务切换中断pendSV和系统时钟节拍中断systick
利用FreeRTOS的任务代码临界段处理函数就可以关闭PenSV中断和systick中断。操作寄存器basepri关闭。
可以说,任务锁和调度锁差别不大。
互斥锁
当程序在访问某些共享数据的时候,可能会出现同时改变共享资源的情况。这种情况可能会造成程序运行出现异常。为了防止这种情况出现,增加了互斥锁的概念。互斥锁是依靠互斥信号量来实现的,具体的互斥信号量的内容,可以参考之前的文章。附上链接。
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)