SDK的编译系统的解析
SDK的编译系统
默认sdk会编译三个目标ws63-loaderboot,ws63-flashboot,ws63-liteos-app。
这是三个目标分别对应ws63芯片不同的启动阶段。
ws63开发阶段,使用串口烧录时,烧录程序burntool会先上传一段搬运程序到内存中,然后运行内存中的搬运程序进行后续的flash烧录。
批量生产时,则使用专业的flash烧录器直接烧录到flash烧录芯片,不需要这个搬运程序
ws63具备240M的运行频率,属于比较高的运行频率。但是flash的读取速度跟不上。所以在ws63内部有一块儿高速cache作为flash指令读取的缓冲区。这样的设计导致硬件初始化复杂,真正运行程序时,需要先对硬件设备进行初始化。
- ws63-loaderboot,串口烧录时的搬运程序,直接在内存中运行,进行后续烧录。
- ws63-flashboot, 初始化硬件,为后续运行操作系统提供环境。类似bios
- 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的开发者不太适应的原因。

浙公网安备 33010602011771号