OpenHarmony南向设备应用程序启动流程分析 精华

吃吃的等
发布于 2022-8-27 17:35
浏览
2收藏

一、用户程序示例

以qihang开发板gpio_led程序为例,为何单板上电后LedTask()会自动运行,SYS_RUN宏在背后是如何起作用的?

static void LedTask(void)
{
    while (1)
    {
        IoTGpioSetOutputVal(LED_TASK_GPIO2,1);
        usleep(500*1000);
        IoTGpioSetOutputVal(LED_TASK_GPIO2,0);
        usleep(500*1000);
    }
}

static void LedExampleEntry(void)
{
    osThreadAttr_t attr;
    IoTGpioInit(LED_TASK_GPIO2);
    IoTGpioSetDir(LED_TASK_GPIO2,IOT_GPIO_DIR_OUT);
 
    attr.name = "LedTask";
    attr.attr_bits = 0U;
    attr.cb_mem = NULL;
    attr.cb_size = 0U;
    attr.stack_mem = NULL;
    attr.stack_size = LED_TASK_STACK_SIZE;
    attr.priority = LED_TASK_PRIO;
    if (osThreadNew((osThreadFunc_t)LedTask1, NULL, &attr) == NULL) {
        printf("Falied to create LedTask!\n");
    }
}

SYS_RUN(LedExampleEntry);

二、第二阶段(应用)启动流程

在上一篇文章中"OpenHarmony南向设备开发建构编译分析",有提到qihang板产品配置文件中相关子系统及组件如下;
vendor/isoftstone/qihang/config.json

......
    {
        "subsystem": "distributedschedule",  #分布式任务调度子系统
        "components": [
          { "component": "samgr_lite", "features":[] }
        ]
    },
    {
        "subsystem": "startup",
        "components": [
          { "component": "bootstrap_lite", "features":[] },  #bootstrap启动引导
          { "component": "syspara_lite", "features":  #提供系统属性读写接口
            [
              "enable_ohos_startup_syspara_lite_use_thirdparty_mbedtls = false"
            ]
          }
        ]
    },
......

可以看到,qihang板使用startup子系统中的bootstrap_lite组件和syspara_lite组件。重点看一下bootstrap_lite组件,位于SDK的base/startup/bootstrap_lite目录。引用该组件的readme文件说明如下:

bootstrap启动引导组件,提供了各服务和功能的启动入口标识。在SAMGR启动时,会调用boostrap标识的入口函数,并启动系统服务。

samgr_lite组件是针对Hi3861这类硬件资源有限的轻量化系统服务框架,代码位于foundation/distributedschedule/samgr_lite目录。该组件的功能引用如下:

系统服务框架基于面向服务的架构,提供了服务开发、服务的子功能开发、对外接口的开发、以及多服务共进程的开发框架。

device/soc/hisilicon/hi3861v100/sdk_liteos/app/wifiiot_app/src/app_main.c

hi_void app_main(hi_void)
{
    hi_flash_partition_table *ptable = HI_NULL;

    peripheral_init();
    peripheral_init_no_sleep();

    hi_u32 ret = hi_factory_nv_init(HI_FNV_DEFAULT_ADDR, HI_NV_DEFAULT_TOTAL_SIZE, HI_NV_DEFAULT_BLOCK_SIZE);

    hi_flash_partition_init();
    ptable = hi_get_partition_table();

    hi_nv_init(ptable->table[HI_FLASH_PARTITON_NORMAL_NV].addr, ptable->table[HI_FLASH_PARTITON_NORMAL_NV].size,
        HI_NV_DEFAULT_BLOCK_SIZE);

    hi_fs_init();

    (hi_void)hi_event_init(APP_INIT_EVENT_NUM, HI_NULL);
    hi_sal_init();

    hi_syserr_watchdog_debug(HI_FALSE);

    hi_syserr_record_crash_info(HI_TRUE);

    hi_lpc_init();
    hi_lpc_register_hw_handler(config_before_sleep, config_after_sleep);

    hi_at_init();

    tcpip_init(NULL, NULL);

    hi_wifi_init(APP_INIT_VAP_NUM, APP_INIT_USR_NUM);

    app_demo_task_release_mem(); /* 释放系统栈内存所使用任务 */

    hilink_main();

    OHOS_Main();
}

device/soc/hisilicon/hi3861v100/sdk_liteos/app/wifiiot_app/src/ohos_main.c

