SDK的编译系统的解析

SDK的编译系统

默认sdk会编译三个目标ws63-loaderboot,ws63-flashboot,ws63-liteos-app

这是三个目标分别对应ws63芯片不同的启动阶段。

ws63开发阶段,使用串口烧录时,烧录程序burntool会先上传一段搬运程序到内存中,然后运行内存中的搬运程序进行后续的flash烧录。
批量生产时,则使用专业的flash烧录器直接烧录到flash烧录芯片,不需要这个搬运程序

ws63具备240M的运行频率,属于比较高的运行频率。但是flash的读取速度跟不上。所以在ws63内部有一块儿高速cache作为flash指令读取的缓冲区。这样的设计导致硬件初始化复杂,真正运行程序时,需要先对硬件设备进行初始化。

  1. ws63-loaderboot,串口烧录时的搬运程序,直接在内存中运行,进行后续烧录。
  2. ws63-flashboot, 初始化硬件,为后续运行操作系统提供环境。类似bios
  3. ws63-liteos-app, 硬件环境稳定之后的程序,传统意义上的main函数,类似kernel

从上述的是三个编译目标可以看到,这个流程和pc的启动非常相似,大体上分为硬件初始化和软件环境。因此我们只需要关注第三个编译目标ws63-liteos-app即可。

接下来的文章,如果没有特殊说明,都是指ws63-liteos-app目标中的代码函数。

main函数

reset_vector文件在application/ws63/ws63_liteos_application/reset_vector.S

main入口文件在 src/application/ws63/ws63_liteos_application/main.c.

// reset_vector.S

// ...
set_stack_loop:
    sw      t2, (t0)
    addi    t0, t0, 4
    blt     t0, t1, set_stack_loop
end_set_stack_loop:

    tail    runtime_init

最后应该是跳转runtime_init函数,该函数在main.c中定义

// main.c

// ...
__attribute__((section(".text.runtime.init"))) void runtime_init(void)
{
    dyn_mem_cfg();
#ifndef CHIP_EDA
    do_relocation();
#endif
    /* Jump to main */
    main();
}

runtime_init开始正式摆脱汇编,进入c代码阶段。

经过dyn_mem_cfg处理之后,正式进入main函数中。

在main函数中,首先进行硬件使能与安全,接下来liteos的预启动,中间件的加载工作,业务任务的初始化创建(此时还不运行),最后调用Kernel的调度器,彻底走上不归路。最后的for (;;)是为了安全措施,如果内核调度意外退出,在这里死循环,防止跑飞。正常情况下不会发生的。

那么,为什么看不到具体的业务task初始化呢?这就是SDK在编译阶段,利用链接器做的特殊处理

从main函数的最后阶段可以看到app_tasks_init函数,这个函数中处理的就是具体些的task任务。

// main.c

LITE_OS_SEC_TEXT_INIT int main(void)
{

    // 各种初始化代码...

    main_initialise(NULL, 0);

    OHOS_SystemInit();
    app_tasks_init();

    /* Start LiteOS */
    (void)osKernelStart();
    /* jump to main() in asm, so ra is main(), return will loop main() */
    for (;;) {}
    return 0;
}

函数的具体实现在src/middleware/utils/app_init/app_init.c中, 可以看到从一段函数指针数组中调用初始化函数。__zinitcall_app_run_start__zinitcall_app_run_end。如果你找过SDK中的c代码,会发现,头文件和c代码中根本没有定义过这两个符号。那么,这两个符号在哪里?

// app_init.c
#include "app_init.h"

extern init_call_t __zinitcall_app_run_start;
extern init_call_t __zinitcall_app_run_end;

void app_tasks_init(void)
{
    init_call_t *initcall = &__zinitcall_app_run_start;
    init_call_t *initend = &__zinitcall_app_run_end;
    for (; initcall < initend; initcall++) {
        (*initcall)();
    }
}

其实这两个符号不存在于编译阶段,最终的链接的时候直接定义在了链接文件中。具体位置在drivers/boards/ws63/evb/linker/ws63_liteos_app_linker/linker.prelds

