1 前言
笔者最近在做一个项目,简单来说就是操作系统的替换,但是由于我们整个项目是需要兼容多个芯片平台的,我们要做到工作就是将各大芯片原厂提供的SDK归整起来,统一开发。 虽然芯片原厂都是基于freeRTOS来提供SDK,但是毕竟是不同厂商来开发,自然他们基于的freeRTOS版本是不一样的。 这个问题就被我们遇上了,A厂商提供的稳定版本的SDK是基于freeRTOS-v9.0.0版本,而B厂商是freeRTOS-v10.4.4版本;面对这样的困境,经过我们内部讨论和评估,为了能最大程度兼容freeRTOS的新版本,我觉得采用10.4.4版本,这就意味着9.0.0版本的SDK就要升级了。
2 遇到的问题
2.1 版本差异
从时间跨度来说,这两个版本是差异比较大的:
29 May 2021 @github-actions github-actions V10.4.4 8de8a9d
V9.0.0 165c24c @RichardBarry RichardBarry tagged this 25 May 2016
这么多年了,自然迭代的功能就非常多,其中API的实现方法改变就是一个在移植升级过程中非常头疼的问题。
2.2 问题描述
本次遇到的主要问题是portENTER_CRITICAL
和portEXIT_CRITICAL
两个适配接口完全不太一样导致的,具体如下:
//v9.0.0版本中使用的宏定义的方式
#define GLOBAL_INT_DECLARATION() uint32_t fiq_tmp, irq_tmp
#define GLOBAL_INT_DISABLE() do{\
fiq_tmp = portDISABLE_FIQ();\
irq_tmp = portDISABLE_IRQ();\
}while(0)
#define GLOBAL_INT_RESTORE() do{ \
if(!fiq_tmp) \
{ \
portENABLE_FIQ(); \
} \
if(!irq_tmp) \
{ \
portENABLE_IRQ(); \
} \
}while(0)
#define portENTER_CRITICAL() do{ \
GLOBAL_INT_DECLARATION();\
GLOBAL_INT_DISABLE();
#define portEXIT_CRITICAL() \
GLOBAL_INT_RESTORE();\
}while(0)
//v10.4.4版本中采用的是函数定义的方式
/* Critical section handling. */
void vPortEnterCritical( void );
void vPortExitCritical( void );
#define portENTER_CRITICAL() vPortEnterCritical()
#define portEXIT_CRITICAL() vPortExitCritical()
看样子从功能上,好像是一样的,但是真正到了替换编译的时候就遇到问题了。 按照v9.0.0的定义方式,我kernel使用v9.0.0的代码编译自然没有问题,但是我一旦切换到v10.4.4的kernel代码,就报了下面的编译错误:
os/core/freertos-v10.4.4/queue.c: In function 'xQueueGenericSend':
core/freertos-v10.4.4/queue.c:938:13: error: 'else' without a previous 'if'
else
^
compilation terminated due to -Wfatal-errors.
core/freertos-v10.4.4/stream_buffer.c: In function 'xStreamBufferSend':
core/freertos-v10.4.4/stream_buffer.c:625:13: error: expected 'while' before 'do'
taskEXIT_CRITICAL();
^
compilation terminated due to -Wfatal-errors.
3 如何解决
3.1 问题分析
一看上面的两个问题,大概猜到了就是v9.0.0中使用的是do{}while(0)
这种宏定义导致的。 找到v10.4.4的源码看下它是什么调用的,为了简洁且能说明问题,这里我删除了一些无相关的代码:
//queue.c中编译报错的函数
BaseType_t xQueueGenericSend( QueueHandle_t xQueue,
const void * const pvItemToQueue,
TickType_t xTicksToWait,
const BaseType_t xCopyPosition )
{
for( ; ; )
{
taskENTER_CRITICAL();
{
if( ( pxQueue->uxMessagesWaiting < pxQueue->uxLength ) || ( xCopyPosition == queueOVERWRITE ) )
{
taskEXIT_CRITICAL(); //这里报错
return pdPASS;
}
else
{
if( xTicksToWait == ( TickType_t ) 0 )
{
/* The queue was full and no block time is specified (or
* the block time has expired) so leave now. */
taskEXIT_CRITICAL();
}
else if( xEntryTimeSet == pdFALSE )
{
}
else
{
}
}
}
taskEXIT_CRITICAL();
}
}
queue.c里面的报错,这个是由于中间有个taskEXIT_CRITICAL
调用,把do {} while(0)给打散了,导致下面的}else{
就变成语法问题了,正如编译报错的那样。
再看下tasks.c的报错代码:
//tasks.c中的编译报错代码
size_t xStreamBufferSend( StreamBufferHandle_t xStreamBuffer,
const void * pvTxData,
size_t xDataLengthBytes,
TickType_t xTicksToWait )
{
if( xTicksToWait != ( TickType_t ) 0 )
{
do
{
/* Wait until the required number of bytes are free in the message
* buffer. */
taskENTER_CRITICAL();
{
xSpace = xStreamBufferSpacesAvailable( pxStreamBuffer );
if( xSpace < xRequiredSpace )
{
}
else
{
taskEXIT_CRITICAL();
break;
}
}
taskEXIT_CRITICAL(); //这里报错
}
else
{
}
与第一个编译报错类似,但是又不太一样,当然也都是do{}while(0)被打散引发
的;这里的错误提示是:后一个while没有前面的do来匹配。
3.2 细看错误代码
既然那两个接口是宏定义,自然我就可以查看到宏定义展开后的样子,看下究竟是如何违背了语法规则? 使用gcc编译器,我们只需要在CFLAGS加上-save-temps=obj
选项,就可以同步输出预编译处理的文件,后缀名是.i
。
//queue.i对应的代码片段
BaseType_t xQueueGenericSend( QueueHandle_t xQueue,
const void * const pvItemToQueue,
TickType_t xTicksToWait,
const BaseType_t xCopyPosition )
{
for( ; ; )
{
do{ uint32_t fiq_tmp, irq_tmp; do{ fiq_tmp = portDISABLE_FIQ(); irq_tmp = portDISABLE_IRQ(); }while(0);;
{
if( ( pxQueue->uxMessagesWaiting < pxQueue->uxLength ) || ( xCopyPosition == ( ( BaseType_t ) 2 ) ) )
{
do{ if(!fiq_tmp) { portENABLE_FIQ(); } if(!irq_tmp) { portENABLE_IRQ(); } }while(0); }while(0); //这里报错
return ( ( ( BaseType_t ) 1 ) );
}
else
{
}
}
do{ if(!fiq_tmp) { portENABLE_FIQ(); } if(!irq_tmp) { portENABLE_IRQ(); } }while(0); }while(0);
}
}
//tasks.i对应的代码片段
size_t xStreamBufferSend( StreamBufferHandle_t xStreamBuffer,
const void * pvTxData,
size_t xDataLengthBytes,
TickType_t xTicksToWait )
{
if( xTicksToWait != ( TickType_t ) 0 )
{
do
{
do{ uint32_t fiq_tmp, irq_tmp; do{ fiq_tmp = portDISABLE_FIQ(); irq_tmp = portDISABLE_IRQ(); }while(0);;
{
xSpace = xStreamBufferSpacesAvailable( pxStreamBuffer );
if( xSpace < xRequiredSpace )
{
}
else
{
do{ if(!fiq_tmp) { portENABLE_FIQ(); } if(!irq_tmp) { portENABLE_IRQ(); } }while(0); }while(0); //这里报错
break;
}
}
do{ if(!fiq_tmp) { portENABLE_FIQ(); } if(!irq_tmp) { portENABLE_IRQ(); } }while(0); }while(0);
;
} while( xTaskCheckForTimeOut( &xTimeOut, &xTicksToWait ) == ( ( BaseType_t ) 0 ) );
}
else
{
;
}
通过.i文件,基本一看就知道啥问题了。就是这个万恶的do{}while(0)
被打散了,引发各种问题。
3.3 能不能把宏定义改为函数?
知道了上面的问题,归根结底就是宏定义的问题,那么我能不能把宏定义转换成函数呢? 之前我有一篇文章讲过内联函数
,即staticinline
的用法,具体参见:【gcc编译优化系列】static与inline的区别与联系 参考这个方案,很快,我给出了一个staticinline
的版本:
//portmacro.h中定义:
static inline void portENTER_CRITICAL(void)
{
GLOBAL_INT_DECLARATION();
GLOBAL_INT_DISABLE();
}
static inline void portEXIT_CRITICAL(void)
{
GLOBAL_INT_DECLARATION();
GLOBAL_INT_RESTORE();
}
static inline void portEXIT_CRITICAL_EARLY(void)
{
GLOBAL_INT_DECLARATION();
GLOBAL_INT_RESTORE();
}
这个portEXITCRITICALEARLY是因为v9.0.0的代码里面有,为了兼容v9.0.0的代码编译,我保留了下它。 同时这个GLOBAL_INT_DECLARATION
这个我也改了一下,加上了extern
:
#define GLOBAL_INT_DECLARATION() extern uint32_t fiq_tmp, irq_tmp //新的定义
//#define GLOBAL_INT_DECLARATION() uint32_t fiq_tmp, irq_tmp //旧的定义
#endif
同时由于这两个变量fiq_tmp,irq_tmp
没法在两个函数中共享,所以得把它们定义成全局变量
:
//必须在其中的一个.c文件中定义,因为定义只能由一个,而extern申明可以有多个。
uint32_t fiq_tmp, irq_tmp;
经过上面的宏定义转内联函数
的定义,一编译,自然,那几个编译报错的语法问题都迎刃而解了。 但是当我烧录到板子上运行时,却遇到了问题,具体问题就是:系统会在不确认的时间内卡死,导致看门狗复位,这里面有可能是厂商的SDK封装的问题,但是找厂商去修改SDK是不可能的,毕竟是由我们单方面升级了freeRTOS了,别人跑得好好的,就你不行。
3.4 能不能有其他解决办法?
想到上一步,为何SDK会出问题,我想上面宏定义转内联函数
只是表象,真正改动的是把中断标记的那两个变量全局化了
;这样带来的问题就是全部线程都可以同时
修改,这显然违背了之前的设计初衷,所以它们一定不能全局化。 那么还有什么方法仅能保证代码编译过去,又能保证这两个变量的访问逻辑呢? 思思一想,还是得保留宏定义的写法,但是宏定义得改一改。 之前不是老是出现do{}while(0)被打散
嘛,我们能不能把do-while(0)去掉,试试看:
#if 1 //新版本
#define portENTER_CRITICAL() GLOBAL_INT_DECLARATION();GLOBAL_INT_DISABLE()
#define portEXIT_CRITICAL() GLOBAL_INT_RESTORE()
#define portEXIT_CRITICAL_EARLY() GLOBAL_INT_RESTORE()
#else
#define portENTER_CRITICAL() do{ \
GLOBAL_INT_DECLARATION();\
GLOBAL_INT_DISABLE();
#define portEXIT_CRITICAL() \
GLOBAL_INT_RESTORE();\
}while(0)
#define portEXIT_CRITICAL_EARLY() GLOBAL_INT_RESTORE()
#endif
如此改动之后,编译一下,又发现了一个报错:
//报错
core/freertos-v10.4.4/queue.c: In function 'xQueueGenericSend':
core/freertos-v10.4.4/queue.c:984:18: error: redeclaration of 'fiq_tmp' with no linkage
prvLockQueue( pxQueue );
//对应代码
taskEXIT_CRITICAL();
/* Interrupts and other tasks can send to and receive from the queue
* now the critical section has been exited. */
vTaskSuspendAll();
prvLockQueue( pxQueue ); //这里报错
/* Update the timeout state to see if it has expired yet. */
if( xTaskCheckForTimeOut( &xTimeOut, &xTicksToWait ) == pdFALSE )
{
}
//对应宏展开的代码
for( ; ; )
{
uint32_t fiq_tmp, irq_tmp;do{ fiq_tmp = portDISABLE_FIQ(); irq_tmp = portDISABLE_IRQ(); }while(0);
do{ if(!fiq_tmp) { portENABLE_FIQ(); } if(!irq_tmp) { portENABLE_IRQ(); } }while(0);
vTaskSuspendAll();
uint32_t fiq_tmp, irq_tmp;do{ fiq_tmp = portDISABLE_FIQ(); irq_tmp = portDISABLE_IRQ(); }while(0); { if( ( pxQueue )->cRxLock == ( ( int8_t ) -1 ) ) { ( pxQueue )->cRxLock = ( ( int8_t ) 0 ); } if( ( pxQueue )->cTxLock == ( ( int8_t ) -1 ) ) { ( pxQueue )->cTxLock = ( ( int8_t ) 0 ); } } do{ if(!fiq_tmp) { portENABLE_FIQ(); } if(!irq_tmp) { portENABLE_IRQ(); } }while(0);
这里的主要问题就是,由于do{}while(0)
去掉了,导致uint32_tfiq_tmp,irq_tmp
在一个代码段范围内被重复定义了,所以语法上报错了。 为了解决这个问题,我们需要有个语法基础:在C里面,一个局部变量的作用域是在其包含的{}内,嵌套的{}可以有同名的变量名
, 也就是说这样的代码时允许的:
{
int a = 1;
{
int a = 1;
{
int a = 1;
}
}
}
虽然写法上很丑陋,但是语法上是可行的。 根据这个理论,我们要改造下这个宏定义:
#if 1 //新代码
#define prvLockQueue0( pxQueue ) \
do { \
taskENTER_CRITICAL(); \
{ \
if( ( pxQueue )->cRxLock == queueUNLOCKED ) \
{ \
( pxQueue )->cRxLock = queueLOCKED_UNMODIFIED; \
} \
if( ( pxQueue )->cTxLock == queueUNLOCKED ) \
{ \
( pxQueue )->cTxLock = queueLOCKED_UNMODIFIED; \
} \
} \
taskEXIT_CRITICAL(); \
} while(0)
#else
#define prvLockQueue( pxQueue ) \
taskENTER_CRITICAL(); \
{ \
if( ( pxQueue )->cRxLock == queueUNLOCKED ) \
{ \
( pxQueue )->cRxLock = queueLOCKED_UNMODIFIED; \
} \
if( ( pxQueue )->cTxLock == queueUNLOCKED ) \
{ \
( pxQueue )->cTxLock = queueLOCKED_UNMODIFIED; \
} \
} \
taskEXIT_CRITICAL()
#endif
这样就可以完美解决了uint32_tfiq_tmp,irq_tmp
重复定义的问题。 编译一下,下载跑了一下,发现工作正常,至此算是把这个升级工作完成了。
3.5 还有个问题
升级过程中,还有一个问题,不过倒是比较好解决。 就是v9.0.0版本有个API接口叫xTaskIsTaskFinished
;而v10.4.4已经把这个函数删除了,而SDK又调用了这个API,所以只能重新实现下这个函数
/* If not found, implemente it ! */
__attribute__ ((weak)) portBASE_TYPE xTaskIsTaskFinished( xTaskHandle xTask )
{
LOG_HERE();
/* always return false ! */
return pdFALSE;
}
这里我加了weak
声明,也就是说当内核有实现这个函数时,用内核的;反之,则使用这个实现;这样的好处就是,在v9.0.0上面是可以兼容编译的,不会报重复定义的问题。但是如果去掉weak声明,就会报错误。
4 经验总结
- freeRTOS的版本不能乱升级,尤其系统跨度比较大的版本之间,严重情况下可能系统都跑不起来
- do-while(0)似的宏定义不是万能的,有些场景下也是会出错的
- C语言下大括号内定义同名局部变量的问题的解决方法,值得借鉴
- 宏定义转内联函数,看似一个最佳实践,实则还是需要具体问题具体分析,否则会引入不必要的问题
- 遇到问题,需要冷静分析问题,解决一个问题还得看下关联的问题有没有影响
- weak函数大有益处(下回写文再细讲)
5 更多分享
欢迎关注我的github仓库01workstation,日常分享一些开发笔记和项目实战,欢迎指正问题。
同时也非常欢迎关注我的专栏:有问题的话,可以跟我讨论,知无不答,谢谢大家。
-
操作系统
+关注
关注
37文章
6814浏览量
123299 -
RTOS
+关注
关注
22文章
811浏览量
119607 -
FreeRTOS
+关注
关注
12文章
484浏览量
62153
发布评论请先 登录
相关推荐
评论