鸿蒙系统中HelloWorld程序是如何被调用的?

百家 作者:51CTO技术栈 2020-10-29 20:12:59

相信大家都已经在鸿蒙系统上实现了自己的第一个 HelloWorld 程序了。


代码很简单,编译烧录后,我们就可以看到串口有打印 [DEMO] Hello world.。


但是 HelloWorld 函数是在何时被调用的呢?SYS_RUN 又是干嘛的呢?我们来看下。


01

启动流程


首先,我们需要分析一下 Hi3861 的启动流程。目前 Hi3861 使用的是 liteOS-M 内核,相关源码厂家没有提供,不过也不妨碍我们。


经过我一番查找,可以知道 Hi3861 启动内后,第一个入口函数是 app_main函数。
vendor\hisi\hi3861\hi3861\app\wifiiot_app\src\app_main.c

大家可以打开,看到 app_main 函数的内容,如下,当然我这里只是简版的,我删除了很多初始化的函数,只保留最终要的。
hi_void app_main(hi_void)
{
 //打印sdk版本
 const hi_char* sdk_ver = hi_get_sdk_version();
    printf("sdk ver:%s\r\n", sdk_ver);

 //串口、IO初始化等
 peripheral_init();

 //wifi初始化
 ret = hi_wifi_init(APP_INIT_VAP_NUM, APP_INIT_USR_NUM);


 //鸿蒙系统初始化
 HOS_SystemInit();
}

我们可以看到其实 app_main 启动后做了很多工作,包括 io 初始化、wifi 初始,最后调用了 HOS_SystemInit();进行鸿蒙系统最后的初始化。


那我们看下 HOS_SystemInit();做了什么动作吧。打开源码:
base\startup\services\bootstrap_lite\source\system_init.c

可以看到函数内容如下:
void HOS_SystemInit(void)
{
    MODULE_INIT(bsp);
    MODULE_INIT(device);
    MODULE_INIT(core);
    SYS_INIT(service);
    SYS_INIT(feature);
    MODULE_INIT(run);
    SAMGR_Bootstrap();
}

看起来好像在调用某些模块,仔细看,其中有一个是 MODULE_INIT(run);。


顾名思义,好像在初始化或者调用 一个 run 模块。那 run 模块又是什么呢?


我们看下标题的 SYS_RUN(HelloWorld)。是不是可以猜测其实 MODULE_INIT(run);就是调用了 HelloWorld 呢?


哈哈哈~其实还真是。大家如果加打印信息,可以看到如下打印:
../../base/startup/services/bootstrap_lite/source/system_init.c 38 

../../applications/sample/wifi-iot/app/my_first_app/hello_world.c 9 

[DEMO] Hello world.

../../base/startup/services/bootstrap_lite/source/system_init.c 40

仔细看我加的打印语句,确实是在 38 行执行 MODULE_INIT(run);后才打印 [DEMO] Hello world.。


所以跟我们猜测的一样。当然没完,我们得分析为啥是这样。

02

链接


我们看下 MODULE_INIT(run);做了什么。事实上,它只是一个宏。
#define MODULE_INIT(name)     \

    do {                      \

        MODULE_CALL(name, 0); \

    } while (0)

而 MODULE_CALL(name, 0);又可以展开:当然里面的 if 语句的打印是我后面加的。
我们可以看到,它其实是定义了一个  InitCall 指针,然后指针是这个:
(MODULE_BEGIN(name, step))

而 MODULE_BEGIN 宏其实展开后如下:
#define MODULE_NAME(namestep) ".zinitcall." #name #step ".init"

我这里再帮大家展开,其实".zinitcall." #name #step ".init" 最后就是 .zinitcall.run2.init。


它其实是一种写法,就是说我们代码编译的时候,代码里面有一段地址比较特殊,它的名字是 .zinitcall.run2.init ,也就是说 InitCall 指针指向的是 .zinitcall.run2.init 代码段的地址。


画个图:

绿色的是 .zinitcall.run2.init 代码段,里面存放着函数指针。


好了,到这里大家应该都明白了吧,继续看这个图,其实 这里只不过是把这个代码段里面的所有函数指针都取出来,然后执行一下函数指针指向的函数。

 聪明的你应该就猜到了,.zinitcall.run2.init 代码段里面的函数指针,指向的就是 HelloWorld 函数了~~~

到了这里就剩下最后一个问题了:怎么让它指向 HelloWorld 函数。


这里其实就是 SYS_RUN的功劳了。我们也来看SYS_RUN做了什么,其它也是一个宏,展开过程如下:


我们可以看到,其实 SYS_RUN(HelloWorld) 其实最终结果就是:
static const InitCall USED_ATTR __zinitcall_##layer##_##func \

        __attribute__((section(".zinitcall." clayer #priority ".init"))) = HelloWorld

看起来很复杂,我们不乏拆解一下:

我们先不看红色字体部分,那么结果就是:
static const InitCall  = HelloWorld

是不是很简单,其实就是定义了一个全局变量(函数指针),它指向 HelloWorld 。


那红色字体是做什么用呢?它其实就是告诉编译,我这个变量(static const InitCall 变量),很特殊,编译的时候给我编译在 .zinitcall.run2.init 段。


到了,一切都明了了,最后来一张启动流程图总结一下:

03

忠告


这里有两个忠告:


①请不要直接在 SYS_RUN() 定义的入口函数直接写 while(1)


这个很简单理解了,因为系统启动后,app_main 会调用到 我们定义的 SYS_RUN() 定义的入口函数,比如 HelloWorld。


如果我们在 HelloWorld 函数中写了 while(1) 就会导致 app_main 后续的代码得不到执行,肯定有问题。


②SYS_RUN() 定义的入口函数创建的线程,请一定要有 sleep 动作


为了解决第一个问题,我们很自然地想到,可以在 SYS_RUN() 定义的入口函数 创建线程,这样就可以 while(1) 了。


哈哈,其实也是有问题,因为 app_main 本身也是一个任务,如果我们自己创建地任务优先级特别高,就会导致 app_main 任务不会被执行,还是有问题。


所以要有 sleep,确保 app_main 后续地代码能顺利执行下去。


关注公众号:拾黑(shiheibook)了解更多

[广告]赞助链接:

四季很好,只要有你,文娱排行榜:https://www.yaopaiming.com/
让资讯触达的更精准有趣:https://www.0xu.cn/

公众号 关注网络尖刀微信公众号
随时掌握互联网精彩
赞助链接