// linker.prelds
SECTIONS
{
    startup : ALIGN(8) {
        ADD_ROM_VERIFY_OFFSET
        KEEP (*(.text.entry))
        KEEP (*(.text.runtime.init))

        . = ALIGN(4);
        __zinitcall_app_run_start = .;
        KEEP(*(.zinitcall.app_run*.init))
        __zinitcall_app_run_end = .;
    } > PROGRAM_STARTUP

    /* ..... 省略 ...... */

可以看到这个数组是由.zinitcall.app_run*.init组成的。那么这个数组是怎么来的?

从命名可以看出,这个是由一组特殊命名的独立段组成。这个段是由具体的业务代码注册进来的。

官方的hello world中可以看到最后调用了app_run。其实app_run并不是函数,而是一个宏。

/**
 * Copyright (c) HiSilicon (Shanghai) Technologies Co., Ltd. 2023-2023. All rights reserved.
 */

#include "common_def.h"
#include "soc_osal.h"
#include "app_init.h"

#define DEFAULT_TASK_STACK_SIZE         0x1000
#define DEFAULT_TASK_PRIORITY           26
#define DELAYS_MS                       1000


static void *hw_task(const char *arg)
{
    unused(arg);
    osal_printk("start helloworld sample\r\n");
    for(;;){
        osal_printk("hello world\r\n");
        osal_msleep(DELAYS_MS);
    }
    return NULL;

}


static void helloworld_entry(void)
{
    osal_task *task_handle = NULL;
    osal_kthread_lock();
    task_handle = osal_kthread_create((osal_kthread_handler)hw_task, 0, "HW_Task", DEFAULT_TASK_STACK_SIZE);
    if (task_handle != NULL) {
        osal_kthread_set_priority(task_handle, DEFAULT_TASK_PRIORITY);
    }
    osal_kthread_unlock();
}

/* Run the helloword_entry. */
app_run(helloworld_entry);

app_run定义在src\middleware\utils\app_init\app_init.h

// app_init.h
// ...
typedef void (*init_call_t)(void);

#define USED_ATTR __attribute__((used))

#define layer_initcall(func, layer, clayer, priority)            \
    static const init_call_t USED_ATTR __zinitcall_##layer##_##func \
        __attribute__((section(".zinitcall." clayer #priority ".init"))) = (func)

#define layer_initcall_def(func, layer, clayer) \
    layer_initcall(func, layer, clayer, 0)

/**
 * @brief Identifies the entry for initializing and starting a system running phase.
 *
 * @param func Indicates the entry function for initializing and starting a system running phase.
 * The type is void (*)(void).
 */

/**
 * @if Eng
 * @brief  Identifies the entry for initializing and starting a system running phase.
 * @param  [in]  func Indicates the entry function for initializing and starting a system running phase.
 *                    The type is void (*)(void).
 * @else
 * @brief  定义系统初始化和启动的入口。
 * @param  [in]  func 系统初始化和启动的入口。类型是:void (*)(void)。
 * @endif
 */
#define app_run(func) layer_initcall_def(func, run, "app_run")


// ...

到这里,整个流程就完全清晰了。

写具体的业务代码时分为两个部分:注册和业务内容。

任务的注册,负责创建任务handle,需要把真正的任务函数告知LiteOS。任务函数的业务内容写真正的业务内容。使用app_run把任务的注册函数放到对应的初始化数组中。

这也就是为什么每个任务必须要使用app_run的原因。

后记

为什么要把流程搞的这么复杂呢,我猜测是为了屏蔽初始化细节,简化应用层的开发。另外,由于ws63的核心是作为射频芯片来使用的,LiteOS必须要严格保证射频的时序。虽然ws63的频率最高可以达到240MHz,但是主要是为了让射频正常工作。所以需要严格限制application的干扰,优先保证射频通信,这样留给application的自由度就要小很多。

这也是很多51和stm32的开发者不太适应的原因。

posted @ 2026-03-14 13:26  Xdesigner  阅读(0)  评论(0)    收藏  举报