写点什么

OpenHarmony 移植案例与原理:如何适配服务启动引导部件 bootstrap_lite

  • 2022 年 2 月 23 日
  • 本文字数:10618 字

    阅读完需:约 35 分钟

本文分享自华为云社区《OpenHarmony移植案例与原理 - startup子系统之bootstrap_lite服务启动引导部件(1)》,作者:zhushy。


bootstrap_lite 服务启动引导组件提供了各服务和功能的启动入口标识。在 SAMGR(System ability manager,系统服务管理)启动时,会调用 bootstrap_lite 标识的入口函数,并启动系统服务。本文介绍下移植开发板时如何适配服务启动引导部件 bootstrap_lite,并介绍下相关的运行机制原理。bootstrap_lite 启动引导部件定义在 build\lite\components\startup.json。bootstrap_lite 启动引导部件源代码目录如下:

base/startup/bootstrap_lite/    # 启动引导组件└── services    └── source                  # 启动引导组件源文件目录
复制代码

1、bootstrap_lite 服务启动引导部件适配示例

1.1 产品解决方案配置启用部件

移植开发板适配 startup 子系统之 bootstrap_lite 服务启动引导部件时,需要在产品解决方案的 config.json 增加下述配置项,可以参考 vendor\bestechnic\display_demo\config.json 中的配置示例:


{  "subsystem": "startup",  "components": [    {      "component": "bootstrap_lite"    },    ......  ]},
复制代码


1.2 使用 bootstrap 服务启动部件提供的初始化宏函数

然后就可以使用 bootstrap 服务启动部件提供的初始化宏函数 SYS_SERVICE_INIT、APP_SERVICE_INIT 等来自动初始化服务,示例代码可以参考 device\board\fnlink\v200zr\liteos_m\at\at_wifi.c 中的用法,片段如下,可以看到调用了宏函数来初始化 RegisterCustomATCmd 函数实现的服务。device\board\bearpi\bearpi_hm_nano\app\目录下有更多的使用示例。下文分析实现机制原理。

static void RegisterCustomATCmd(){    cmd_tbl_t cmd_list[] = {        {"AT+IFCFG", 8, at_lwip_ifconfig, "AT+IFCFG - ifconfig\n"},        {"AT+STARTAP", 7, at_start_softap, "AT+STARTAP - start wifi softap\n"},        {"AT+STOPAP", 1, at_stop_softap, "AT+STOPAP - stop wifi softap\n"},        {"AT+STARTSTA", 1, at_start_wifista, "AT+STARTSTA - start wifi sta\n"},        {"AT+STOPSTA", 1, at_stop_wifista, "AT+STOPSTA - stop wifi sta\n"},        {"AT+DHCP", 3, at_setup_dhcp, "AT+DHCP - dhcp\n"},        {"AT+DHCPS", 3, at_setup_dhcps, "AT+DHCPS - dhcps\n"},    };    for (int i = 0; i < sizeof(cmd_list) / sizeof(cmd_tbl_t); i++) {        console_cmd_add(&cmd_list[i]);    }}SYS_SERVICE_INIT(RegisterCustomATCmd);
复制代码

1.3 链接脚本中增加 zInit 代码段

适配 bootstrap_lite 部件时,还需要在链接脚本文件中手动新增如下段,链接脚本示例可以参考//device/soc/bestechnic/bes2600/liteos_m/sdk/bsp/out/best2600w_liteos/_best2001.lds,还可以参考 device\soc\hisilicon\hi3861v100\sdk_liteos\build\link\link.ld.S。从链接脚本片段中可以看出,有.zinitcall.bsp、.zinitcall.device、.zinitcall.core、.zinitcall.sys.service、.zinitcall.sys.feature、.zinitcall.run、.zinitcall.app.service、.zinitcall.app.feature、.zinitcall.test 和.zinitcall.exit 等几种类型的段。