void OHOS_Main()
{
    OHOS_SystemInit();
}

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

void OHOS_SystemInit(void)
{
    MODULE_INIT(bsp);
    MODULE_INIT(device);
    MODULE_INIT(core);
    SYS_INIT(service);
    SYS_INIT(feature);
    MODULE_INIT(run);
    SAMGR_Bootstrap();
}

#define SYS_INIT(name)     \
    do {                   \
        SYS_CALL(name, 0); \
    } while (0)

#define SYS_CALL(name, step)                                      \
    do {                                                          \
        InitCall *initcall = (InitCall *)(SYS_BEGIN(name, step)); \
        InitCall *initend = (InitCall *)(SYS_END(name, step));    \
        for (; initcall < initend; initcall++) {                  \
            (*initcall)();                                        \
        }                                                         \
    } while (0)

#define SYS_BEGIN(name, step)                                 \
    ({        extern InitCall __zinitcall_sys_##name##_start;       \
        InitCall *initCall = &__zinitcall_sys_##name##_start; \
        (initCall);                                           \
    })

#define SYS_END(name, step)                                 \
    ({        extern InitCall __zinitcall_sys_##name##_end;       \
        InitCall *initCall = &__zinitcall_sys_##name##_end; \
        (initCall);                                         \
    })

#define MODULE_INIT(name)     \
    do {                      \
        MODULE_CALL(name, 0); \
    } while (0)

#define MODULE_CALL(name, step)                                      \
    do {                                                             \
        InitCall *initcall = (InitCall *)(MODULE_BEGIN(name, step)); \
        InitCall *initend = (InitCall *)(MODULE_END(name, step));    \
        for (; initcall < initend; initcall++) {                     \
            (*initcall)();                                           \
        }                                                            \
    } while (0)

#define MODULE_BEGIN(name, step)                          \
    ({        extern InitCall __zinitcall_##name##_start;       \
        InitCall *initCall = &__zinitcall_##name##_start; \
        (initCall);                                       \
    })
#define MODULE_END(name, step)                          \
    ({        extern InitCall __zinitcall_##name##_end;       \
        InitCall *initCall = &__zinitcall_##name##_end; \
        (initCall);                                     \
    })

foundation/distributedschedule/samgr_lite/samgr/source/samgr_lite.c

void SAMGR_Bootstrap(void)
{
    SamgrLiteImpl *samgr = GetImplement();

    WDT_Reset(WDG_SVC_BOOT_TIME);
    Vector initServices = VECTOR_Make(NULL, NULL);
    MUTEX_Lock(samgr->mutex);
    samgr->status = TO_NEXT_STATUS(samgr->status);
    int16 size = VECTOR_Size(&(samgr->services));
    int16 i;
    for (i = 0; i < size; ++i) {
        ServiceImpl *serviceImpl = (ServiceImpl *)VECTOR_At(&(samgr->services), i);
        VECTOR_Add(&initServices, serviceImpl);
    }
    MUTEX_Unlock(samgr->mutex);
    InitializeAllServices(&initServices);
    VECTOR_Clear(&initServices);
    InitCompleted();
}

在用户应用程序组件的代码中,会包含下述声明:

APP_FEATURE_INIT(MQTTDemo);

SYS_RUN(LedExampleEntry);

上述宏的说明引用如下:

/**

  • @brief Identifies the entry for initializing and starting an application-layer service by the
  • priority 2.
  • This macro is used to identify the entry called at the priority 2 of the application-layer
  • service phase of the startup process. \n
  • @param func Indicates the entry function for initializing and starting an application-layer
  • service. The type is void (*)(void).
    */

#define APP_SERVICE_INIT(func) LAYER_INITCALL_DEF(func, app_service, “app.service”)

/**

  • @brief Identifies the entry for initializing and starting a system running phase by the
  • priority 2.
  • This macro is used to identify the entry called at the priority 2 in the system startup
  • phase of the startup process. \n
  • @param func Indicates the entry function for initializing and starting a system running phase.
  • The type is void (*)(void).
  • #define SYS_RUN(func) LAYER_INITCALL_DEF(func, run, “run”)
    */

总结以上分析,程序第二阶段启动流程如下图图所示:
OpenHarmony南向设备应用程序启动流程分析-鸿蒙开发者社区

三、第一阶段(上电)启动流程

