Zephyr如何运行到main

本文以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向量所在位置执行

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阶段执行
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

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运行

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

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等级的驱动

	_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函数)
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;
}