/* zInit code and data - will be freed after init */ .zInit (.) : {  __zinitcall_bsp_start = .;  KEEP (*(.zinitcall.bsp0.init))  KEEP (*(.zinitcall.bsp1.init))  KEEP (*(.zinitcall.bsp2.init))  KEEP (*(.zinitcall.bsp3.init))  KEEP (*(.zinitcall.bsp4.init))  __zinitcall_bsp_end = .;  . = ALIGN(4);  __zinitcall_device_start = .;  KEEP (*(.zinitcall.device0.init))  KEEP (*(.zinitcall.device1.init))  KEEP (*(.zinitcall.device2.init))  KEEP (*(.zinitcall.device3.init))  KEEP (*(.zinitcall.device4.init))  __zinitcall_device_end = .;  . = ALIGN(4);  __zinitcall_core_start = .;  KEEP (*(.zinitcall.core0.init))  KEEP (*(.zinitcall.core1.init))  KEEP (*(.zinitcall.core2.init))  KEEP (*(.zinitcall.core3.init))  KEEP (*(.zinitcall.core4.init))  __zinitcall_core_end = .;  . = ALIGN(4);  __zinitcall_sys_service_start = .;  KEEP (*(.zinitcall.sys.service0.init))  KEEP (*(.zinitcall.sys.service1.init))  KEEP (*(.zinitcall.sys.service2.init))  KEEP (*(.zinitcall.sys.service3.init))  KEEP (*(.zinitcall.sys.service4.init))  __zinitcall_sys_service_end = .;  . = ALIGN(4);  __zinitcall_sys_feature_start = .;  KEEP (*(.zinitcall.sys.feature0.init))  KEEP (*(.zinitcall.sys.feature1.init))  KEEP (*(.zinitcall.sys.feature2.init))  KEEP (*(.zinitcall.sys.feature3.init))  KEEP (*(.zinitcall.sys.feature4.init))  __zinitcall_sys_feature_end = .;  . = ALIGN(4);  __zinitcall_run_start = .;  KEEP (*(.zinitcall.run0.init))  KEEP (*(.zinitcall.run1.init))  KEEP (*(.zinitcall.run2.init))  KEEP (*(.zinitcall.run3.init))  KEEP (*(.zinitcall.run4.init))  __zinitcall_run_end = .;  . = ALIGN(4);  __zinitcall_app_service_start = .;  KEEP (*(.zinitcall.app.service0.init))  KEEP (*(.zinitcall.app.service1.init))  KEEP (*(.zinitcall.app.service2.init))  KEEP (*(.zinitcall.app.service3.init))  KEEP (*(.zinitcall.app.service4.init))  __zinitcall_app_service_end = .;  . = ALIGN(4);  __zinitcall_app_feature_start = .;  KEEP (*(.zinitcall.app.feature0.init))  KEEP (*(.zinitcall.app.feature1.init))  KEEP (*(.zinitcall.app.feature2.init))  KEEP (*(.zinitcall.app.feature3.init))         KEEP (*(.zinitcall.app.feature4.init))  __zinitcall_app_feature_end = .;  . = ALIGN(4);  __zinitcall_test_start = .;  KEEP (*(.zinitcall.test0.init))  KEEP (*(.zinitcall.test1.init))  KEEP (*(.zinitcall.test2.init))  KEEP (*(.zinitcall.test3.init))  KEEP (*(.zinitcall.test4.init))  __zinitcall_test_end = .;  . = ALIGN(4);  __zinitcall_exit_start = .;  KEEP (*(.zinitcall.exit0.init))  KEEP (*(.zinitcall.exit1.init))  KEEP (*(.zinitcall.exit2.init))  KEEP (*(.zinitcall.exit3.init))  KEEP (*(.zinitcall.exit4.init))  __zinitcall_exit_end = .;  . = ALIGN(4); } > FLASH
复制代码

1.4 配置编译时链接 bootstrap 库

另外,bootstrap_lite 部件会编译//base/startup/bootstrap_lite/services/source/bootstrap_service.c,该文件中,通过 SYS_SERVICE_INIT 将 Init 函数符号指定到__zinitcall_sys_service_start 和__zinitcall_sys_service_end 段中,由于没有显式调用 Init 函数,所以需要将它强制链接到最终的镜像。可以参考 device\board\goodix\gr5515_sk\liteos_m\config.gni 文件中的链接选项。恒玄的开发板适配时,是配置到 vendor\bestechnic\display_demo\config.json 文件中的自己定义的配置项 force_link_libs 里,该配置项在 device\soc\bestechnic\bes2600\BUILD.gn 中被解析、链接。

