跳转至

Zephyr 启动流程

Zephyr OS 是一个基于小型内核设计的 RTOS,面向资源受限的嵌入式系统。 其应用场景从简单的嵌入式环境传感器、LED可穿戴设备,到复杂的嵌入式控制器、 智能手表以及 IOT 无线应用。

此笔记概要地分析了 Zephyr 的启动流程。

reset.S 汇编初始化

reset.S 汇编文件为芯片复位后执行入口,执行一些需要汇编介入的最底层的操作,然后跳转到 z_arm_prep_c

 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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
/*
 * The entry point is located at the z_arm_reset symbol, which
 * is fetched by a XIP image playing the role of a bootloader, which jumps to
 * it, not through the reset vector mechanism. Such bootloaders might want to
 * search for a __start symbol instead, so create that alias here.
 */
SECTION_SUBSEC_FUNC(TEXT,_reset_section,__start)

#if defined(CONFIG_PLATFORM_SPECIFIC_INIT)
    bl z_platform_init
#endif

    /* lock interrupts: will get unlocked when switch to main task */
#if defined(CONFIG_ARMV6_M_ARMV8_M_BASELINE)
    cpsid i
#elif defined(CONFIG_ARMV7_M_ARMV8_M_MAINLINE)
    movs.n r0, #_EXC_IRQ_DEFAULT_PRIO
    msr BASEPRI, r0
#else
#error Unknown ARM architecture
#endif

#ifdef CONFIG_WDOG_INIT
    /* board-specific watchdog initialization is necessary */
    bl z_arm_watchdog_init
#endif

/*
 *
 * Note: in all Cortex-M variants the interrupt stack area is at
 * least equal to CONFIG_ISR_STACK_SIZE + MPU_GUARD_ALIGN_AND_SIZE
 * (may be larger due to rounding up for stack pointer aligning
 * purposes but this is sufficient during initialization).
 */

#ifdef CONFIG_INIT_STACKS
    ldr r0, =z_interrupt_stacks
    ldr r1, =0xaa
    ldr r2, =CONFIG_ISR_STACK_SIZE + MPU_GUARD_ALIGN_AND_SIZE
    bl memset
#endif

    /*
     * Set PSP and use it to boot without using MSP, so that it
     * gets set to z_interrupt_stacks during initialization.
     */
    ldr r0, =z_interrupt_stacks
    ldr r1, =CONFIG_ISR_STACK_SIZE + MPU_GUARD_ALIGN_AND_SIZE
    adds r0, r0, r1
    msr PSP, r0
    mrs r0, CONTROL
    movs r1, #2
    orrs r0, r1 /* CONTROL_SPSEL_Msk */
    msr CONTROL, r0
    /*
     * When changing the stack pointer, software must use an ISB instruction
     * immediately after the MSR instruction. This ensures that instructions
     * after the ISB instruction execute using the new stack pointer.
     */
    isb

    /*
     * 'bl' jumps the furthest of the branch instructions that are
     * supported on all platforms. So it is used when jumping to z_arm_prep_c
     * (even though we do not intend to return).
     */
    bl z_arm_prep_c

z_arm_prep_c C环境初始化

z_arm_prep_c() 函数同样也是执行一些底层初始化工作,初始化C的运行环境。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/**
 *
 * @brief Prepare to and run C code
 *
 * This routine prepares for the execution of and runs C code.
 *
 * @return N/A
 */
void z_arm_prep_c(void)
{
    relocate_vector_table();
#if defined(CONFIG_CPU_HAS_FPU)
    z_arm_floating_point_init();
#endif
    z_bss_zero();
    z_data_copy();
#if defined(CONFIG_ARMV7_R) && defined(CONFIG_INIT_STACKS)
    z_arm_init_stacks();
#endif
    z_arm_interrupt_init();
    z_cstart();
    CODE_UNREACHABLE;
}

z_cstart 内核初始化

z_cstart() 函数初始化内核相关功能,并且执行 _SYS_INIT_LEVEL_PRE_KERNEL_1 与 _SYS_INIT_LEVEL_PRE_KERNEL_2 级别的初始化工作。

然后初始化调度器,启动main线程(多线程模式)或者跳转到 bg_thread_main(不启用多线程)。

需要注意的是单线程模式现在处于弃用状态,并且如无意外将会在 2.6 版本移除。

此时内核相关功能无法使用,因此主要是一些跟中断相关的较底层代码会在此阶段运行。

 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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
/**
 *
 * @brief Initialize kernel
 *
 * This routine is invoked when the system is ready to run C code. The
 * processor must be running in 32-bit mode, and the BSS must have been
 * cleared/zeroed.
 *
 * @return Does not return
 */
