Hi3861_WiFi IoT工程:理解启动恢复子系统 原创 精华
Hi3861_WiFiIoT工程的一点理解
liangkz 2021.04.28 v1.6
目录
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.
前面章节见:
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接口去完成服务的启动,为系统提供服务。
【本文还未结束,未来会继续添加对工程的新的理解。】
你我本无缘,网络一线牵
楼主要不建个专栏,理个文章合集呀~下回我就不迷路了哈哈
好主意,我先看看怎么搞。感谢提醒。
楼主肯专研的精神值得学习。
因为不懂,所以才想快点弄懂~~
已建专栏:鸿蒙系统学习笔记
欢迎订阅。
👍👍👍
那必须订阅嘿嘿~
哈哈哈
哈主要不建个专栏,理个文章合集呀~下回我就不迷路了哈哈
谢谢分享,不理解3861为啥启动代码不提供
支持支持~