【freeRTOS開發筆記】記一次坑爹的freeTOS-v9.0.0升級到freeRTOS-v10.4.4

      網友投稿 1521 2025-03-31

      1 前言


      【freeRTOS開發筆記】記一次坑爹的freeTOS-v9.0.0升級到freeRTOS-v10.4.4

      筆者最近在做一個項目,簡單來說就是操作系統的替換,但是由于我們整個項目是需要兼容多個芯片平臺的,我們要做到工作就是將各大芯片原廠提供的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 能不能把宏定義改為函數?

      知道了上面的問題,歸根結底就是宏定義的問題,那么我能不能把宏定義轉換成函數呢?

      之前我有一篇文章講過內聯函數,即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小時內刪除侵權內容。

      版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。

      上一篇:excel自定義自動篩選的方法步驟
      下一篇:手機wps怎么快速填充表格的方法
      相關文章
      国产亚洲精aa在线看| 亚洲六月丁香六月婷婷色伊人| 亚洲人色大成年网站在线观看| 亚洲国产精品国自产电影| 亚洲乱码一区二区三区在线观看 | 亚洲激情在线观看| 亚洲av综合avav中文| 亚洲精品无码午夜福利中文字幕| 亚洲精品99久久久久中文字幕| 亚洲日韩在线中文字幕综合 | 久久久久亚洲av无码专区蜜芽| 亚洲精品成人无限看| 亚洲精品国产精品乱码视色| 日韩一卡2卡3卡4卡新区亚洲| 亚洲桃色AV无码| 久久精品国产亚洲夜色AV网站| 亚洲成Av人片乱码色午夜| 亚洲VA成无码人在线观看天堂| 亚洲AV无码成人网站久久精品大| 日本亚洲欧洲免费天堂午夜看片女人员| 亚洲午夜福利717| 亚洲AV无码日韩AV无码导航| 亚洲AV日韩精品久久久久| 亚洲天堂视频在线观看| 337p日本欧洲亚洲大胆艺术| 亚洲日本乱码一区二区在线二产线 | 亚洲日韩国产二区无码| 色天使色婷婷在线影院亚洲| 少妇亚洲免费精品| 4338×亚洲全国最大色成网站| 中文字幕一精品亚洲无线一区| 亚洲AV无码欧洲AV无码网站| 无码专区—VA亚洲V天堂| 91精品国产亚洲爽啪在线观看| 亚洲国产中文在线视频| 亚洲日本VA午夜在线影院| 国产综合成人亚洲区| 亚洲尤码不卡AV麻豆| 亚洲电影国产一区| 亚洲人成电影青青在线播放| 亚洲人成网站色7799|