board_ld_flags = [....  "-lbootstrap",
复制代码

1.5 调用 OHOS_SystemInit 接口

函数 void OHOS_SystemInit(void)定义在文件 base\startup\bootstrap_lite\services\source\system_init.c 中,在移植适配时,需要调用该接口。可以参考文件 device\soc\bestechnic\bes2600\liteos_m\sdk\bsp\rtos\liteos\liteos_m\board.c 中的使用示例。

int main(void);extern void OHOS_SystemInit(void);    ......        OHOS_SystemInit();    ......    while (1) {        osDelay(1000);        TRACE(0, "main idle");    }}
复制代码

2、bootstrap_lite 服务启动引导部件实现原理之 system_init

bootstrap_lite 服务启动部件实现了服务的自动初始化,即服务的初始化函数无需显式调用,它是使用宏定义的方式申明,在系统启动时自动被执行。实现原理是将服务启动的函数通过宏申明之后,放在预定义好的 zInit 代码段中,系统启动的时候调用 OHOS_SystemInit 接口,遍历该代码段并调用其中的函数。因此在适配移植时,需要在 device/soc/下面具体的芯片的链接脚本中添加 zInit 段,并且在 main 函数里调用 OHOS_SystemInit 接口。

2.1 bootstrap_lite 服务启动引导部件的初始化宏

bootstrap_lite 服务启动引导部件的初始化宏定义在文件 utils\native\lite\include\ohos_init.h,片段如下。初始化函数宏 SYS_SERVICE_INIT(func)用于标识核心系统服务的初始化入口,该宏识别的函数在启动过程中核心系统服务优先级 2 阶段被调用;初始化宏 SYS_SERVICE_INIT_PRI(func, priority)可以指定优先级数值,优先级的取值范围为[0,5),调用顺序为 0, 1, 2, 3, 4。

/**  * @brief Identifies the entry for initializing and starting a core system service by the  * priority 2.  *  * This macro is used to identify the entry called at the priority 2 in the core system  * service phase of the startup process. \n  *  * @param func Indicates the entry function for initializing and starting a core system service.  * The type is void (*)(void).  */  #define SYS_SERVICE_INIT(func) LAYER_INITCALL_DEF(func, sys_service, "sys.service")  /**  * @brief Identifies the entry for initializing and starting a core system service by the  * specified priority.  *  * This macro is used to identify the entry called at the specified priority in the core system  * service phase of the startup process. \n  *  * @param func Indicates the entry function for initializing and starting a core system service.  * The type is void (*)(void).  * @param priority Indicates the calling priority when starting the core system service in the  * startup phase. The value range is [0,5), and the calling sequence is 0, 1, 2, 3, and 4.  */  #define SYS_SERVICE_INIT_PRI(func, priority) LAYER_INITCALL(func, sys_service, "sys.service", priority)
复制代码

更多的初始化宏见下文的列表,处理这些初始化宏,还有可以指定优先级的版本 XXX_PRI。

2.2 LAYER_INITCALL 宏定义

从上文已知,bootstrap_lite 服务启动引导部件的初始化宏会调用 LAYER_INITCALL_DEF 和 LAYER_INITCALL 宏。这些宏的定义在文件 utils\native\lite\include\ohos_init.h,代码片段如下。⑴处声明函数类型,无参无返回值。⑵处处理定义分层初始化共享库宏 LAYER_INIT_SHARED_LIB 的情况,如果没有定义该宏,则执行⑹。在文件 foundation/distributedschedule/samgr_lite/samgr/BUILD.gn 中定义了该宏,在移植适配芯片开发板时,没有定义这个宏。⑶处定义 5 个分层初始化级别,⑷处定义 7 个构建值(constructor value,简称 CTOR Value)。⑸处是宏 LAYER_INITCALL 的定义,该宏需要 4 个参数,分别是初始化服务或功能函数 func;layer 是分层名称,支持的取值为 device、core、sys_service、sys_feature、app_service、app_feature 和 run,拼装为 CTOR_VALUE_XXX;clayer 参数在定义宏 LAYER_INIT_SHARED_LIB 时未使用;priority 是优先级参数。attribute((constructor))表示这段代码将在 main 函数前调用。当传入参数为(myFunc, sys_feature, “sys.feature”, 2)时,函数宏替换为:static __attribute__((constructor(130 + 2))) void BOOT_sys_featurer2myFunc {myFunc();}。等于定义个一个新的启动引导函数 BOOT_sys_featurer2myFunc()。


