STM32:main函式退出後發生什麼?
我們都在說微控制器要執行在無限迴圈裡,不能退出,可退出之後會發生什麼?
討論STM32啟動過程的文章數不勝數,可main函式結束之後會發生什麼卻少有討論。
幾日前突然想到這個問題,便開始了探究。
如果不想看冗長的調查和實驗過程,可以直接到文章底部看結論,也有流程圖版哦。
網上搜尋
可能因為大家不太關心這種情況,我沒有找到有關論述微控制器main函式退出的文章。不過在ST Community、阿莫BBS、StackOverflow看到有人在問同樣的問題,下面摘錄了一些不同角度的回答:
- C語言環境角度,三種可能性
- 編譯器在main函式後加入隱性的無限迴圈
- 編譯器在main外面新增一層無限迴圈
- CPU繼續向下取址執行(也就是跑飛了)
- 微控制器設計角度,退出會引發異常、事件等
- 實際測試,網友們得到的結果卻不太一樣
- 有的會自動迴圈,像是自動復位了
- 有的會迴圈同一段彙編
可以看出,答案眾說紛紜,並沒有權威性,於是就轉向了最權威的資料:Keil手冊,arm官方工具鏈文件。
文件查閱
為了尋找main外面的呼叫情況,我們要從熟悉的啟動程式碼開始:
Reset_Handler PROC
EXPORT Reset_Handler [WEAK]
IMPORT SystemInit
IMPORT __main
LDR R0, =SystemInit
BLX R0
LDR R0, =__main
BX R0
ENDP
我們知道,是__main
呼叫了使用者main函式,在手冊1.8.1 Initialization of the execution environment and execution of the application
這一小節,概述了__main
的作用:
- 複製RO和RW段的內容,必要的話進行解壓縮
- 初始化ZI段(置零)
- 呼叫
__rt_entry
那這個__rt_entry
是十分的重要啊!
按圖索驥,__rt_entry
的功能有以下幾點:
- 呼叫函式初始化堆疊
- 初始化C庫,runtime
- 呼叫使用者的main
- Calls exit() with the value returned by main()
情況不唯一,第四步的exit()可以換為另外兩個退出函式,他們三個退出函式的關係在後面會提到。
情況變得明朗起來,只要找到這個exit()
呼叫的實現即可。我在stdlib.h中找到了exit
的宣告:
extern _ARMABI_NORETURN void exit(int /*status*/);
/*註釋有刪減,刪掉了不少次要內容,有興趣可以去看一看。
* First, all functions registered by the atexit function are called.
* Next, all open output streams are flushed, all open streams are closed,
* and all files created by the tmpfile function are removed.
* Finally, control is returned to the host environment.
*/
總結下來有三個功能:1. 呼叫之前註冊過的atexit函式 2. 關閉C執行時 3. 向宿主環境上交控制權。
然而具體實現細節還是未知的,我們回到__rt_entry
的文件中看看:
最後一步,必須呼叫exit
、__rt_exit
、_sys_exit
三個中的一個。然而仔細觀察他們三個的功能,是不是能察覺出一絲重複的意味。在功能上,exit
包含__rt_exit
包含_sys_exit
,顯然他們三個不會是毫無關聯的。
在閱讀完所有相關文件後,我們能得出結論:exit
呼叫__rt_exit
呼叫_sys_exit
,後面實驗中的彙編也印證了這一點。
然而,其中的_sys_exit
是不是看起來很眼熟呢?相信用過STM32的朋友都瞭解串列埠列印除錯與printf
函式重定向(只討論不使用microlib的情況),其中會有這樣一段函式定義:
void _sys_exit(int x) //避免半主機模式
{
x = x;
}
如果閱讀了1.6.4 Using the libraries in a nonsemihosting environment
這一節,我們就會發現_sys_exit
是典型的依賴半主機模式的呼叫。因為啟動程式碼中的函式一路呼叫會呼叫到_sys_exit
上去,所以在非半主機模式下我們需要自己提供它的定義。
Semihosting,半主機模式會把標準C庫中的一些應該提供的函式使用特定的指令交給除錯主機來實現。由
1.8.5 Direct semihosting C library function dependencies
可知這些函式包括:
_sys_exit
_sys_close
_sys_open
_sys_write
等,在半主機模式下,對這些函式直接或者間接的呼叫將轉化為特定的指令。在非半主機模式下,就需要手動實現被呼叫的函式。
半主機作為一種除錯手段,聽起來非常誘人,ARM自己的Keil MDK竟然不支援。既然半主機模式影響了必然會被呼叫的_sys_exit
,那就會影響到main函式退出之後的動向。在下一節的實測中,也確實體現出了巨大的差異。
實驗測試
晶片:STM32F407ZGT6
模擬器:DAP-Link
環境:ARMCC V5.06 update 6 ,Keil 5.25.2.0 , -O0
main函式內容如下:
int main(void){
GPIO_InitTypeDef GPIO_Initure;
HAL_Init(); //初始化HAL庫
Stm32_Clock_Init(336,8,2,7); //設定時鐘,168Mhz
__HAL_RCC_GPIOF_CLK_ENABLE(); //開啟GPIOF時鐘
GPIO_Initure.Pin=GPIO_PIN_9|GPIO_PIN_10; //PF9,10
GPIO_Initure.Mode=GPIO_MODE_OUTPUT_PP; //推輓輸出
GPIO_Initure.Pull=GPIO_PULLUP; //上拉
GPIO_Initure.Speed=GPIO_SPEED_HIGH;
//開燈
HAL_GPIO_WritePin(GPIOF,GPIO_PIN_9,GPIO_PIN_RESET);
HAL_GPIO_WritePin(GPIOF,GPIO_PIN_10,GPIO_PIN_RESET);
HAL_Delay(1000);
//關燈
HAL_GPIO_WritePin(GPIOF,GPIO_PIN_9,GPIO_PIN_SET);
HAL_GPIO_WritePin(GPIOF,GPIO_PIN_10,GPIO_PIN_SET);
HAL_Delay(1000);
}
PF9和PF10是開發板上兩顆LED燈,能提供直觀展示。
非半主機
現象:兩顆LED不斷閃動,就像處於迴圈之中。
為了找出原因,自然是要開始打斷點+單步除錯彙編。
此時的彙編是這樣的:
繼續向下取址的話,接下來會彈棧,也將返回到呼叫main
的函式__rt_entry
中:
這也印證了之前的推斷,在預設情況下,呼叫的是exit
。實際執行與之前分析一致,exit
呼叫__rt_exit
呼叫_sys_exit
。
最後呼叫的,我們自己定義的_sys_exit
,可以看出x=x
被編譯器優化成為一句空指令。
重點在於接下來,按照手冊上說,_sys_exit
將會把控制權交回宿主環境,此時C執行庫已經被關閉。然而下一句彙編BX lr
直接將函式返回0x08000227
,也就是__rt_exit
函式呼叫_sys_exit
的下文。在上上張圖中,可以發現程式碼又回到了熟悉的啟動程式碼,接下來,時鐘、堆疊、C庫依次初始化,main函式被呼叫,形成迴圈。
這就是退出主函式後表現為迴圈的原因。
半主機
如果想進入半主機模式,我們可以將#pragma import(__use_no_semihosting)
這句巨集刪除,之後把自定義的_sys_exit
等函式註釋掉,再進行編譯、下載、除錯。
現象:LED燈亮滅一次,無後序現象。
啟動以及退出流程與非半主機完全一樣,除了在呼叫_sys_exit
時會變為相應的核心特定指令。ARM處理器在進入半主機模式時會呼叫trap instruction
,對於所有的Cortex-M
微處理器來說,這個指令是BKPT 0xAB
。緊接著,就進入了跳轉到自己的死迴圈。
至此,微控制器陷入空白死迴圈,形成了前文所說的執行一次現象。
結論
通過查閱官方文件,以及除錯實測,我們能得出結論:
在關閉半主機模式下,STM32的使用者main函式退出了,微控制器將會復位,形成迴圈的效果。開啟半主機模式下,如果退出主程式,會在空迴圈卡死,表現為只會執行一遍主函式內容。補充一點,如果使用微庫(microlib),文件中明文禁止退出main函式。
使用流程圖表示如下:
這篇文章所討論的退出主函式,對於沒有OS的微控制器來說,可以說是一種未定義行為,本身是不安全的、不被推薦的。以上的討論與實驗。雖然實用性不高,但在學習過程中仍有不少的收穫。
這個主題原本是偶然想到的,花費了一些精力,算把這個問題弄清楚一些了。同時,在這個過程中產生了更多的疑問:將啟動程式碼放置在_sys_exit
之後是ARM還是ST的安排?是在哪一步實施的?文中的實驗具有普適性嗎?等等疑問還等待著解答。
技術新人,水平有限,希望各位前輩、高人不吝賜教,如有錯誤請一定指出。更多嵌入式原創文章可以來公眾號,來找我聊聊天吧:
歡迎轉載,請註明作者與原文連結。
作者:胡小安