HarmonyOS 鸿蒙Next中SYS_RUN()和MODULE_INIT()之间的那些事
HarmonyOS 鸿蒙Next中SYS_RUN()和MODULE_INIT()之间的那些事 接触鸿蒙设备开发有一段时间了,也是时候好好挖一挖鸿蒙设备程序的启动流程了。
破冰问题:鸿蒙设备程序从哪里开始运行的?
相信大家都已经非常清楚了,鸿蒙设备程序需要指定入口函数,具体表现在代码层面就是通过语句 SYS_RUN(app_entry);
指定,其中 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)
了。
仔细观察这两个宏拼接出来的符号!
可以发现它们都和 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
的数据段中!
查看最终可执行程序中的符号信息和段信息可证明这个结论。
总结:
- 通过强制编译链接构成一个全局指针数组(每个
SYS_RUN()
定义一个数组元素) - 在链接脚本中定义符号自动确认这个数组的起始地址和结束地址
MODULE_INIT()
通过遍历的方式调用数组元素所指向的函数
PS:
大家如果感兴趣可以自己亲手动手实验一下,所需材料和工具可在附件中下载(.zip格式)。
- 编译附件中的
hello_world
工程(基于Hi3861) - 将下面编译得到的目标文件拷贝到工具目录
\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
- 执行命令观察结果
./nm hello_world.o
./objdump -h hello_world.o
./nm Hi3861_wifiiot_app.out
./objdump -h Hi3861_wifiiot_app.out
感叹一下,这真是一个精妙绝伦的设计方案!这样设计,理论上支持任意多的设备程序,开发者只需要简单的指定程序入口即可,完全不用关心背后的机制,也不用担心最多支持多少程序的问题。
这一招,学到了!!!
文中涉及多个进阶知识点,可参考狄泰软件学院《C语言进阶剖析教程》和《唐老师的私房课》。
刚看完老师前年讲的《轻松掌握鸿蒙开发板外设控制》课程,收获满满,想找更多老师的课程来看!
更多关于HarmonyOS 鸿蒙Next中SYS_RUN()和MODULE_INIT()之间的那些事的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html
在HarmonyOS(鸿蒙)Next中,SYS_RUN()
和MODULE_INIT()
是两个关键的系统初始化函数,它们在系统启动过程中扮演不同的角色。
MODULE_INIT()
用于模块的初始化。每个模块在系统启动时需要通过MODULE_INIT()
函数进行初始化,以确保模块的功能在系统运行前准备就绪。MODULE_INIT()
通常包括资源分配、配置加载、硬件初始化等操作。该函数在系统启动的早期阶段被调用,确保模块在系统运行前完成初始化。
SYS_RUN()
是系统运行的主入口函数。在MODULE_INIT()
完成所有模块的初始化后,SYS_RUN()
被调用,标志着系统正式进入运行状态。SYS_RUN()
负责启动系统的核心服务、调度任务、处理事件等,确保系统能够正常运行并响应用户操作。
两者的主要区别在于调用时机和作用范围。MODULE_INIT()
在系统启动的早期阶段被调用,用于模块的初始化;而SYS_RUN()
在模块初始化完成后被调用,负责系统的整体运行。MODULE_INIT()
是系统启动的前提,SYS_RUN()
是系统运行的起点。
在HarmonyOS(鸿蒙OS)的Next版本中,SYS_RUN()
和MODULE_INIT()
是两个关键的系统初始化函数。MODULE_INIT()
用于模块的初始化,通常在系统启动时调用,负责模块的配置和资源分配。而SYS_RUN()
则是在模块初始化完成后,系统进入运行状态时调用,负责启动模块的核心功能和服务。两者的调用顺序是先MODULE_INIT()
后SYS_RUN()
,确保系统在模块准备就绪后正常运行。