当没有定义 LAYER_INIT_SHARED_LIB 宏时,执行⑹,当传入参数为(myFunc, sys_feature, “sys.feature”, 2)时,函数宏替换为:static const InitCall __attribute__((used)) __zinitcall_sys_feature_myFunc __attribute__((section(".zinitcall.sys.feature2.init"))) = myFunc,除了__attribute__部分,等于声明一个函数类型 InitCall 的变量__zinitcall_sys_feature_myFunc。该函数变量放入 section 段".zinitcall.sys.feature2.init"内,所以移植适配时,需要在芯片开发板的链接脚本里添加 zInit 代码段。

⑴  typedef void (*InitCall)(void);
#define USED_ATTR __attribute__((used))
⑵ #ifdef LAYER_INIT_SHARED_LIB⑶ #define LAYER_INIT_LEVEL_0 0 #define LAYER_INIT_LEVEL_1 1 #define LAYER_INIT_LEVEL_2 2 #define LAYER_INIT_LEVEL_3 3 #define LAYER_INIT_LEVEL_4 4⑷ #define CTOR_VALUE_device 100 #define CTOR_VALUE_core 110 #define CTOR_VALUE_sys_service 120 #define CTOR_VALUE_sys_feature 130 #define CTOR_VALUE_app_service 140 #define CTOR_VALUE_app_feature 150 #define CTOR_VALUE_run 700⑸ #define LAYER_INITCALL(func, layer, clayer, priority) \ static __attribute__((constructor(CTOR_VALUE_##layer + LAYER_INIT_LEVEL_##priority))) \ void BOOT_##layer##priority##func() {func();} #else⑹ #define LAYER_INITCALL(func, layer, clayer, priority) \ static const InitCall USED_ATTR __zinitcall_##layer##_##func \ __attribute__((section(".zinitcall." clayer #priority ".init"))) = func #endif // Default priority is 2, priority range is [0, 4] #define LAYER_INITCALL_DEF(func, layer, clayer) \ LAYER_INITCALL(func, layer, clayer, 2)
复制代码

2.3 OHOS_SystemInit 函数

函数 OHOS_SystemInit()定义在文件 base\startup\bootstrap_lite\services\source\system_init.c,代码如下。调用宏函数 MODULE_INIT、SYS_INIT 和函数 SAMGR_Bootstrap()进行初始化启动。

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

我们详细分析下宏函数 MODULE_INIT 和 SYS_INIT 的源代码,这 2 个宏函数在文件 base\startup\bootstrap_lite\services\source\core_main.h 中定义,代码如下。这些宏函数的参数大都为 name 和 step,name 取值为 bsp、device、core、service、feature、run,step 取值为 0。从⑺和⑻处可以看出,分别调用 SYS_CALL、MODULE_CALL 两个宏,第二个参数设置为 0。⑸处定义的 MODULE_BEGIN 函数,用于返回链接脚本中定义的代码段的开始地址,当传入参数为 bsp 时,返回 &__zinitcall_bsp_start,MODULE_BEGIN 函数被展开为如下片段:

{        extern InitCall __zinitcall_sys_start;      \        InitCall *initCall = &__zinitcall_bsp_start; \        (initCall);                                      \}
复制代码

⑹处定义的 MODULE_END 函数,用于返回链接脚本中定义的代码段的结束地址,当传入参数为 bsp 时,返回 &__zinitcall_bsp_end,MODULE_END 函数被展开为如下片段:

