1 前言
筆者最近在做一個項目,簡單來說就是操作系統(tǒng)的替換,但是由于我們整個項目是需要兼容多個芯片平臺的,我們要做到工作就是將各大芯片原廠提供的SDK歸整起來,統(tǒng)一開發(fā)。 雖然芯片原廠都是基于freeRTOS來提供SDK,但是畢竟是不同廠商來開發(fā),自然他們基于的freeRTOS版本是不一樣的。 這個問題就被我們遇上了,A廠商提供的穩(wěn)定版本的SDK是基于freeRTOS-v9.0.0版本,而B廠商是freeRTOS-v10.4.4版本;面對這樣的困境,經(jīng)過我們內(nèi)部討論和評估,為了能最大程度兼容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的實現(xiàn)方法改變就是一個在移植升級過程中非常頭疼的問題。
2.2 問題描述
本次遇到的主要問題是portENTER_CRITICAL
和portEXIT_CRITICAL
兩個適配接口完全不太一樣導(dǎo)致的,具體如下:
//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版本中采用的是函數(shù)定義的方式
/* 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)
這種宏定義導(dǎo)致的。 找到v10.4.4的源碼看下它是什么調(diào)用的,為了簡潔且能說明問題,這里我刪除了一些無相關(guān)的代碼:
//queue.c中編譯報錯的函數(shù)
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
調(diào)用,把do {} while(0)給打散了,導(dǎo)致下面的}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
{
}
與第一個編譯報錯類似,但是又不太一樣,當(dāng)然也都是do{}while(0)被打散引發(fā)
的;這里的錯誤提示是:后一個while沒有前面的do來匹配。
3.2 細(xì)看錯誤代碼
既然那兩個接口是宏定義,自然我就可以查看到宏定義展開后的樣子,看下究竟是如何違背了語法規(guī)則? 使用gcc編譯器,我們只需要在CFLAGS加上-save-temps=obj
選項,就可以同步輸出預(yù)編譯處理的文件,后綴名是.i
。
//queue.i對應(yīng)的代碼片段
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對應(yīng)的代碼片段
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)
被打散了,引發(fā)各種問題。
3.3 能不能把宏定義改為函數(shù)?
知道了上面的問題,歸根結(jié)底就是宏定義的問題,那么我能不能把宏定義轉(zhuǎn)換成函數(shù)呢? 之前我有一篇文章講過內(nèi)聯(lián)函數(shù)
,即staticinline
的用法,具體參見:【gcc編譯優(yōu)化系列】static與inline的區(qū)別與聯(lián)系 參考這個方案,很快,我給出了一個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
沒法在兩個函數(shù)中共享,所以得把它們定義成全局變量
:
//必須在其中的一個.c文件中定義,因為定義只能由一個,而extern申明可以有多個。
uint32_t fiq_tmp, irq_tmp;
經(jīng)過上面的宏定義轉(zhuǎn)內(nèi)聯(lián)函數(shù)
的定義,一編譯,自然,那幾個編譯報錯的語法問題都迎刃而解了。 但是當(dāng)我燒錄到板子上運行時,卻遇到了問題,具體問題就是:系統(tǒng)會在不確認(rèn)的時間內(nèi)卡死,導(dǎo)致看門狗復(fù)位,這里面有可能是廠商的SDK封裝的問題,但是找廠商去修改SDK是不可能的,畢竟是由我們單方面升級了freeRTOS了,別人跑得好好的,就你不行。
3.4 能不能有其他解決辦法?
想到上一步,為何SDK會出問題,我想上面宏定義轉(zhuǎn)內(nèi)聯(lián)函數(shù)
只是表象,真正改動的是把中斷標(biāo)記的那兩個變量全局化了
;這樣帶來的問題就是全部線程都可以同時
修改,這顯然違背了之前的設(shè)計初衷,所以它們一定不能全局化。 那么還有什么方法僅能保證代碼編譯過去,又能保證這兩個變量的訪問邏輯呢? 思思一想,還是得保留宏定義的寫法,但是宏定義得改一改。 之前不是老是出現(xiàn)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
如此改動之后,編譯一下,又發(fā)現(xiàn)了一個報錯:
//報錯
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 );
//對應(yīng)代碼
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 )
{
}
//對應(yīng)宏展開的代碼
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)
去掉了,導(dǎo)致uint32_tfiq_tmp,irq_tmp
在一個代碼段范圍內(nèi)被重復(fù)定義了,所以語法上報錯了。 為了解決這個問題,我們需要有個語法基礎(chǔ):在C里面,一個局部變量的作用域是在其包含的{}內(nèi),嵌套的{}可以有同名的變量名
, 也就是說這樣的代碼時允許的:
{
int a = 1;
{
int a = 1;
{
int a = 1;
}
}
}
雖然寫法上很丑陋,但是語法上是可行的。 根據(jù)這個理論,我們要改造下這個宏定義:
#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
重復(fù)定義的問題。 編譯一下,下載跑了一下,發(fā)現(xiàn)工作正常,至此算是把這個升級工作完成了。
3.5 還有個問題
升級過程中,還有一個問題,不過倒是比較好解決。 就是v9.0.0版本有個API接口叫xTaskIsTaskFinished
;而v10.4.4已經(jīng)把這個函數(shù)刪除了,而SDK又調(diào)用了這個API,所以只能重新實現(xiàn)下這個函數(shù)
/* If not found, implemente it ! */
__attribute__ ((weak)) portBASE_TYPE xTaskIsTaskFinished( xTaskHandle xTask )
{
LOG_HERE();
/* always return false ! */
return pdFALSE;
}
這里我加了weak
聲明,也就是說當(dāng)內(nèi)核有實現(xiàn)這個函數(shù)時,用內(nèi)核的;反之,則使用這個實現(xiàn);這樣的好處就是,在v9.0.0上面是可以兼容編譯的,不會報重復(fù)定義的問題。但是如果去掉weak聲明,就會報錯誤。
4 經(jīng)驗總結(jié)
- freeRTOS的版本不能亂升級,尤其系統(tǒng)跨度比較大的版本之間,嚴(yán)重情況下可能系統(tǒng)都跑不起來
- do-while(0)似的宏定義不是萬能的,有些場景下也是會出錯的
- C語言下大括號內(nèi)定義同名局部變量的問題的解決方法,值得借鑒
- 宏定義轉(zhuǎn)內(nèi)聯(lián)函數(shù),看似一個最佳實踐,實則還是需要具體問題具體分析,否則會引入不必要的問題
- 遇到問題,需要冷靜分析問題,解決一個問題還得看下關(guān)聯(lián)的問題有沒有影響
- weak函數(shù)大有益處(下回寫文再細(xì)講)
5 更多分享
歡迎關(guān)注我的github倉庫01workstation,日常分享一些開發(fā)筆記和項目實戰(zhàn),歡迎指正問題。
同時也非常歡迎關(guān)注我的專欄:有問題的話,可以跟我討論,知無不答,謝謝大家。
-
操作系統(tǒng)
+關(guān)注
關(guān)注
37文章
7077瀏覽量
124909 -
RTOS
+關(guān)注
關(guān)注
24文章
840瀏覽量
120723 -
FreeRTOS
+關(guān)注
關(guān)注
12文章
487瀏覽量
63714
發(fā)布評論請先 登錄
【freeRTOS開發(fā)筆記】關(guān)注創(chuàng)建任務(wù)時傳入優(yōu)先級數(shù)值問題

基于STM32的USB程序開發(fā)筆記 匯總
Modbus庫開發(fā)筆記之十一:關(guān)于Modbus協(xié)議棧開發(fā)的說明
壇友經(jīng)驗分享之STM32的USB程序開發(fā)筆記
Odrive開發(fā)筆記 精選資料推薦
lua開發(fā)筆記分享
基于LM3S網(wǎng)絡(luò)開發(fā)筆記3_多網(wǎng)頁開發(fā)

基于LM3S網(wǎng)絡(luò)開發(fā)筆記1_開發(fā)平臺

lua開發(fā)筆記(1)

評論