Hi3861_WiFi IoT工程:理解啟動恢復子系統
Hi3861_WiFiIoT工程的一點理解
目錄
- 1.關于工程本身
- 2.ohos_bundles
- 3.工程的目錄結構
- 4.理解IoT外設控制模塊
- 4.1 BUILD.gn 的展開
- 4.2 led_example.c 的展開
- 4.3 IoT外設控制模塊的整體理解
- 5.理解啟動恢復子系統
- 5.1 #A分析SYS_INIT(service)
- 5.2 #B分析MODULE_INIT(run)
- 5.3 #C分析SAMGR_Bootstrap()
- X.總結:
更新記錄:
- 說明:本文是 "Hi3861_WiFi IoT工程的一點理解" 的新增章節,版本升級到v1.6.
- 前面章節見:
- Hi3861_WiFi IoT工程的一點理解v1.0
- Hi3861_WiFi IoT工程:理解IoT外設控制模塊
5.理解啟動恢復子系統
這是一個非常重要的子系統,我在之前《鴻蒙系統的啟動流程》一文中做過一些簡單的分析,建議先去看一下《鴻蒙系統的啟動流程v3.0》Part 1/2的3/4章節。這里就先到鴻蒙系統來整體看一下它具體是怎么回事,然后再回到本工程來對比看hpm的裁剪給我們留下了什么。
仍然是先看官方readme 和重新整理目錄結構。
- 啟動恢復子系統負責從內核啟動之后到應用啟動之前的系統關鍵服務進程的啟動過程以及設備恢復出廠設置的功能。涉及以下組件:
- init啟動引導組件
- init啟動引導組件對應的進程為init進程,是內核完成初始化后啟動的第一個用戶態進程。init進程啟動之后,讀取init.cfg配置文 件,根據解析結果,執行相應命令并依次啟動各關鍵系統服務進程,在啟動系統服務進程的同時設置其對應權限。
- appspawn應用孵化組件
- 負責接收用戶程序框架的命令孵化應用進程,設置新進程的權限,并調用應用程序框架的入口函數。
- bootstrap服務啟動組件
- 提供了各服務和功能的啟動入口標識。在SAMGR啟動時,會調用boostrap標識的入口函數,并啟動系統服務。
- syspara系統屬性組件
- 系統屬性組件,根據HarmonyOS產品兼容性規范提供獲取設備信息的接口,如:產品名、品牌名、廠家名等,同時提供設置/讀 取系統屬性的接口。
- startup啟動組件
- 負責提供大型系統(參考內存≥1GB)獲取與設置操作系統相關的系統屬性。
- 大型系統支持的系統屬性包括:設備信息如設備類型、產品名稱等,系統信息如系統版本、API版本等默認系統屬性。
兩相比較就可以看到Hi3861工程相對于完整系統裁剪掉了appspawn_lite 和 init_lite兩個組件(先灰化掉了),因為啟動方式/流程上有比較大的差別,裁掉init_lite其實很容易理解,但為什么裁掉appspawn_lite我還沒仔細研究。
這里就只分析Hi3861的bootstrap_lite,至于syspara_lite比較簡單,看官方文檔照著上表右邊的調用順序就可以獲取屬性信息了。appspawn_lite 和 init_lite兩個組件,待我把相關細節搞清楚了,再完善到《鴻蒙系統的啟動流程》的更新版本中,或者單獨寫一個理解總結出來。
下面的文字,其實也算是《鴻蒙系統的啟動流程v3.0》Part 2的“4.第四階段:鴻蒙系統框架層的啟動”的完整分析版本。
官方readme 對bootstrap服務啟動組件的描述就兩句話,該怎么理解:
“提供了各服務和功能的啟動入口標識。” 就是指在//base/startup/services/bootstrap_lite/source/core_main.h 頭文件中定義的宏:SYS_INIT(name)和MODULE_INIT(name)。
- 【在這里又要強烈推薦去看:
- 連志安老師的《分析 helloworld程序是如何被調用,SYS_RUN做什么事情》
- 唐佐林老師的《SYS_RUN()和MODULE_INIT()之間的那些事》
- 這兩篇文章了。】
“在SAMGR啟動時,會調用boostrap標識的入口函數,并啟動系統服務。”就是指在system_init.c文件中的HOS_SystemInit()函數調用 SAMGR_Bootstrap(); 去啟動系統服務了。什么是“boostrap標識的入口函數”,我們在下面會解釋。
- void HOS_SystemInit(void)
- {
- MODULE_INIT(bsp);
- MODULE_INIT(device);
- MODULE_INIT(core);
- SYS_INIT(service); //#A
- SYS_INIT(feature);
- MODULE_INIT(run); //#B
- SAMGR_Bootstrap(); //#C
- }
灰掉部分目前我還未涉足,先跳過,但要是理解了下面的內容,灰掉部分也就基本上理解了。
5.1 #A分析SYS_INIT(service)
打開//base/startup/services/bootstrap_lite/source/core_main.h 文件,工程編譯是使用gcc編譯器的,所以__GNUC__是有定義的。
把service代進去展開一下:
- #define SYS_INIT(service) \
- do { \
- SYS_CALL(service, 0); \
- } while (0)
- #define SYS_CALL(service, 0) \
- do { \
- InitCall *initcall = (InitCall *)(SYS_BEGIN(service, 0)); \
- InitCall *initend = (InitCall *)(SYS_END(service, 0)); \
- for (; initcall < initend; initcall++) { \
- (*initcall)(); \
- } \
- } while (0)
SYS_BEGIN和SYS_END就不展開了,重點在for循環,可能還是不太好理解,我再把它翻譯成大白話:
- for循環就是從 initcall 地址開始,到 initend地址(不含)結束,
- 依次調用*initcall內的地址所指向的函數,執行函數內的指令。
- initcall 是一個指針,其內容 *initcall是符號__zinitcall_sys_service_start的地址,即 &__zinitcall_sys_service_start,
- initend 是一個指針,其內容 *initend 是符號__zinitcall_sys_service_end的地址,即 &__zinitcall_sys_service_end
Hi3516/Hi3518平臺工程,打開 build\lite\platform\......\link.ld
Hi3861工程則是 vendor\hisi\hi3861\hi3861\build\link\link.ld.S
可以看到上面的 start/end 符號,但估計你打開文件,看到里面的東西,心里還是會有很大的問號。
那就直接去看編譯后輸出的map文件:out\wifiiot\Hi3861_wifiiot_app.map,文本編輯器打開該文件,搜索一下:
這里有三個service 要 init,從抓回來的log看,確實如此:
這下應該夠清楚了吧?
SYS_INIT(service) 是調用端的宏,對應的,定義端也有一個對應的宏SYS_SERVICE_INIT(xxx)。
工程代碼全局搜索一下“SYS_SERVICE_INIT”,把沒什么用的 sample和 .h中的先去掉,就得到四個:
前三個就是上面的三個service。
第四個“SYS_SERVICE_INIT(InitializeRegistry);”在
foundation\distributedschedule\services\samgr_lite\samgr_server\source\samgr_server.c 文件中,
看它的 BUILD.gn 文件“shared_library("server")”,再到上級目錄查看BUILD.gn,
- if (ohos_kernel_type == "liteos_a" || ohos_kernel_type == "linux"){
- features += [
- "samgr_server:server",
- "samgr_client:client",
- ]
- }
這是LiteOS_A或Linux內核的平臺才會有的,所以Hi3861平臺的log上看不到這個server的log。
上面的三個service使用的宏SYS_SERVICE_INIT(Init),我們也一步一步展開,
- #define SYS_SERVICE_INIT(func) LAYER_INITCALL_DEF(func, sys_service, "sys.service")
- #define LAYER_INITCALL_DEF(func, layer, clayer) \
- LAYER_INITCALL(func, layer, clayer, 2) //默認優先級 2
- #define LAYER_INITCALL(func, layer, clayer, priority) \
- static const InitCall USED_ATTR __zinitcall_##layer##_##func \
- __attribute__((section(".zinitcall." clayer #priority ".init"))) = func
__attribute__((section(“section_name”))) 其作用是將函數或數據放入指定名為"section_name"對應的段中。
最后分別得到:
- //.zinitcall.sys.service2.init = Init //bootstrap_service 的Init
- //.zinitcall.sys.service2.init = Init //broadcast_service 的Init
- //.zinitcall.sys.service2.init = Init //hiview_service 的Init
編譯器根據attribute+section關鍵字,就把三個Init函數全都編譯鏈接到了 .zinitcall.sys.service2.init 對應的段中,
結果就是上面Hi3861_wifiiot_app.map文件截圖的樣子。
5.2 #B分析MODULE_INIT(run)
對于MODULE_INIT(Xxx) 和對應的SYS_RUN(Xxx)的分析,和上面沒什么差別,只是編譯鏈接的時候,將函數放到不同的段中去而已。
我在應用層放了兩個APP,分別是helloworld和led_example,log中也看到對應的log了。
5.3 #C分析SAMGR_Bootstrap()
SAMGER:system ability manager。系統服務框架子系統,這是一個非常核心的子系統了,我會另開一章來分析。不過這里還是先簡單了解一下它在這一步,做了些什么事情。
先看官方文檔:
由于平臺資源有限,且硬件平臺多樣,因此需要屏蔽不同硬件架構和平臺資源的不同、以及運行形態的不同,提供統一化的系統服務開發框架[system ability (SA) framework]。根據RISC-V、Cortex-M、Cortex-A不同硬件平臺,分為兩種硬件平臺,以下簡稱M核、A核。
- M核:處理器架構為Cortex-M或同等處理能力的硬件平臺,系統內存一般低于512KB,無文件系統或者僅提供一個可有限使用的輕量級文件系統,遵循CMSIS接口規范。
- A核:處理器架構為Cortex-A或同等處理能力的硬件平臺,內存資源大于512KB,文件系統完善,可存儲大量數據,遵循POSIX接口規范。
系統服務框架基于面向服務的架構,提供了服務開發、服務的子功能開發、對外接口的開發、以及多服務共進程、進程間服務調用等開發能力。其中:
- M核:包含服務開發、服務的子功能開發、對外接口的開發以及多服務共進程的開發框架。
- A核:在M核能力基礎之上,包含了進程間服務調用、進程間服務調用權限控制、進程間服務接口的開發等能力。
約束
- 系統服務開發框架統一使用C開發。
- 同進程內服務間調用統一使用IUnknown接口對外象,消息接口統一由IUnknown接口傳遞給本服務。
- 服務名和功能名必需使用常量字符串且長度小于16個字節。
- M核:系統依賴上bootstrap服務,在系統啟動函數中調用OHOS_SystemInit()函數。
- A核:系統依賴samgr庫,在main函數中調用SAMGR_Bootstrap()函數。
再看一個完整一點的log:
看log,在 SYS_INIT(service) 這一步,bootstrap_service 首先init,
打開 base\startup\services\bootstrap_lite\source\bootstrap_service.c 查看它的 Init 函數,
- static void Init(void)
- {
- static Bootstrap bootstrap;
- bootstrap.GetName = GetName;
- bootstrap.Initialize = Initialize;
- bootstrap.MessageHandle = MessageHandle;
- bootstrap.GetTaskConfig = GetTaskConfig;
- bootstrap.flag = FALSE;
- printf("[bootstrap_service] SYS_SERVICE_INIT(Init).\n");
- SAMGR_GetInstance()->RegisterService((Service *)&bootstrap);
- }
它會先去get一個SAMGR的instance實例,向這個實例注冊bootstrap服務。
進入SAMGR_GetInstance(),在 foundation\distributedschedule\services\samgr_lite\samgr\source\samgr_lite.c文件中:
bootstrap_service 是第一個調用SAMGR_GetInstance() 的服務,這時候全局變量g_samgrImpl還沒有初始化,所以就要先init,然后就可以返回instance給Bootstrap 注冊服務用了,后面的broadcast_service、hiview_service在 init時,直接就可以拿到instance去注冊了。
全局變量g_samgrImpl記錄了向它注冊的所有服務的信息,包括了一組四個函數:
GetName/Initialize/MessageHandle/GetTaskConfig,這就是上面提到的“boostrap標識的入口函數”。
bootstrap_service、broadcast_service、hiview_service在SYS_INIT(service)這一步只能做很簡單的注冊服務的事情,否則會導致后面的INIT受阻。
在SAMGR_Bootstrap(); 這一步時,SAMGR才會真正根據注冊在g_samgrImpl的信息,逐一為已注冊的服務創建和分配資源,InitializeAllServices,AddTaskPool,SAMGR_StartTaskPool,SAMGR_SendSharedDirectRequest等待系統調度,然后在HandleInitRequest中才真正調用各自serveice注冊的Initialize接口去完成服務的啟動,為系統提供服務。