openharmony:bootstrap服務(wù)啟動引導(dǎo)組件

kernel:litoes_m
MCU:stm32f407zgt6

本文只講解bootstrap組件的移植。bootstrap組件的原理介紹請移步官網(wǎng)文檔.

簡介

bootstrap服務(wù)啟動組件實現(xiàn)了服務(wù)的自動初始化,即服務(wù)的初始化函數(shù)無需顯式調(diào)用,而是將其使用宏定義的方式申明,就會在系統(tǒng)啟動時自動被執(zhí)行。實現(xiàn)原理是將服務(wù)啟動的函數(shù)通過宏定義的方式申明之后,放在預(yù)定義好的zInit代碼段中,系統(tǒng)啟動的時候調(diào)用OHOS_SystemInit接口遍歷該代碼段并調(diào)用其中的函數(shù)。因此,需要在鏈接腳本中添加zInit段,并且在main函數(shù)里調(diào)用OHOS_SystemInit接口。

目錄

base/startup/bootstrap_lite/    # 啟動引導(dǎo)組件
└── services
    └── source               # 啟動引導(dǎo)組件源文件目錄

約束

目前支持輕量系統(tǒng)設(shè)備(參考內(nèi)存≥128KB),如Hi3861V100。

使用說明

bootstrap組件無需單獨配置,在SAMGR啟動時會自動調(diào)用,用于啟動系統(tǒng)服務(wù)。

移植過程

1. STM32F407ZGTx_FLASH.ld

在連接腳本中增加zInit代碼段

  .text :
  {
    . = ALIGN(4);
    *(.text)           /* .text sections (code) */
    *(.text*)          /* .text* sections (code) */
    *(.glue_7)         /* glue arm to thumb code */
    *(.glue_7t)        /* glue thumb to arm code */
    *(.eh_frame)

    /* bootstrap中增加的內(nèi)容 - 開始 */
    __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 = .;
    __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 = .;
    __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 = .;
    __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 = .;
    __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 = .;
    __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 = .;
    __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 = .;
    __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 = .;
    __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 = .;
    __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 = .;
    /* bootstrap中增加的內(nèi)容 - 結(jié)束 */

    KEEP (*(.init))
    KEEP (*(.fini))

    . = ALIGN(4);
    _etext = .;        /* define a global symbols at end of code */
  } >FLASH

2. vendor/fx/TunnelControl/config.json下增加組件

//vendor/fx/TunnelControl/config.json
        {
            "subsystem": "startup",
            "components": [
                {
                    "component": "bootstrap_lite"
                }
            ]
        },

組件bootstrap的初始化接口函數(shù)base/startup/bootstrap_lite/services/source/system_init.c中的OHOS_SystemInit()中調(diào)用函數(shù)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();
}

hb build之后會繼續(xù),會報如下錯誤:

[OHOS ERROR] system_init.c:(.text.OHOS_SystemInit+0x6e): undefined reference to `SAMGR_Bootstrap'
[OHOS ERROR] collect2: error: ld returned 1 exit status

vendor/fx/tunnel/config.json下需要加入組件samgr_lite。

        {
            "subsystem": "distributedschedule",
            "components": [
              {
                "component": "samgr_lite",
                "features": []
              }
            ]
        },

hb build之后會繼續(xù),會報如下錯誤:

[OHOS ERROR] samgr_lite.c:(.text.AddTaskPool+0x62): undefined reference to `HiLogPrintf'

vendor/fx/TunnelControl/config.json下需要加入組件hilog_lite。

        {
            "subsystem": "hiviewdfx",
            "components": [
              {
                "component": "hilog_lite",
                "features": []
              }
            ]
        },

hb build之后會繼續(xù),會報如下錯誤:

[OHOS ERROR] In file included from ../../../base/hiviewdfx/hiview_lite/hiview_file.c:16:
[OHOS ERROR] ../../../base/hiviewdfx/hiview_lite/hiview_config.h:21:10: fatal error: ohos_types.h: No such file or directory
[OHOS ERROR]    21 | #include "ohos_types.h"

找到ohos_types.h路徑,將ohos_types.h的路徑加入device/board/fx/bajie/liteos_m/config.gni中。如下所示:

//device/board/fx/bajie/liteos_m/config.gni
board_include_dirs = [
    "http://utils/native/lite/include"
]

