Zephyr如何运行到main

Creative Commons
本作品采用知识共享署名

本文以qemu_cortex_m3为例说明zephyr从上电后到如何运行到Zephyr app的main函数

概述

Zephyr创建应用&模拟运行中介绍了如何创建和运行一个zephyr app. 一个app的入口函数是main函数,但一个zephyr的嵌入式系统平台启动时并不能从main函数开始执行,本文将以qemu_cortex_m3平台为例说明从上电开始如何一步一步执行到main函数。
zephyr启动流程大体分为下面几步:

  • 汇编阶段(arch相关)
  • prep_c阶段(arch相关)
  • init阶段(arch无关)
    前面两部分对于不同的arch不一样,从init阶段开始就都一样了,所以根据你的平台研究前面两部分,然后所有平台都熟悉init之后的部分。

汇编阶段

ARM Cortex M3上电

arch/arm/core/cortex_m/vector_table.S中定义了向量表,CPU上电后,会从__reset向量所在位置执行

1
2
3
4
5
6
7
SECTION_SUBSEC_FUNC(exc_vector_table,_vector_table_section,_vector_table)
.word _main_stack + CONFIG_MAIN_STACK_SIZE

.word __reset
.word __nmi

.word __hard_fault

__reset

arch/arm/core/cortex_m/reset.S 中的__reset函数在启动过程主要完成下面3件事

  1. 关闭中断
  2. 初始中断stack
  3. 跳到prep C阶段执行
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    SECTION_SUBSEC_FUNC(TEXT,_reset_section,__start)
    //关闭中断
    movs.n r0, #_EXC_IRQ_DEFAULT_PRIO
    msr BASEPRI, r0

    //初始化中断堆栈
    ldr r0, =_interrupt_stack
    ldr r1, =CONFIG_ISR_STACK_SIZE
    adds r0, r0, r1
    msr PSP, r0
    movs.n r0, #2 /* switch to using PSP (bit1 of CONTROL reg) */
    msr CONTROL, r0

    //跳到prep C阶段执行
    b _PrepC

Prep C阶段

Prec C阶段主要是为后面的C执行准备环境包括:

  1. bss段清0
  2. data段拷贝
  3. 进入C环境执行

arch/arm/core/cortex_m/prep_c.C中_PrepC

1
2
3
4
5
6
7
8
9
10
11
12
13
14
void _PrepC(void)
{
relocate_vector_table(); //重新分配向量表
enable_floating_point();
_bss_zero(); //bss段清0
_data_copy(); //copy data段
#ifdef CONFIG_BOOT_TIME_MEASUREMENT
__start_time_stamp = 0;
#endif
_Cstart(); //启动Kernel,执行Main函数
CODE_UNREACHABLE;
}

#define CODE_UNREACHABLE __builtin_unreachable()

说明: __builtin_unreachable()并不执行任何操作,只是为了告诉编译器,代码不会执行到这里来, 参考地址

init阶段

kernel/init.c 的_Cstart函数主要完成thread环境准备,低级驱动初始化,切换到main thread运行

1
2
3
4
5
6
7
8
9
10
11
FUNC_NORETURN void _Cstart(void)
{
prepare_multithreading(dummy_thread);//中断初始,kernel初始化,准备main thread, 待启动

_sys_device_do_config_level(_SYS_INIT_LEVEL_PRE_KERNEL_1); //初始化PRE_KERNEL_1驱动
_sys_device_do_config_level(_SYS_INIT_LEVEL_PRE_KERNEL_2); //初始化PRE_KERNEL_2驱动

switch_to_main_thread(); //切换到main thread执行

CODE_UNREACHABLE;
}

thread环境准备

主要完成初始化kernel数据结构,准备好main thread

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
static void prepare_multithreading(struct k_thread *dummy_thread)
{
....

_IntLibInit(); //中断初始化

for (int ii = 0; ii < K_NUM_PRIORITIES; ii++) {
sys_dlist_init(&_ready_q.q[ii]);
}

_ready_q.cache = _main_thread;
//创建和标记main thread
_setup_new_thread(_main_thread, _main_stack,
MAIN_STACK_SIZE, _main, NULL, NULL, NULL,
CONFIG_MAIN_THREAD_PRIORITY, K_ESSENTIAL);
_mark_thread_as_started(_main_thread);
_add_thread_to_ready_q(_main_thread);

//初始化idle thread
init_idle_thread(_idle_thread, _idle_stack);
//system clock初始化
initialize_timeouts();
//进行和arch有关的kernel设置
kernel_arch_init();
}

低级驱动初始化

Zephyr的驱动等级一共分四种PRE_KERNEL_1&PRE_KERNEL_2&POST_KERNEL&APPLICATION,本文把PRE_KERNEL_1&PRE_KERNEL_2叫做低级驱动,详细的Zephyr驱动模型以后另写文章,这里不做介绍。
完成kernel初始化后,__Cstart进行PRE_KERNEL_1&PRE_KERNEL_2驱动初始化,软由switch_to_main_thread切到main thread内运行,在main thread中继续初始化POST_KERNEL和APPLICATION等级的驱动

1
2
_sys_device_do_config_level(_SYS_INIT_LEVEL_PRE_KERNEL_1);
_sys_device_do_config_level(_SYS_INIT_LEVEL_PRE_KERNEL_2);

切换到main thread运行

main thread的thread函数为bg_thread_main完成下面几件事:

  1. 初始化POST_KERNEL驱动
  2. 初始化APPLICATION驱动
  3. 初始化静态thread
  4. 跳到main函数执行(这里的main就是app的main函数)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    static void switch_to_main_thread(void)
    {

    _arch_switch_to_main_thread(_main_thread, _main_stack, MAIN_STACK_SIZE,
    bg_thread_main);

    }

    static void bg_thread_main(void *unused1, void *unused2, void *unused3)
    {
    ARG_UNUSED(unused1);
    ARG_UNUSED(unused2);
    ARG_UNUSED(unused3);

    _sys_device_do_config_level(_SYS_INIT_LEVEL_POST_KERNEL); //初始化POST_KERNEL驱动
    if (boot_delay > 0) {
    printk("***** delaying boot " STRINGIFY(CONFIG_BOOT_DELAY)
    "ms (per build configuration) *****\n");
    k_busy_wait(CONFIG_BOOT_DELAY * USEC_PER_MSEC);
    }
    PRINT_BOOT_BANNER(); //显示boot字符串
    /* Final init level before app starts */
    _sys_device_do_config_level(_SYS_INIT_LEVEL_APPLICATION); //初始化APPLICATION驱动

    _init_static_threads(); //如果定义到有静态thread(放在._static_thread_data.static.内),在这里初始化

    extern void main(void);

    main(); //跳到main执行,这里的main就是app内定义的main,之后就是执行APP的代码

    /* Terminate thread normally since it has no more work to do */
    _main_thread->base.user_options &= ~K_ESSENTIAL;
    }