【freeRTOS开发笔记】记一次坑爹的freeTOS-v9.0.0升级到freeRTOS-v10.4.4

网友投稿 1209 2022-05-30

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 版本差异

从时间跨度来说,这两个版本是差异比较大的:

【freeRTOS开发笔记】记一次坑爹的freeTOS-v9.0.0升级到freeRTOS-v10.4.4

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 能不能把宏定义改为函数?

知道了上面的问题,归根结底就是宏定义的问题,那么我能不能把宏定义转换成函数呢?

之前我有一篇文章讲过内联函数,即static inline的用法,具体参见:【gcc编译优化系列】static与inline的区别与联系

参考这个方案,很快,我给出了一个static inline的版本:

//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(); }

这个portEXIT_CRITICAL_EARLY是因为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_t fiq_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_t fiq_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,日常分享一些开发笔记和项目实战,欢迎指正问题。

同时也非常欢迎关注我的CSDN主页和专栏:

【CSDN主页:架构师李肯】

【RT-Thread主页:架构师李肯】

【C/C++语言编程专栏】

【GCC专栏】

【信息安全专栏】

【RT-Thread开发笔记】

【freeRTOS开发笔记】

有问题的话,可以跟我讨论,知无不答,谢谢大家。

AI ARM

版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:java中的NIO和IO到底是什么区别?20个问题告诉你答案【奔跑吧!JAVA】
下一篇:Linux-生产者消费者模型
相关文章