hb build之后會繼續(xù),會報如下錯誤:

[OHOS ERROR] hiview_util.c:(.text.HIVIEW_GetCurrentTime+0x1c): undefined reference to `clock_gettime'
[OHOS ERROR] collect2: error: ld returned 1 exit status

函數(shù)clock_gettimekernel/liteos_m/kal/posix/src/time.c中。從posix可以看出,需要使能posix接口。make menuconfig選擇如下:

[*] Enable KAL CMSIS (NEW)
    Choose libc implementation (newlibc)  --->
[*] Enable POSIX API
[*]     Enable POSIX Thread API (NEW)
[*]     Enable POSIX Semaphore API (NEW)
[*]     Enable POSIX Clock API (NEW)
[*]     Enable POSIX Mqueue API (NEW)
[*]     Enable POSIX Pipe API (NEW)
[*]     Enable POSIX Signal API (NEW)

此時hb build不報錯。

[OHOS INFO] ---------------------------------------------
[OHOS INFO] c targets overlap rate statistics
[OHOS INFO] subsystem           files NO.       percentage      builds NO.      percentage      overlap rate
[OHOS INFO] distributedschedule       15        6.1%          15        6.1%    1.00
[OHOS INFO] hiviewdfx                 10        4.0%          10        4.0%    1.00
[OHOS INFO] kernel                    51        20.6%         51        20.6%   1.00
[OHOS INFO] securec                   39        15.8%         39        15.8%   1.00
[OHOS INFO] startup                    2        0.8%           2        0.8%    1.00
[OHOS INFO] test                       2        0.8%           2        0.8%    1.00
[OHOS INFO] third_party              127        51.4%        127        51.4%   1.00
[OHOS INFO] xts                        2        0.8%           2        0.8%    1.00
[OHOS INFO] 
[OHOS INFO] c overall build overlap rate: 1.00
[OHOS INFO] 
[OHOS INFO] 
[OHOS INFO] tunnel build success
[OHOS INFO] cost time: 0:00:08

可以看出,子系統(tǒng)startup、hiviewdfxdistributedschedule都被編譯成功。

3.編寫測試代碼

在main.c中編程測試代碼,測試startup功能。

//增加一下兩個頭文件
#include "ohos_init.h"
#include "ohos_types.h"
……
int main(void)
{
    HAL_Init();
    SystemClock_Config();
    MX_GPIO_Init();
    HAL_Delay(3000);
    usart_printf_init();
    PRINT_INFO("SCB->VTOR = 0x%08lX\r\n", SCB->VTOR);
    UINT32 ret;
    ret = LOS_KernelInit();
    OHOS_SystemInit();    // 這句式必須增加的,該接口遍歷zInit代碼段并調(diào)用其中的函數(shù)。
    if (ret == LOS_OK)
    {
        LOS_Start();
    }

    while (1)
    {
        __asm volatile("wfi");
    }
}

運行時報如下錯誤:

*************Exception Information**************
Type      = 11
ThrdPid   = 25
Phase     = exc in task
FaultAddr = 0xabababab
Current task info:
Task name = 
Task ID   = 25
Task SP   = 00000000
Task ST   = 0x0
Task SS   = 0x0
Exception reg dump:
PC        = 0x80155e2
LR        = 0x8015823
SP        = 0x2001ffb8
R0        = 0x0
R1        = 0x8023a38
R2        = 0x0
R3        = 0x20011de0
R4        = 0x0
R5        = 0x8023a38
R6        = 0x0
R7        = 0x2001fff0
R8        = 0x0
R9        = 0x0
R10       = 0x0
R11       = 0x0
R12       = 0x0
PriMask   = 0x0
xPSR      = 0x41000000
----- backtrace start -----
backtrace 0 -- lr = 0x8015822
backtrace 1 -- lr = 0x8013314
backtrace 2 -- lr = 0x8011998
backtrace 3 -- lr = 0x80123b6
----- backtrace end -----

 TID  Priority   Status StackSize WaterLine StackPoint TopOfStack EventMask  SemID  TaskEntry name
 ---  -------- -------- --------- --------- ---------- ---------- --------- ------ ---------- ----
   0        0     Ready     0x2d0      0xcc 0x20004614 0x20004410         0 0xffff 0x800ea41 Swt_Task                        
   1       31     Ready     0x500      0xcc 0x20004b24 0x200046f0         0 0xffff 0x800ef15 IdleCore000  

從圖中可以看出PC= 0x80155e2。在.asm文件中查到搜索80155e2,定位到出現(xiàn)問題的代碼位置??紤]到cortex-m采用的是三級流水線技術(shù)。實際當(dāng)前運行位置應(yīng)該是80155e8。

080155c8 <HiLogRegisterModule>:
 80155c8:   b570        push    {r4, r5, r6, lr}
 80155ca:   b082        sub sp, #8
 80155cc:   4604        mov r4, r0
 80155ce:   460d        mov r5, r1
 80155d0:   4b2b        ldr r3, [pc, #172]  ; (8015680 <HiLogRegisterModule+0xb8>)
 80155d2:   681b        ldr r3, [r3, #0]
 80155d4:   9301        str r3, [sp, #4]
 80155d6:   f04f 0300   mov.w   r3, #0
 80155da:   eb00 0280   add.w   r2, r0, r0, lsl #2
 80155de:   4b29        ldr r3, [pc, #164]  ; (8015684 <HiLogRegisterModule+0xbc>)
 80155e0:   4413        add r3, r2
 80155e2:   f8d3 2001   ldr.w   r2, [r3, #1]
 80155e6:   4601        mov r1, r0
 80155e8:   4827        ldr r0, [pc, #156]  ; (8015688 <HiLogRegisterModule+0xc0>)
 80155ea:   f7fc f8d7   bl  801179c <__wrap_printf>
 80155ee:   2c3f        cmp r4, #63 ; 0x3f
 80155f0:   d840        bhi.n   8015674 <HiLogRegisterModule+0xac>
 80155f2:   2d00        cmp r5, #0
 80155f4:   d040        beq.n   8015678 <HiLogRegisterModule+0xb0>
 80155f6:   eb04 0284   add.w   r2, r4, r4, lsl #2
 80155fa:   4b22        ldr r3, [pc, #136]  ; (8015684 <HiLogRegisterModule+0xbc>)
 80155fc:   4413        add r3, r2
 80155fe:   f8d3 3001   ldr.w   r3, [r3, #1]
 8015602:   b14b        cbz r3, 8015618 <HiLogRegisterModule+0x50>
 8015604:   2000        movs    r0, #0
 8015606:   4b1e        ldr r3, [pc, #120]  ; (8015680 <HiLogRegisterModule+0xb8>)
 8015608:   681a        ldr r2, [r3, #0]
 801560a:   9b01        ldr r3, [sp, #4]

異常發(fā)生在一個名為base/hiviewdfx/hilog_lite/frameworks/mini/hiview_log.c中的函數(shù)HiLogRegisterModule。分析問題的過程就不說了。問題出現(xiàn)在結(jié)構(gòu)體HiLogModuleInfo上。代碼中強制1字節(jié)對齊。cortex-m的地址是4字節(jié)的,必須4字節(jié)對齊。

base/hiviewdfx/hiview_lite/hiview_def.h

#pragma pack(1)  此處的pack(1)->pack(4)
typedef struct {
    uint8 id;
    const char *name; /* LOG_MODULE_NAME_LEN */
} HiLogModuleInfo;
#pragma pack()

hb build之后,下載運行后,報如下錯誤:

*************Exception Information**************
Type      = 11
ThrdPid   = 25
Phase     = exc in task
FaultAddr = 0xabababab
Current task info:
Task name = 
Task ID   = 25
Task SP   = 00000000
Task ST   = 0x0
Task SS   = 0x0
Exception reg dump:
PC        = 0x80156e0
LR        = 0x80156dd
SP        = 0x2001ff88
R0        = 0x1
R1        = 0x1
R2        = 0x20000534
R3        = 0x0
R4        = 0x1
R5        = 0x0
R6        = 0x1
R7        = 0x80239b8
R8        = 0x0
R9        = 0x0
R10       = 0x0
R11       = 0x0
R12       = 0x6
PriMask   = 0x0
xPSR      = 0x41000000
----- backtrace start -----
backtrace 0 -- lr = 0x80158a2
backtrace 1 -- lr = 0x8013314
backtrace 2 -- lr = 0x8011998
backtrace 3 -- lr = 0x80123b6
----- backtrace end -----

 TID  Priority   Status StackSize WaterLine StackPoint TopOfStack EventMask  SemID  TaskEntry name
 ---  -------- -------- --------- --------- ---------- ---------- --------- ------ ---------- ----
   0        0     Ready     0x2d0      0xcc 0x20004614 0x20004410         0 0xffff 0x800ea41 Swt_Task                        
   1       31     Ready     0x500      0xcc 0x20004b24 0x200046f0         0 0xffff 0x800ef15 IdleCore000       

根據(jù)PC=0x80156e0,在.asm文件中找到出問題的函數(shù)HiLogPrintf()。找到其中的結(jié)構(gòu)體,查看一下是否又pack(1)了。

base/hiviewdfx/hilog_lite/interfaces/native/kits/hilog_lite/hiview_log.h

#pragma pack(1) 改成 pack(4)
typedef struct {
    uint8 head;
    uint8 module;
    uint8 level : 4;
    uint8 valueNumber : 4;
    uint8 task;
    uint32 time; /* seconds */
    uint16 milli; /* millisecond, 0-999 */
    const char *fmt;
} HiLogCommon;

typedef struct {
    HiLogCommon commonContent;
    uint32 values[LOG_MULTI_PARA_MAX];
} HiLogContent;
#pragma pack()

之后遇到的幾個問題都是用于pack(1)導(dǎo)致的。都改完之后,系統(tǒng)可以運行就起作用了。

然后可以調(diào)用utils/native/lite/include/ohos_init.h提供的api了。

#define CORE_INIT(func) LAYER_INITCALL_DEF(func, core, "core")
#define CORE_INIT_PRI(func, priority) LAYER_INITCALL(func, core, "core", priority)
#define SYS_SERVICE_INIT(func) LAYER_INITCALL_DEF(func, sys_service, "sys.service")
#define SYS_SERVICE_INIT_PRI(func, priority) LAYER_INITCALL(func, sys_service, "sys.service", priority)
#define SYS_FEATURE_INIT(func) LAYER_INITCALL_DEF(func, sys_feature, "sys.feature")
#define SYS_FEATURE_INIT_PRI(func, priority) LAYER_INITCALL(func, sys_feature, "sys.feature", priority)
#define SYS_RUN(func) LAYER_INITCALL_DEF(func, run, "run")
#define SYS_RUN_PRI(func, priority) LAYER_INITCALL(func, run, "run", priority)
#define SYSEX_SERVICE_INIT(func) LAYER_INITCALL_DEF(func, app_service, "app.service")
#define SYSEX_SERVICE_INIT_PRI(func, priority) LAYER_INITCALL(func, app_service, "app.service", priority)
#define SYSEX_FEATURE_INIT(func) LAYER_INITCALL_DEF(func, app_feature, "app.feature")
#define SYSEX_FEATURE_INIT_PRI(func, priority) LAYER_INITCALL(func, app_feature, "app.feature", priority)
#define APP_SERVICE_INIT(func) LAYER_INITCALL_DEF(func, app_service, "app.service")
#define APP_SERVICE_INIT_PRI(func, priority) LAYER_INITCALL(func, app_service, "app.service", priority)
#define APP_FEATURE_INIT(func) LAYER_INITCALL_DEF(func, app_feature, "app.feature")
#define APP_FEATURE_INIT_PRI(func, priority) LAYER_INITCALL(func, app_feature, "app.feature", priority)

編寫測試demo。

VOID TaskSampleEntry1(VOID) // 任務(wù)1的入口函數(shù)
{
    while(1) {
      LOS_TaskDelay(LOS_MS2Tick(2000));
      HAL_GPIO_TogglePin(GPIOE, GPIO_PIN_2);
      HILOG_INFO(HILOG_MODULE_MAIN, "This is an electric light program.");
    //   PRINTK("This is an electric light program.\r\n");
    }
}

UINT32 TaskSample(VOID)
{
    UINT32 uwRet;
    UINT32 taskID1,taskID2;
    TSK_INIT_PARAM_S stTask1={0};
    stTask1.pfnTaskEntry = (TSK_ENTRY_FUNC)TaskSampleEntry1;
    stTask1.uwStackSize  = 0X1000;
    stTask1.pcName       = "taskSampleEntry1";
    stTask1.usTaskPrio   = 6; //stTask1的任務(wù)優(yōu)先級設(shè)定,不同于stTask2
    uwRet = LOS_TaskCreate(&taskID1, &stTask1);
    if(uwRet != LOS_OK)
    {
        PRINT_ERR("start_task init error.\r\n");
    }
    return LOS_OK;
}

SYS_RUN(TaskSample);

hb build之后,重新下載運行。電燈成功。說明SYS_RUN()運行有效。

bootstrap加入acts兼容性測試

device/board/fx/tunnel_box/liteos_m/boot/BUILD.gn

config("public") {
  include_dirs = [
    "include",
  ]

  ldflags = [
    "-Wl,-T" + rebase_path("STM32F407ZGTx_FLASH.ld"),
    "-Wl,-u_printf_float",
  ]

  if (build_xts) {
    lib_dirs = [ "$root_out_dir/libs" ]

    ldflags += [
      "-Wl,--whole-archive",
      "-lbootstrap",
      "-lbroadcast",
      "-lhctest",

      #公共基礎(chǔ)庫
    #   "-lmodule_ActsUtilsFileTest",
    #   "-lmodule_ActsKvStoreTest",

      #DFX
    #   "-lmodule_ActsDfxFuncTest",
    #   "-lmodule_ActsHieventLiteTest",

      #啟動恢復(fù)
      "-lmodule_ActsBootstrapTest",  
      # "-lmodule_ActsParameterTest",

      #分布式任務(wù)調(diào)度
    #   "-lmodule_ActsSamgrTest",

      "-Wl,--no-whole-archive",
    ]
  }

  libs = [
    "c",
    "m",
    "nosys",
  ]
}

```hb build``之后,下載到板子中運行現(xiàn)實如下信息:

