专栏名称: 51CTO技术栈
有趣 | 有料 | 有内涵,为您提供最优质的内容,愿我们一起悦享技术,成就人生。
目录
相关文章推荐
51好读  ›  专栏  ›  51CTO技术栈

挖一挖鸿蒙设备程序的启动流程!

51CTO技术栈  · 公众号  · 程序员  · 2020-12-10 18:05

正文

接触鸿蒙设备开发有一段时间了,也是时候好好挖一挖鸿蒙设备程序的启动流程了。


图片来自 Pexels


1

破冰问题:鸿蒙设备程序从哪里开始运行的?


相信大家都已经非常清楚了,鸿蒙设备程序需要指定入口函数,具体表现在代码层面就是通过语句 SYS_RUN(app_entry); 指定,其中 app_entry 是设备程序入口函数名;而整个鸿蒙设备的启动流程也可以顺理成章的挖掘出来。


如下图:

这看起来非常完美了,解决了所有问题!可是,我觉得还是有不清楚的地方,即:MODULE_INIT(run) 干了什么事?为什么最终会调用到 app_entry() 这个入口函数?


接下来,我们逐个问题的解决!


2

本质问题:MODULE_INIT(run) 干了什么事?


要弄清楚这个问题,就得先来讲讲 SYS_RUN() 究竟是什么?!有同学可能会认为 SYS_RUN(app_entry); 是一个函数调用语句,将设备程序入口地址注册到系统中,进而调用。


从原理上这么理解没错,可细节上根本不是那么回事!SYS_RUN() 在用法上很像函数,但本质是一个宏!


必须强调: 在 C 语言中无法在函数之外进行函数调用,而 SYS_RUN(app_entry); 出现的位置并不在任何函数中,所以它不可能是函数调用。


那会是什么呢?真相只有一个,只可能是一个定义(声明)语句。为了证明这个结论,我们将 SYS_RUN() 这个宏彻底扒光了看个透彻。


如下:

3

剖析



最终,我们可以知道: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() 必然被调用。







请到「今天看啥」查看全文