2020征文-鴻蒙開發板SYS_RUN()和MODULE_INIT()之間的那些事
https://harmonyos.51cto.com/#zz
接觸鴻蒙設備開發有一段時間了,也是時候好好挖一挖鴻蒙設備程序的啟動流程了。
破冰問題:鴻蒙設備程序從哪里開始運行的?
相信大家都已經非常清楚了,鴻蒙設備程序需要指定入口函數,具體表現在代碼層面就是通過語句 SYS_RUN(app_entry); 指定,其中 app_entry 是設備程序入口函數名;而整個鴻蒙設備的啟動流程也可以順理成章的挖掘出來。如下圖:
這看起來非常完美了,解決了所有問題!可是,我覺得還是有不清楚的地方,即:MODULE_INIT(run) 干了什么事?為什么最終會調用到 app_entry() 這個入口函數?
接下來,我們逐個問題的解決!
本質問題:MODULE_INIT(run) 干了什么事?
要弄清楚這個問題,就得先來講講 SYS_RUN() 究竟是什么?!有同學可能會認為 SYS_RUN(app_entry); 是一個函數調用語句,將設備程序入口地址注冊到系統中,進而調用。從原理上這么理解沒錯,可細節上根本不是那么回事! SYS_RUN() 在用法上很像函數,但本質是一個宏!必須強調: 在 C 語言中無法在函數之外進行函數調用,而 SYS_RUN(app_entry); 出現的位置并不在任何函數中,所以它不可能是函數調用。那會是什么呢?真相只有一個,只可能是一個定義(聲明)語句。為了證明這個結論,我們將 SYS_RUN() 這個宏徹底扒光了看個透徹。如下:
剖析:
最終,我們可以知道:SYS_RUN(app_entry); 是定義了一個名為 __zinitcall_run_app_entry 的函數指針,其類型是 InitCall,無論是否使用都不會編譯報錯,并且強制編譯使其最終存放在名為 .zinitcall.run2.init 的段中。
好!接下來就可以直接分析 MODULE_INIT(run) 了。
MODULE_INIT(run) 展開之后根本看不出和 app_entry 有任何關系啊!我們用了九牛二虎之力把宏掰開了,可結果貌似一無所獲!!!MODULE_INIT() 和 SYS_RUN() 之間的關系還是非常不明朗,接下來該怎么辦呢?
仔細觀察這兩個宏拼接出來的符號!

可以發現它們都和 zinitcall 有關,并且我們也知道了 SYS_RUN(app_entry) 定義的全局指針就放在名為 .zinitcall.run2.init 的段中,所以可以推測:這個兩個宏的關系是通過鏈接腳本關聯的。
接下來,通過工具查看目標文件的段信息和符號信息。

通過輸出可以知道,在名為 .zinitcall.run2.init 的段中確實存在 __zinitcall_run_app_entry 這個符號。
之后,動手翻源碼。。。。
經過努力,我們可以找到 \code-1.0\vendor\hisi\hi3861\hi3861\build\build_tmp\scripts\link.lds 文件,并且發現如下的腳本代碼:

這樣就真相大白了:
SYS_RUN(app_entry) 定義的函數指針 __zinitcall_run_app_entry 通過強制編譯的方式進入 .zinitcall.run2.init 段中。在鏈接腳本中定義的兩個符號 __zinitcall_run_start (理解為數組名)和 __zinitcall_run_end 分別指向 __zinitcall_run_app_entry 所在數據段的起始位置和結束位置。 又因為 MODULE_INIT(run) 的功能就是遍歷 __zinitcall_run_start 和 __zinitcall_run_end 所指定的區域(理解為函數指針數組),并調用每個單元(指針)所指向的函數,因此,__zinitcall_run_app_entry 所指向的函數必然被調用,即:app_entry() 必然被調用。
更進一步閱讀這個鏈接腳本可知:目標文件中的 .zinitcall.run2.init 段最終會被鏈接并匯編進一個名為 .zInit 的數據段中!
查看最終可執行程序中的符號信息和段信息可證明這個結論。
最終可執行程序中存在 __zinitcall_run_app_entry , __zinitcall_run_start 以及 __zinitcall_run_end, 并且 __zinitcall_run_app_entry 和 __zinitcall_run_start 的地址均為 0x004aeb1c,根據輸出的段信息可知,它們均位于 .zInit 段中。證畢!
總結:
- 通過強制編譯鏈接構成一個全局指針數組(每個 SYS_RUN() 定義一個數組元素)
- 在鏈接腳本中定義符號自動確認這個數組的起始地址和結束地址
- MODULE_INIT() 通過遍歷的方式調用數組元素所指向的函數
PS:
大家如果感興趣可以自己親手動手實驗一下,所需材料和工具可在文末附件中下載。
1)編譯附件中的 hello_world 工程(基于Hi3861)
2)將下面編譯得到的目標文件拷貝到工具目錄
\code-1.0\out\wifiiot\obj\applications\sample\wifi-iot\app\hello_world\hello_world.o
\code-1.0\out\wifiiot\Hi3861_wifiiot_app.out
3)執行命令觀察結果
./nm hello_world.o
./objdump -h hello_world.o
./nm Hi3861_wifiiot_app.out
./objdump -h Hi3861_wifiiot_app.out
感嘆一下,這真是一個精妙絕倫的設計方案!這樣設計,理論上支持任意多的設備程序,開發者只需要簡單的指定程序入口即可,完全不用關心背后的機制,也不用擔心最多支持多少程序的問題。
這一招,學到了!!!
https://harmonyos.51cto.com/#zz
【編輯推薦】