衡阳派盒市场营销有限公司

0
  • 聊天消息
  • 系統消息
  • 評論與回復
登錄后你可以
  • 下載海量資料
  • 學習在線課程
  • 觀看技術視頻
  • 寫文章/發帖/加入社區
會員中心
創作中心

完善資料讓更多小伙伴認識你,還能領取20積分哦,立即完善>

3天內不再提示

【freeRTOS開發筆記】記一次坑爹的freeTOS升級

嵌入式物聯網開發 ? 來源:嵌入式物聯網開發 ? 作者:嵌入式物聯網開發 ? 2022-07-11 09:15 ? 次閱讀

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,日常分享一些開發筆記和項目實戰,歡迎指正問題。

同時也非常歡迎關注我的專欄:有問題的話,可以跟我討論,知無不答,謝謝大家。

聲明:本文內容及配圖由入駐作者撰寫或者入駐合作網站授權轉載。文章觀點僅代表作者本人,不代表電子發燒友網立場。文章及其配圖僅供工程師學習之用,如有內容侵權或者其他違規問題,請聯系本站處理。 舉報投訴
  • 操作系統
    +關注

    關注

    37

    文章

    6892

    瀏覽量

    123742
  • RTOS
    +關注

    關注

    22

    文章

    819

    瀏覽量

    119887
  • FreeRTOS
    +關注

    關注

    12

    文章

    484

    瀏覽量

    62395
收藏 人收藏

    評論

    相關推薦

    freeRTOS開發筆記】關注創建任務時傳入優先級數值問題

    freeRTOS開發筆記】關注創建任務時傳入的優先級數值問題
    的頭像 發表于 07-11 09:13 ?2818次閱讀
    【<b class='flag-5'>freeRTOS</b><b class='flag-5'>開發筆記</b>】關注創建任務時傳入優先級數值問題

    安卓開發筆記

    安卓開發筆記(中文)
    發表于 04-26 10:57

    基于STM32的USB程序開發筆記 匯總

    忙了下午終于有時間整理了,基于STM32的USB程序開發筆記匯總,需要的親們點擊鏈接閱讀哈!{:4_95:}基于STM32的USB程序開發筆記)https://bbs.elecf
    發表于 03-20 16:08

    Modbus庫開發筆記之十一:關于Modbus協議棧開發的說明

    們不就使用的最終結果負責。當然如果發現任何的不足,我們非常并歡迎大家將發現的問題告知我們,以便我們持續的改進之。本系列的全部分裝如下:Modbus庫開發筆記:實現功能的基本設計https
    發表于 08-27 20:32

    壇友經驗分享之STM32的USB程序開發筆記

    基于STM32的USB程序開發筆記)基于STM32的USB程序開發筆記(二)基于STM32的USB程序開發筆記(三)基于STM32的USB程序
    發表于 09-04 17:42

    基于STM32的USB程序開發筆記

    基于STM32的USB程序開發筆記
    發表于 04-24 09:23

    一次網站設計稿的方法

    一次網站設計稿
    發表于 06-16 09:43

    Odrive開發筆記 精選資料推薦

    Odrive開發筆記文章目錄Odrive開發筆記接線配置進入校準測試用python來控制odrive電機控制介紹位置環速度環把從開始做odrive驅動無刷電機的所有過程都記錄下來接線1. 首先
    發表于 09-02 07:33

    求大佬分享CAN開發筆記

    求大佬分享CAN開發筆記
    發表于 02-07 06:16

    lua開發筆記分享

    lua開發筆記(1)單片機與luaPython與lua單片機與lua我第一次接觸lua是幾年前偶然發現了個單片機(MCU)的開源項目——NodeMCU。這個項目很有意思,他的目的是讓傳統程序員
    發表于 02-08 06:12

    基于STM32的USB程序開發筆記

    基于STM32的USB程序開發筆記STM32 USB 源代碼及筆記下載.rar
    發表于 10-09 06:05

    STM32的USB程序開發筆記

    STM32的USB程序開發筆記
    發表于 09-29 14:55 ?27次下載
    STM32的USB程序<b class='flag-5'>開發筆記</b>

    基于LM3S網絡開發筆記3_多網頁開發

    基于LM3S網絡開發筆記3_多網頁開發
    發表于 10-11 08:52 ?4次下載
    基于LM3S網絡<b class='flag-5'>開發筆記</b>3_多網頁<b class='flag-5'>開發</b>

    基于LM3S網絡開發筆記1_開發平臺

    基于LM3S網絡開發筆記1_開發平臺
    發表于 10-11 08:57 ?4次下載
    基于LM3S網絡<b class='flag-5'>開發筆記</b>1_<b class='flag-5'>開發</b>平臺

    lua開發筆記(1)

    lua開發筆記(1)單片機與luaPython與lua單片機與lua我第一次接觸lua是幾年前偶然發現了個單片機(MCU)的開源項目——NodeMCU。這個項目很有意思,他的目的是讓傳統程序員
    發表于 12-05 11:51 ?8次下載
    lua<b class='flag-5'>開發筆記</b>(1)
    尚品棋牌注册| 宝格丽百家乐娱乐城| 大发888相关资讯| 权威百家乐信誉网站| 三亚百家乐官网的玩法技巧和规则| 宝鸡市| 威尼斯人娱乐城提款| 真人百家乐免费开户送钱| 免佣百家乐官网规则| 赞皇县| 百家乐官网玩家技巧分享| 荣昌县| 大发888娱乐场下载专区| 威尼斯人娱乐城活动| 百家乐的分析| 新澳博百家乐娱乐城| 百家乐投注玩多少钱| 百家乐官网园好又多| A8百家乐官网娱乐平台| 海威百家乐官网赌博机| 百家乐官网游戏机分析仪| 明陞百家乐娱乐城| 百家乐怎么出千| 奔驰百家乐游戏电玩| 百家乐开户导航| 百家乐美食坊| 百家乐网投注| 线上百家乐网站| 百家乐挂机软件| 百家乐出老千视频| 百家乐真人游戏娱乐| 番禺百家乐电器店| 24山吉凶视频| 百家乐视频软件| 百家乐怎么玩啊| 澳门百家乐信誉| 娱乐网百家乐的玩法技巧和规则 | 电子百家乐官网规则| 百家乐洗码全讯网| 百家乐澳门规矩| 大发888游戏官方下载客户端|