最近在开发STM32项目中出现了信号量交互异常的问题,该项目搭载的是FreeRtos系统,具体内容如下:
现象:
在系统运行过程中,出现如下断言错误:
问题排查:
找到代码断言定义处如下:
#define configASSERT( x ) if( ( x ) == 0 ) { taskDISABLE_INTERRUPTS(); printf("assert: %s->%d\n", (__FUNCTION__), (__LINE__));for( ;; ); }
由日志可以知道,是系统出现了信号量在执行获取过程中错误。
沿着错误继续追踪代码的上层调用,很快可以找到对应的configASSERT出错地方,如下:
#if ( ( INCLUDE_xTaskGetSchedulerState == 1 ) || ( configUSE_TIMERS == 1 ) )
{
configASSERT( !( ( xTaskGetSchedulerState() == taskSCHEDULER_SUSPENDED ) && ( xTicksToWait != 0 ) ) );
}
#endif
根据configASSERT的定义可知,只有括号中的x等于0,才会报错,可得出代码运行到此处:
xTaskGetSchedulerState() == taskSCHEDULER_SUSPENDED 成立。说明调度器当前状态处于
挂起态,xTaskGetSchedulerState() 函数是用于获取当前调度器状态。
xTicksToWait != 0 成立。 说明传递的入口参数信号量等待时间不为0。
综上分析,并结合FreeRtos系统代码注释
根据我们对信号量实现功能的认知:信号量可以用来实现对共享资源的互斥访问和多任务之间的
同步。
由此结合分析可以知道:信号量的获取和释放操作运行在多任务的运行状态下,当正要执行
xQueueSemaphoreTake进行信号量获取操作的时候,任务调度器且是处于挂起状态,这是不被允
许的。
重点:当调度器处于挂起状态时,是不能调用FreeRtos API函数的
那到底是什么操作使得此时的调度器被挂起了呢?
可以通过结合日志出错地方的上下文查看,找到引起调度器挂起的使用地方,也就是
vTaskSuspendAll()使用。
void vTaskSuspendAll( void )
{
++uxSchedulerSuspended;
}
通过一步步排查定位到是FreeRtos内存分配函数pvPortMalloc引起的调度器挂起。在内存分配过
程中,会先调用vTaskSuspendAll挂起调度器,分配完成后在调用xTaskResumeAll()开启调度器
void *pvPortMalloc( size_t xWantedSize )
{
void *pvReturn = NULL;
static uint8_t *pucAlignedHeap = NULL;
#if( portBYTE_ALIGNMENT != 1 )
{
if( xWantedSize & portBYTE_ALIGNMENT_MASK )
{
xWantedSize += ( portBYTE_ALIGNMENT - ( xWantedSize & portBYTE_ALIGNMENT_MASK ) );
}
}
#endif
vTaskSuspendAll();
{
if( pucAlignedHeap == NULL )
{
pucAlignedHeap = ( uint8_t * ) ( ( ( portPOINTER_SIZE_TYPE ) &ucHeap[ portBYTE_ALIGNMENT ] ) & ( ~( ( portPOINTER_SIZE_TYPE ) portBYTE_ALIGNMENT_MASK ) ) );
}
if( ( ( xNextFreeByte + xWantedSize ) < configADJUSTED_HEAP_SIZE ) &&
( ( xNextFreeByte + xWantedSize ) > xNextFreeByte ) )
{
pvReturn = pucAlignedHeap + xNextFreeByte;
xNextFreeByte += xWantedSize;
}
traceMALLOC( pvReturn, xWantedSize );
}
( void ) xTaskResumeAll();
#if( configUSE_MALLOC_FAILED_HOOK == 1 )
{
if( pvReturn == NULL )
{
extern void vApplicationMallocFailedHook( void );
vApplicationMallocFailedHook();
}
}
#endif
return pvReturn;
}
由于挂起调度器实现的临界区只可以保护一段代码区间不被其他任务打断,而此时中断是使能
的,也就是可以被中断打断。加上频繁使用pvPortMalloc分配和释放使用不固定大小的内存空间过
程中,容易出现在挂起调度器时被中断打断后,在其他地方进行信号量操作,而导致出现以上问
题。
解决方法:
1.可以将频繁分配释放不固定内存大小的pvPortMalloc操作改为每次都分配释放同样大小的内存空
间,这样也有利于减小内存碎片发生
2.也可选择将你需要经常分配释放那块内存改为使用定义的一块全局的数组空间,而不用动态分配
的内存空间
在操作FreeRtos系统过程中应该注意事项:
1.互斥量不能在中断服务函数中使用,因为其特有的优先级继承机制只在任务起作用,在中断的上
下文环境毫无意义
2.FreeRtos系统对应的一些功能接口函数是有区分用于中断的接口和非中断中使用的接口,如果
在中断函数里调用了非中断的系统接口,就会出现系统性断言错误