请参考本文最后延申阅读第二篇文章的具体介绍,程序加载由3个boot程序前后配合完成:

  • romboot:
    • 芯片内部自带的上电引导程序,引导loaderboot
  • loaderboot (device/soc/hisilicon/hi3861v100/sdk_liteos/boot/loaderboot)
    • 与HiBurn通讯,下载镜像到flash
    • 烧写EFUSE(芯片配置信息)
    • 校验并引导flashboot
  • flashboot (device/soc/hisilicon/hi3861v100/sdk_liteos/boot/flashboot)
    • 升级固件
    • 校验并引导固件(主程序)

loaderboot/common/cmd_loop.c 定义了从hiburn接收并处理的操作:

const loader_cmd g_loader_cmdtable[LOADER_CMD_MAX] = {
    { CMD_DL_IMAGE,         loader_download_image },
    { CMD_BURN_EFUSE,       loader_burn_efuse },
    { CMD_UL_DATA,          loader_upload_data },
    { CMD_READ_EFUSE,       loader_read_efuse },
    { CMD_FLASH_PROTECT,    loader_flash_protect },
    { CMD_RESET,            loader_reset },
    { CMD_FACTORY_IMAGE,    loader_download_image },
    { CMD_VERSION,          loader_burn_version},
};

其中:
loader_download_image就是接收hiburn传来的升级文件,并烧录到flash中。

flashboot/startup目录下有两个重要文件:

  • riscv_init_flshboot.S 汇编语言格式,RISC-V启动代码
  • main.c
#define KERNEL_START_ADDR   0x40D3C0
boot_kernel(KERNEL_START_ADDR);
global_reset();

hi_void boot_kernel(uintptr_t kaddr)
{
    __asm__ __volatile__("ecall");  /* switch U-MODE -> M-MODE */
    hi_void (*entry)(hi_void) = (hi_void*)(kaddr);
    entry();
}

在最后build应用生成的map文件,可看到内存布局如下:

Name             Origin             Length             Attributes
BIN              0x000000000040d3c0 0x0000000000200000 xr
ROM_TEXT         0x00000000003b8000 0x00000000000457e0 xr
ROM_DATA0        0x000000000011d7c0 0x0000000000000020 xrw
ROM_DATA1        0x000000000011d7e0 0x00000000000006e8 xrw
ROM_BSS          0x000000000011a9c0 0x0000000000002e00 xrw
STACK            0x00000000001185c0 0x0000000000002400 rw
CHECK_INFO       0x000000000011dfc0 0x0000000000000040 rw
FLASH            0x000000000040d3c0 0x00000000001f2c40 xrw
PATCH_BSS        0x00000000000d8000 0x0000000000000400 xrw
RAM              0x00000000000d8400 0x00000000000401c0 xrw
EXTERN_ROM_DATA1_BSS 0x000000000011dec8 0x00000000000000f8 xrw
*default*        0x0000000000000000 0xffffffffffffffff

 .entry.text    0x000000000040d3c0        0x4 build/libs/hi3861/release/no_mesh/liblitekernel_flash.a(los_startup.o)
                0x000000000040d3c0                _start
                0x000000000040d3e0                . = ALIGN (0x20)

对比可以看到,KERNEL_START_ADDR与应用程序的起始地址一致,基本可推断flashboot最后操作为调用应用程序。通过ecall指令,实现RISC-V处理器( Hi3861使用 )从User Mode( 禁止不可信代码执行特权指令 )切换为Machine Mode( 最高特权模式 )。

四、小结

本文采用倒序的方式,初步梳理了从Hi3861芯片上电到OpenHarmony应用程序启动运行的流程。还有很多内容都没有涉及,包括芯片安全启动,Flash的存储分布等,boot部分说明也比较粗浅,同时或也有理解错误,欢迎大家批评指正。


延申阅读:

  1. SYS_RUN()和MODULE_INIT()之间的那些事
    https://ost.51cto.com/posts/2017
  2. 鸿蒙芯片Hi3861启动流程介绍
    https://ost.51cto.com/posts/7825

3
收藏 2
回复
举报
1条回复
按时间正序
/
按时间倒序
红叶亦知秋
红叶亦知秋

楼主可以给自己写的文章标下原创,这样可能会吸引到更多小伙伴来学习

回复
2022-8-29 10:22:32
回复
    相关推荐