{        extern InitCall __zinitcall_bsp_end;      \        InitCall *initCall = &__zinitcall_bsp_end; \        (initCall);                                    \}    
复制代码

⑶和⑷处定义的 SYS_BEGIN、SYS_END 代码类似,分于返回链接脚本中定义的标记系统服务或特性的代码段的开始、结束地址,即 &__zinitcall_sys_service_start、&__zinitcall_sys_service_end、&__zinitcall_sys_feature_start、&__zinitcall_sys_feature_end。⑴处定义的 SYS_CALL 函数,分别获取链接脚本中 zInit 代码段的开始 initcall 和结束地址 initend,这些地址存放的是初始化函数的地址,语句 (*initcall)()会循环调用执行这些初始化函数。⑵处 MODULE_CALL 宏类似。

⑴  #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 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 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_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); \ })
⑺ #define SYS_INIT(name) \ do { \ SYS_CALL(name, 0); \ } while (0)
⑻ #define MODULE_INIT(name) \ do { \ MODULE_CALL(name, 0); \ } while (0)
复制代码

3、 bootstrap_lite 服务启动引导部件实现原理之 bootstrap_service

在文件 base\startup\bootstrap_lite\services\source\bootstrap_service.h 中定义了 2 个宏函数 INIT_APP_CALL 和 INIT_TEST_CALL,分别用来调用代码段 &__zinitcall_app_XXX_start、&__zinitcall_app_XXX_end 和 &__zinitcall_test_start、&__zinitcall_test_end 之间的初始化启动函数。bootstrap_service.h 文件中的宏和 base\startup\bootstrap_lite\services\source\core_main.h 文件中的宏类似,不再一一分析。

3.1 结构体 struct Bootstrap

在文件 base\startup\bootstrap_lite\services\source\bootstrap_service.c 中定义了结构体 struct Bootstrap,如下代码⑵处。其中结构体中的 INHERIT_SERVICE 定义在文件 foundation/distributedschedule/samgr_lite/interfaces/kits/samgr/service.h,见代码片段⑴处。

⑴   #define INHERIT_SERVICE                                          \        const char *(*GetName)(Service * service);                   \        BOOL (*Initialize)(Service * service, Identity identity);    \        BOOL (*MessageHandle)(Service * service, Request * request); \        TaskConfig (*GetTaskConfig)(Service * service)
......⑵ typedef struct Bootstrap { INHERIT_SERVICE; Identity identity; uint8 flag; } Bootstrap;
复制代码

结构体 Identity 定义在文件 foundation\distributedschedule\samgr_lite\interfaces\kits\samgr\message.h 中,用于标识一个服务或特性。

/** * @brief Identifies a service and feature. * * You can use this structure to identity a {@link IUnknown} feature to which messages will be * sent through the asynchronous function of {@link IUnknown}. \n * */struct Identity {    /** Service ID */    int16 serviceId;    /** Feature ID */    int16 featureId;    /** Message queue ID */    MQueueId queueId;};
复制代码

3.2 Init(void)函数

讲解移植适配示例时,我们已经知道,bootstrap_lite 部件会编译//base/startup/bootstrap_lite/services/source/bootstrap_service.c,该文件通过函数宏 SYS_SERVICE_INIT 将 Init()函数符号灌段到__zinitcall_sys_service_start 和__zinitcall_sys_service_end 代码段中,代码片段如下。下文再详细分析 GetName、Initialize、MessageHandle 和 GetTaskConfig 函数。

 static const char *GetName(Service *service);  static BOOL Initialize(Service *service, Identity identity);  static TaskConfig GetTaskConfig(Service *service);  static BOOL MessageHandle(Service *service, Request *request);
static void Init(void) { static Bootstrap bootstrap; bootstrap.GetName = GetName; bootstrap.Initialize = Initialize; bootstrap.MessageHandle = MessageHandle; bootstrap.GetTaskConfig = GetTaskConfig; bootstrap.flag = FALSE; SAMGR_GetInstance()->RegisterService((Service *)&bootstrap); } SYS_SERVICE_INIT(Init);
复制代码

3.3 GetName 和 Initialize 函数

