【freeRTOS开发笔记】记一次坑爹的freeTOS升级

描述

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_CRITICALportEXIT_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,日常分享一些开发笔记和项目实战,欢迎指正问题。

同时也非常欢迎关注我的专栏:有问题的话,可以跟我讨论,知无不答,谢谢大家。

打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分