Run test suite 1 times
[TestStartBootstrapSamgr001:0]../../../test/xts/acts/startup_lite/bootstrap_hal/src/samgr_api_test.c:342:TestStartBootstrapSamgr001:PASS
g_coreInit0: 01: 0default: 03: 04: 05: 0../../../test/xts/acts/startup_lite/bootstrap_hal/src/samgr_api_test.c:353:TestStartBootstrapSamgr002:PASS
g_sysServiceInit0: 01: 0default: 03: 04: 05: 0../../../test/xts/acts/startup_lite/bootstrap_hal/src/samgr_api_test.c:365:TestStartBootstrapSamgr003:PASS
g_sysFeatureInit0: 01: 0default: 03: 04: 05: 0../../../test/xts/acts/startup_lite/bootstrap_hal/src/samgr_api_test.c:377:TestStartBootstrapSamgr004:PASS
g_sysExServiceInit0: 01: 0default: 03: 04: 05: 0g_appServiceInit0: 01: 0default: 03: 04: 05: 0../../../test/xts/acts/startup_lite/bootstrap_hal/src/samgr_api_test.c:389:TestStartBootstrapSamgr005:PASS
g_sysExFeatureInit0: 01: 0default: 03: 04: 05: 0g_appFeatureInit0: 01: 0default: 03: 04: 05: 0../../../test/xts/acts/startup_lite/bootstrap_hal/src/samgr_api_test.c:403:TestStartBootstrapSamgr006:PASS
g_sysRun0: 01: 0default: 03: 04: 05: 0../../../test/xts/acts/startup_lite/bootstrap_hal/src/samgr_api_test.c:417:TestStartBootstrapSamgr007:PASS
+-------------------------------------------+

-----------------------
7 Tests 0 Failures 0 Ignored 
OK
All the test suites finished!

bootstrap加入acts兼容性測試成功

補充非對齊訪問的問題

根據(jù)STM32 Cortex?-M4 MCUs and MPUs programming manual - Programming manual的說法,cortex-m4支持非對齊訪問。不對齊訪問比對齊訪問要慢,而且有些存儲區(qū)域不支持非對齊訪問。所以arm官方建議對齊訪問。如下圖所示:

address alignment.png

為了使用非對齊訪問,需要將寄存器NVIC_CCR中的UNALIGN_TRP位清零。當(dāng)UNALIGN_TRP=0時,非對齊訪問就不會觸發(fā)usage fault。
liteos_m中make menuconfig可以關(guān)閉對齊訪問操作。如下圖所示:


unalign.png

這樣的話就可以在不改源碼的情況下,解決對齊訪問的問題。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容