FUNC_NORETURN void z_cstart(void)
{
    /* gcov hook needed to get the coverage report.*/
    gcov_static_init();

    LOG_CORE_INIT();

    /* perform any architecture-specific initialization */
    arch_kernel_init();

#if defined(CONFIG_MULTITHREADING)
    /* Note: The z_ready_thread() call in prepare_multithreading() requires
     * a dummy thread even if CONFIG_ARCH_HAS_CUSTOM_SWAP_TO_MAIN=y
     */
    struct k_thread dummy_thread;

    z_dummy_thread_init(&dummy_thread);
#endif

    /* perform basic hardware initialization */
    z_sys_init_run_level(_SYS_INIT_LEVEL_PRE_KERNEL_1);
    z_sys_init_run_level(_SYS_INIT_LEVEL_PRE_KERNEL_2);

#ifdef CONFIG_STACK_CANARIES
    uintptr_t stack_guard;

    z_early_boot_rand_get((uint8_t *)&stack_guard, sizeof(stack_guard));
    __stack_chk_guard = stack_guard;
    __stack_chk_guard <<= 8;
#endif  /* CONFIG_STACK_CANARIES */

#ifdef CONFIG_MULTITHREADING
    switch_to_main_thread(prepare_multithreading());
#else
#ifdef ARCH_SWITCH_TO_MAIN_NO_MULTITHREADING
    /* Custom ARCH-specific routine to switch to main()
     * in the case of no multi-threading.
     */
    ARCH_SWITCH_TO_MAIN_NO_MULTITHREADING(bg_thread_main,
        NULL, NULL, NULL);
#else
    bg_thread_main(NULL, NULL, NULL);

    /* LCOV_EXCL_START
     * We've already dumped coverage data at this point.
     */
    irq_lock();
    while (true) {
    }
    /* LCOV_EXCL_STOP */
#endif
#endif /* CONFIG_MULTITHREADING */

    /*
     * Compiler can't tell that the above routines won't return and issues
     * a warning unless we explicitly tell it that control never gets this
     * far.
     */

    CODE_UNREACHABLE; /* LCOV_EXCL_LINE */
}

bg_thread_main 启动应用层线程

bg_thread_main 将执行 _SYS_INIT_LEVEL_POST_KERNEL 与 _SYS_INIT_LEVEL_APPLICATION 级别的初始化工作,然后跳转到 main()

此时调度器已经运行,并且各种内核服务可用。

大多数驱动会在 _SYS_INIT_LEVEL_POST_KERNEL 阶段初始化,同时也根据紧急程度设定优先级。

如:

1
2
3
#define CONFIG_KERNEL_INIT_PRIORITY_OBJECTS 30
#define CONFIG_KERNEL_INIT_PRIORITY_DEFAULT 40
#define CONFIG_KERNEL_INIT_PRIORITY_DEVICE 50

大多数系统相关服务 _SYS_INIT_LEVEL_APPLICATION,如网络、BSD相关服务、依赖于其他驱动的驱动等。

然后系统将创建与运行定义的线程(多线程模式下),并且进入 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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
/**
 *
 * @brief Mainline for kernel's background thread
 *
 * This routine completes kernel initialization by invoking the remaining
 * init functions, then invokes application's main() routine.
 *
 * @return N/A
 */
static void bg_thread_main(void *unused1, void *unused2, void *unused3)
{
    ARG_UNUSED(unused1);
    ARG_UNUSED(unused2);
    ARG_UNUSED(unused3);

#if defined(CONFIG_BOOT_DELAY) && CONFIG_BOOT_DELAY > 0
    static const unsigned int boot_delay = CONFIG_BOOT_DELAY;
#else
    static const unsigned int boot_delay;
#endif

    z_sys_post_kernel = true;

    z_sys_init_run_level(_SYS_INIT_LEVEL_POST_KERNEL);
#if CONFIG_STACK_POINTER_RANDOM
    z_stack_adjust_initialized = 1;
#endif
    if (boot_delay > 0 && IS_ENABLED(CONFIG_MULTITHREADING)) {
        printk("***** delaying boot " STRINGIFY(CONFIG_BOOT_DELAY)
               "ms (per build configuration) *****\n");
        k_busy_wait(CONFIG_BOOT_DELAY * USEC_PER_MSEC);
    }

#if defined(CONFIG_BOOT_BANNER)
#ifdef BUILD_VERSION
    printk("*** Booting Zephyr OS build %s %s ***\n",
            STRINGIFY(BUILD_VERSION), BOOT_DELAY_BANNER);
#else
    printk("*** Booting Zephyr OS version %s %s ***\n",
            KERNEL_VERSION_STRING, BOOT_DELAY_BANNER);
#endif
#endif

#ifdef CONFIG_CPLUSPLUS
    /* Process the .ctors and .init_array sections */
    extern void __do_global_ctors_aux(void);
    extern void __do_init_array_aux(void);
    __do_global_ctors_aux();
    __do_init_array_aux();
#endif

    /* Final init level before app starts */
    z_sys_init_run_level(_SYS_INIT_LEVEL_APPLICATION);

    z_init_static_threads();

#ifdef CONFIG_SMP
    z_smp_init();
    z_sys_init_run_level(_SYS_INIT_LEVEL_SMP);
#endif

#ifdef CONFIG_BOOT_TIME_MEASUREMENT
    z_timestamp_main = k_cycle_get_32();
#endif

    extern void main(void);

    main();

    /* Mark nonessenrial since main() has no more work to do */
    z_main_thread.base.user_options &= ~K_ESSENTIAL;

#ifdef CONFIG_COVERAGE_DUMP
    /* Dump coverage data once the main() has exited. */
    gcov_coverage_dump();
#endif
} /* LCOV_EXCL_LINE ... because we just dumped final coverage data */