#夏日挑战赛#【FFH】OpenHarmony设备开发基础(四)启动流程 原创 精华

Beazer
发布于 2022-7-29 19:58
浏览
3收藏

[本文正在参加星光计划3.0–夏日挑战赛]

一、前言

至于如何编写一个简单的Hello_World程序打印出来,应该不用多说了吧。上篇文章我们详细阐述了如何添加编译依赖使得最终指向my_first_demo.c文件,让我们的.c文件能够编译进去。本篇文章将介绍SYSTEM(demo)是如何将.c文件注册进OH从而执行的,即启动流程分析。

二、准备

话不多说,先上代码:

demo01.c如下
``` c
#include <stdio.h>
#include "ohos_init.h"
#include "ohos_types.h" 

void demo(void)
{
    printf("开源项目 OpenHarmony\n是每个人的OpenHarmony\n");
}

SYS_RUN(demo);

注意看该文件的最后一行,如果没有SYS_RUN(demo)的话,demo函数是不会被调用的。如果想要demo函数被调用,就必须要有HarmonyOS启动恢复模块接口SYS_RUN(demo)启动业务。那SYS_RUN是函数调用吗(虽然长得有点像)? 但因为C语言里的函数调用不能独立于任何函数体之外,所以,显然不是。简单来说,我们可以把SYS_RUN(demo)看作一个注册语句,注册demo。系统想要运行这个函数的话就要把这个函数加进来。注册后,demo作为一个入口就可以被运行。
至于为什么在SYS_RUN中注册demo后就能运行OpenHarmony设备程序,那得从内核启动后的第一个入口函数说起。
<br>
在原版相关教程提过放在vendor子系统下的两个重要文件:app_io_init.c和app_main.c。
其路径分别为:
vendor\hisi\hi3861\app\wifiiot_app\init\app_io_init.c
vendor\hisi\hi3861\app\wifiiot_app\src\app_main.c
其中,app_io_init.c是hi3861内核启动后的io相关设置,用户可以根据应用场景,合理选择各外设的IO复用配置。
我们可以找找现在的最新版官方代码(在原有注释的基础上我加了中文注释)来看看:

#include <hi_io.h>
#include <hi3861_platform.h>

hi_void app_io_set_gpio2_clkout_enable(hi_bool enable)
{
    if (enable == HI_TRUE) {
        hi_io_set_input_enable(HI_IO_NAME_GPIO_2, HI_TRUE);
        hi_reg_write16(DIAG_CTL_DIAG_MUX, 0x2);
        hi_reg_write16(DIAG_CTL_CLOCK_TEST_DIV, 0x0);
        hi_reg_write16(DIAG_CTL_CLOCK_TEST_EN, 0x3FFF);
        hi_reg_write16(DIAG_CTL_CLOCK_TEST_SEL, 0x1);
        hi_io_set_func(HI_IO_NAME_GPIO_2, 0x6); /* let gpio2 output clk. */
        hi_io_set_driver_strength(HI_IO_NAME_GPIO_2, HI_IO_DRIVER_STRENGTH_3);
    } else {
        hi_io_set_func(HI_IO_NAME_GPIO_2, HI_IO_FUNC_GPIO_2_GPIO);
        hi_io_set_input_enable(HI_IO_NAME_GPIO_2, HI_FALSE); /* set io enable false, for low power. */
        hi_io_set_pull(HI_IO_NAME_GPIO_2, HI_IO_PULL_NONE);
    }
}

hi_void app_io_init(hi_void)
{
    /*
     * You need to select the I/O multiplexing configuration of each peripheral
     * based on the application scenario. The following is an example.
     *用户需根据应用场景,合理选择各设备的IO复用配置,此处仅列出示例
     */
#ifdef CONFIG_UART0_SUPPORT
    /* Configure UART0 as the debugging serial port.uart0调试串口 */
    hi_io_set_func(HI_IO_NAME_GPIO_3, HI_IO_FUNC_GPIO_3_UART0_TXD); /* uart0 tx */
    hi_io_set_func(HI_IO_NAME_GPIO_4, HI_IO_FUNC_GPIO_4_UART0_RXD); /* uart0 rx */
#endif

#ifdef CONFIG_UART1_SUPPORT
    /* Configure UART1 as the AT command serial port.uart1 AT命令串口 */
    hi_io_set_func(HI_IO_NAME_GPIO_5, HI_IO_FUNC_GPIO_5_UART1_RXD); /* uart1 rx */
    hi_io_set_func(HI_IO_NAME_GPIO_6, HI_IO_FUNC_GPIO_6_UART1_TXD); /* uart1 tx */
#endif

#ifdef CONFIG_UART2_SUPPORT
    /* Configure UART2 as the SIGMA authentication serial port. uart2 sigma认证使用串口*/
    hi_io_set_func(HI_IO_NAME_GPIO_11, HI_IO_FUNC_GPIO_11_UART2_TXD); /* uart2 tx */
    hi_io_set_func(HI_IO_NAME_GPIO_12, HI_IO_FUNC_GPIO_12_UART2_RXD); /* uart2 rx */
#endif

    /* SPI MUX: */
#ifdef CONFIG_SPI_SUPPORT
    /* The SPI I/O multiplexing mode can also be 5/6/7/8 or 0/1/2/3 based on the product design. */
    /* SPI IO复用也可以选择5/6/7/8;0/1/2/3,根据产品设计选择 */
    hi_io_set_func(HI_IO_NAME_GPIO_9, HI_IO_FUNC_GPIO_9_SPI0_TXD);
    hi_io_set_func(HI_IO_NAME_GPIO_10, HI_IO_FUNC_GPIO_10_SPI0_CK);
    hi_io_set_func(HI_IO_NAME_GPIO_11, HI_IO_FUNC_GPIO_11_SPI0_RXD);
    hi_io_set_func(HI_IO_NAME_GPIO_12, HI_IO_FUNC_GPIO_12_SPI0_CSN);
    hi_io_set_driver_strength(HI_IO_NAME_GPIO_9, HI_IO_DRIVER_STRENGTH_0);
#endif

    /* I2C MUX: */
#ifdef CONFIG_I2C_SUPPORT
    /* The I2C I/O multiplexing mode can also be 3/4 or 9/10 based on the product design. */
    /* I2C IO复用也可以选择3/4;9/10,根据产品设计选择*/
    hi_io_set_func(HI_IO_NAME_GPIO_0, HI_IO_FUNC_GPIO_0_I2C1_SDA);
    hi_io_set_func(HI_IO_NAME_GPIO_1, HI_IO_FUNC_GPIO_1_I2C1_SCL);
#endif

    /* PWM MUX: */
#ifdef CONFIG_PWM_SUPPORT
    /* The configurations for multiplexing 0/2/3/4/5 into PWM are similar. */
    hi_io_set_func(HI_IO_NAME_GPIO_8, HI_IO_FUNC_GPIO_8_PWM1_OUT);
#endif

    /* I2S MUX: */
#ifdef CONFIG_I2S_SUPPORT
    /* PWM 0/2/3/4/5配置同理 */
    hi_io_set_func(HI_IO_NAME_GPIO_9, HI_IO_FUNC_GPIO_9_I2S0_MCLK);
    hi_io_set_func(HI_IO_NAME_GPIO_10, HI_IO_FUNC_GPIO_10_I2S0_TX);
    hi_io_set_func(HI_IO_NAME_GPIO_11, HI_IO_FUNC_GPIO_11_I2S0_RX);
    hi_io_set_func(HI_IO_NAME_GPIO_12, HI_IO_FUNC_GPIO_12_I2S0_BCLK);
    hi_io_set_func(HI_IO_NAME_GPIO_13, HI_IO_FUNC_GPIO_13_I2S0_WS);
#endif

    /* SDIO MUX: */
#ifdef CONFIG_SDIO_SUPPORT
    (hi_void)hi_io_set_func(HI_IO_NAME_GPIO_9, HI_IO_FUNC_GPIO_9_SDIO_D2);
    (hi_void)hi_io_set_func(HI_IO_NAME_GPIO_10, HI_IO_FUNC_GPIO_10_SDIO_D3);
    (hi_void)hi_io_set_func(HI_IO_NAME_GPIO_11, HI_IO_FUNC_GPIO_11_SDIO_CMD);
    (hi_void)hi_io_set_func(HI_IO_NAME_GPIO_12, HI_IO_FUNC_GPIO_12_SDIO_CLK);
    (hi_void)hi_io_set_func(HI_IO_NAME_GPIO_13, HI_IO_FUNC_GPIO_13_SDIO_D0);
    (hi_void)hi_io_set_func(HI_IO_NAME_GPIO_14, HI_IO_FUNC_GPIO_14_SDIO_D1);

    (hi_void)hi_io_set_pull(HI_IO_NAME_GPIO_9, HI_IO_PULL_UP);
    (hi_void)hi_io_set_pull(HI_IO_NAME_GPIO_10, HI_IO_PULL_UP);
    (hi_void)hi_io_set_pull(HI_IO_NAME_GPIO_11, HI_IO_PULL_UP);
    (hi_void)hi_io_set_pull(HI_IO_NAME_GPIO_13, HI_IO_PULL_UP);
    (hi_void)hi_io_set_pull(HI_IO_NAME_GPIO_14, HI_IO_PULL_UP);
#endif
}

如今官方代码的路径变成了device/soc/hisilicon/hi3861v100/sdk_liteos/app/demo/init/app_io_init.c。
<br>app_main.c则是内核启动进入的应用程序入口,如今官方代码的路径也变成了device/soc/hisilicon/hi3861v100/sdk_liteos/app/wifiiot_app/src/app_main.c
之前的文章我们提到过,vendor文件夹存放的是厂商相关的配置,包括组件配置、HDF 相关配置。原版vendor\hisi\hi3861存放的则是与hi3861相关产商SDK之类的文件,app_io_init.c作为hi3861内核启动后的io口相关配置文件,为什么在最新代码中被移进了device/soc中去呢?
当时我在写第一篇文章的时候还不能理解为什么教程里说的源码目录代码结构和我自己看到的代码结构不一样呢?原来是更新换代了呀。在《OpenHarmony设备开发基础【2022最新版】》一书中:
#夏日挑战赛#【FFH】OpenHarmony设备开发基础(四)启动流程 -鸿蒙开发者社区
“因为后续可能存在一个soc多个board的情况”。

三、启动流程介绍

好了,回到正题,继续从内核启动后的第一个入口函数app_main.c文件说起。
其实连老师的介绍已经写得非常详细了
OpenHarmony轻量系统开发【4】编写第一个程序、启动流程分析
这里我就来试验一下吧,我将会在一些特定的位置加上下面这行代码:

printf("%s %d \r\n",__FILE__,__LINE__);

通过__FILE__指明当前文件路径,一般情况下,和__LINE__结合一起使用,用于打印我们代码信息,方便快速定位代码位置。
以下是流程重点截图:
<br>

  1. app_main先会打印了SDK版本号,中间会进行一些初始化
    #夏日挑战赛#【FFH】OpenHarmony设备开发基础(四)启动流程 -鸿蒙开发者社区
    <br>
  2. app_main的最后一行会调用OHOS_Main函数
    #夏日挑战赛#【FFH】OpenHarmony设备开发基础(四)启动流程 -鸿蒙开发者社区
    <br>
  3. OHOS_Main函数原型如下,最后一行调用了OHOS_SystemInit()
    #夏日挑战赛#【FFH】OpenHarmony设备开发基础(四)启动流程 -鸿蒙开发者社区
    (注意是base\startup\bootstrap_lite\services\source\system_init.c路径下的OHOS_SystemInit())
    <br>
  4. OHOS_SystemInit()函数原型如下:
    #夏日挑战赛#【FFH】OpenHarmony设备开发基础(四)启动流程 -鸿蒙开发者社区
    进行了一系列模块的初始化——bsp(板级支持包)、device(设备)、core(代码)、service(服务)、feature(功能)
    当执行到MODULE_INIT(run)时就会去调用run段的代码,其实就是我们之前注册进宏设置SYS_RUN()的函数。
    此时我们可以再在原来的代码中加入一行提示语句进行验证:
    #夏日挑战赛#【FFH】OpenHarmony设备开发基础(四)启动流程 -鸿蒙开发者社区
    至于为什么执行到MODULE_INIT(run)时就会去调用注册函数呢?大家可以看看唐老师的这篇文章:
    SYS_RUN()和MODULE_INIT()之间的那些事
    <br>
    最后就到了检验的时候了:
    #夏日挑战赛#【FFH】OpenHarmony设备开发基础(四)启动流程 -鸿蒙开发者社区
    如果仔细比对我前面贴出来的截图的话就会发现我根本没有打印“这是提示语句:…”的操作,而希望打印的“49下面这行执行了OHOS_SystemInit()”却没被打印,这是因为没有执行OHOS_SystemInit()吗?显然不是,因为从后面几行的打印语句中知道OHOS_SystemInit()函数里面的语句被执行。经过仔细检查,发现正确的路径是我第一次进行注释时的文件,即“这是提示语句”后的路径————app/wifiiot_app/src/app_main.c,而下面右图的文件根本不是我们需要执行的文件中,所以不会被打印出来。这也阴差阳错地从另一个角度佐证了我们的证明。
    #夏日挑战赛#【FFH】OpenHarmony设备开发基础(四)启动流程 -鸿蒙开发者社区
    修正后:
    #夏日挑战赛#【FFH】OpenHarmony设备开发基础(四)启动流程 -鸿蒙开发者社区

我们发现,所有打印语句本身带有的行数与后面打印出来的一致,对照前面说明的启动流程顺序一致,该执行的都执行了。而且除了打印SDK后那一行的“上面这行…”被打印出来以外,其他的“上面这行…”都没被紧接着打印出来,说明其调用OHOS_Main()和OHOS_SystemInit()后进行了跳转,暂时不会执行其下面还剩的打印语句。
同时,由打印语句知:在执行MODUILE_INIT(run)后就打印了my_first_demo中注册的demo函数,在demo01.c文件中完成了目标操作。
最后,在跳转完成后,又回到原来的启动程序完成未打印的语句“上面这行…”。
<br>
至此,打印输出符合预期,证毕!

四、后记

参考文档:
OpenHarmony轻量系统开发【4】编写第一个程序、启动流程分析
SYS_RUN()和MODULE_INIT()之间的那些事
本篇文章主要参考上述文章对启动流程进行了分析验证
流程图如下:
#夏日挑战赛#【FFH】OpenHarmony设备开发基础(四)启动流程 -鸿蒙开发者社区
上面两篇文章强烈建议大家去读一读,然后自己动手实验一下加深理解~
如果发现本篇文章有不对的地方,欢迎交流探讨哦!

©著作权归作者所有,如需转载,请注明出处,否则将追究法律责任
已于2022-11-10 16:39:33修改
8
收藏 3
回复
举报
2条回复
按时间正序
/
按时间倒序
FFH杞人
FFH杞人

证明好详细啊,为楼主点赞

回复
2022-7-30 09:36:58
mb62e4e0f88122f
mb62e4e0f88122f

写的挺好,为楼主点赞

回复
2022-7-30 15:43:35
回复
    相关推荐