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文章
6892瀏覽量
123742 -
RTOS
+關注
關注
22文章
819瀏覽量
119887 -
FreeRTOS
+關注
關注
12文章
484瀏覽量
62395
發布評論請先 登錄
相關推薦
評論