GetName 函数代码如下,其中 BOOTSTRAP_SERVICE 定义在文件 foundation\distributedschedule\samgr_lite\interfaces\kits\samgr\samgr_lite.h 中,取值为"Bootstrap",表示启动引导服务。

static const char *GetName(Service *service){    (void)service;    return BOOTSTRAP_SERVICE;}
复制代码

Initialize 函数定义如下,用于设置启动引导服务的标识信息。

static BOOL Initialize(Service *service, Identity identity){    Bootstrap *bootstrap = (Bootstrap *)service;    bootstrap->identity = identity;    return TRUE;}
复制代码

3.4 MessageHandle 函数和 GetTaskConfig 函数

MessageHandle 函数和 GetTaskConfig 函数代码如下,MessageHandle 函数用于处理各种请求,请求消息编号定义 request->msgId 定义在文件 foundation\distributedschedule\samgr_lite\interfaces\kits\samgr\samgr_lite.h 中的枚举 enum BootMessage,如下⑴处所示。在 MessageHandle 函数中,当请求的消息编号为 BOOT_SYS_COMPLETED 系统服务初始化完成时,检查应用服务是否加载。当应用没有加载时,执行 INIT_APP_CALL 宏函数调用 zInit 代码段上的应用服务和应用特性初始化函数。然后执行⑶,调用函数 SAMGR_SendResponseByIdentity 进行响应。GetTaskConfig 函数用于获取任务配置。

⑴  typedef enum BootMessage {        /** Message indicating that the core system service is initialized */        /** 标识核心系统服务初始化完成 */        BOOT_SYS_COMPLETED,        /** Message indicating that the system and application-layer services are initialized */        /** 标识应用层服务初始化完成 */        BOOT_APP_COMPLETED,        /** Message indicating service registration during running */        /** 标识运行时的服务注册 */        BOOT_REG_SERVICE,        /** Maximum number of message IDs */        /** 标识消息最大值,butt是烟蒂;屁股;枪托的意思,表示尾部吧 */        BOOTSTRAP_BUTT    } BootMessage;    ......  static BOOL MessageHandle(Service *service, Request *request)  {      Bootstrap *bootstrap = (Bootstrap *)service;      switch (request->msgId) {          case BOOT_SYS_COMPLETED:⑵            if ((bootstrap->flag & LOAD_FLAG) != LOAD_FLAG) {                  INIT_APP_CALL(service);                  INIT_APP_CALL(feature);                  bootstrap->flag |= LOAD_FLAG;              }⑶            (void)SAMGR_SendResponseByIdentity(&bootstrap->identity, request, NULL);              break;
case BOOT_APP_COMPLETED: (void)SAMGR_SendResponseByIdentity(&bootstrap->identity, request, NULL); break;
case BOOT_REG_SERVICE: (void)SAMGR_SendResponseByIdentity(&bootstrap->identity, request, NULL); break;
default: break; } return TRUE; }
static TaskConfig GetTaskConfig(Service *service) { (void)service; // The bootstrap service uses a stack of 2 KB (0x800) in size and a queue of 20 elements. // You can adjust it according to the actual situation. TaskConfig config = {LEVEL_HIGH, PRI_NORMAL, 0x800, 20, SHARED_TASK}; return config; }
复制代码

参考站点

小结

本文介绍了 startup 子系统之 bootstrap_lite 服务启动引导部件的移植适配案例及原理。因为时间关系,仓促写作,或能力限制,若有失误之处,请各位读者多多指正。感谢阅读,有什么问题,请留言。


点击关注,第一时间了解华为云新鲜技术~


发布于: 刚刚阅读数: 2
用户头像

提供全面深入的云计算技术干货 2020.07.14 加入

华为云开发者社区,提供全面深入的云计算前景分析、丰富的技术干货、程序样例,分享华为云前沿资讯动态,方便开发者快速成长与发展,欢迎提问、互动,多方位了解云计算! 传送门:https://bbs.huaweicloud.com/

评论

发布
暂无评论
OpenHarmony移植案例与原理:如何适配服务启动引导部件bootstrap_lite