Zephyr启动过程与中断响应
// 本文部分内容来自网络
1. 启动过程
一般嵌入式处理器启动方式分为两种:
1. XIP 模式 (eXecute In Place), 在该模式下CPU直接从Nor Flash上读代码执行,执行速度慢 ;
2. 非XIP模式, 在该模式下硬件先将代码从Flash上搬移到RAM上后,CPU才能从RAM上访问数据,执行速度快;
对于非XIP模式,启动前需要先借助boot把代码段从FLASH拷贝到RAM;
对于XIP模式,如果FLASH基地址映射到0地址,复位后可以直接启动;
如果FLASH基地址非0地址,启动之前需要先借助boot在映射的0地址(笔者项目的使用SOC通过bootlink将0地址映射到了0x20110000 :IRAM_AON)中写入MSP的初始值+Reset入口地址(针对CortexM处理器)。
下文以CortexM + Zephyr1.9.2 +XIP模式 为例进行相关阐述。
__reset
根据向量表找到的reset入口,位于(zephyr\arch\arm\core\cortex_m\reset.S)
reset入口完成工作:
- 初始化watchdog: _WdogInit
- 将中断栈_interrupt_stack内容初始化为0xaa
- 复位后CortexM处理器默认处于线程模式+特权访问+使用MSP主栈指针,这里切换为PSP指针并将PSP设为_interrupt_stack中断栈
- 进入C语言准备_PrepC
SECTION_SUBSEC_FUNC(TEXT,_reset_section,__reset) /* * The entry point is located at the __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) /* lock interrupts: will get unlocked when switch to main task */ cpsid i #ifdef CONFIG_WDOG_INIT /* board-specific watchdog initialization is necessary */ bl _WdogInit #endif #ifdef CONFIG_INIT_STACKS ldr r0, =_interrupt_stack ldr r1, =0xaa ldr r2, =CONFIG_ISR_STACK_SIZE bl memset #endif /* * Set PSP and use it to boot without using MSP, so that it * gets set to _interrupt_stack during nanoInit(). */ 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 b _PrepC
_PrepC
完成进入C语言环境后的准备工作,位于(zephyr\arch\arm\core\cortex_m\Prep_c.c)
_PrepC完成工作:
- 重定位向量表
- 使能浮点计算(如果有相关特性)
- 清零BSS段
- 从ROM拷贝数据段到RAM(针对XIP模式)
- 正式进入C环境执行
void _PrepC(void) { relocate_vector_table(); enable_floating_point(); _bss_zero(); _data_copy(); #ifdef CONFIG_BOOT_TIME_MEASUREMENT __start_time_stamp = 0; #endif _Cstart(); CODE_UNREACHABLE; }
relocate_vector_table
根据CortexM处理器的特性,向量表固定于0地址,对于(XIP模式并且FLASH基地址非0)或者(非XIP模式并且RAM基地址非0)的情况,需要重新将向量表拷贝到0地址。
#define VECTOR_ADDRESS 0 static inline void relocate_vector_table(void) { #if defined(CONFIG_XIP) && (CONFIG_FLASH_BASE_ADDRESS != 0) || \ !defined(CONFIG_XIP) && (CONFIG_SRAM_BASE_ADDRESS != 0) size_t vector_size = (size_t)_vector_end - (size_t)_vector_start; memcpy(VECTOR_ADDRESS, _vector_start, vector_size); #endif }
_vector_end/_vector_start两个符号的地址在linker.ld链接脚本里面有定义,linker文件位于(zephyr\include\arch\arm\cortex_m\scripts):
SECTION_PROLOGUE(_TEXT_SECTION_NAME,,) { . = CONFIG_TEXT_SECTION_OFFSET; KEEP(*(.os.start)) _vector_start = .; KEEP(*(.exc_vector_table)) KEEP(*(".exc_vector_table.*")) KEEP(*(IRQ_VECTOR_TABLE)) KEEP(*(.openocd_dbg)) KEEP(*(".openocd_dbg.*")) /* Kinetis has to write 16 bytes at 0x400 */ SKIP_TO_KINETIS_FLASH_CONFIG KEEP(*(.kinetis_flash_config)) KEEP(*(".kinetis_flash_config.*")) #ifdef CONFIG_GEN_SW_ISR_TABLE KEEP(*(SW_ISR_TABLE)) #endif _vector_end = .; _image_text_start = .; *(.text) *(".text.*") *(.gnu.linkonce.t.*) } GROUP_LINK_IN(ROMABLE_REGION) _image_text_end = .;
_Cstart
正式进入C语言环境,开始操作系统初始化工作,位于(zephyr\kernel\init.c)
_Cstart完成工作:
- 多线程初始化
- PRE_KERNEL_1、PRE_KERNEL_2级别设备初始化
- 切换到主线程开始运行
FUNC_NORETURN void _Cstart(void) { #ifdef CONFIG_TIME_TRACK insert_pack_time_debug(PACK_AP_CSTART, NULL, -1); #endif #ifdef CONFIG_ARCH_HAS_CUSTOM_SWAP_TO_MAIN struct k_thread *dummy_thread = NULL; #else struct k_thread dummy_thread_memory; struct k_thread *dummy_thread = &dummy_thread_memory; #endif /* * Initialize kernel data structures. This step includes * initializing the interrupt subsystem, which must be performed * before the hardware initialization phase. */ prepare_multithreading(dummy_thread); /* perform basic hardware initialization */ _sys_device_do_config_level(_SYS_INIT_LEVEL_PRE_KERNEL_1); _sys_device_do_config_level(_SYS_INIT_LEVEL_PRE_KERNEL_2); /* initialize stack canaries */ #ifdef CONFIG_STACK_CANARIES __stack_chk_guard = (void *)sys_rand32_get(); #endif /* display boot banner */ switch_to_main_thread(); /* * 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; }
prepare_multithreading
该函数完成多线程初始化,主要完成
- 中断(NVIC)初始化
- 创建了两个线程,分别是_main_thread(使用_main_stack,即系统入口对应的MSP指针)和_idle_thread(使用_idle_stack)
- 初始化_timeout_q超时队列
- 架构相关的内核初始化,针对CortexM主要完成MSP主栈设置为_interrupt_stack,以及中断异常相关初始化
static void prepare_multithreading(struct k_thread *dummy_thread) { #ifdef CONFIG_ARCH_HAS_CUSTOM_SWAP_TO_MAIN ARG_UNUSED(dummy_thread); #else /* * Initialize the current execution thread to permit a level of * debugging output if an exception should happen during kernel * initialization. However, don't waste effort initializing the * fields of the dummy thread beyond those needed to identify it as a * dummy thread. */ _current = dummy_thread; dummy_thread->base.user_options = K_ESSENTIAL; dummy_thread->base.thread_state = _THREAD_DUMMY; #endif /* _kernel.ready_q is all zeroes */ /* * The interrupt library needs to be initialized early since a series * of handlers are installed into the interrupt table to catch * spurious interrupts. This must be performed before other kernel * subsystems install bonafide handlers, or before hardware device * drivers are initialized. */ _IntLibInit(); /* ready the init/main and idle threads */ for (int ii = 0; ii < K_NUM_PRIORITIES; ii++) { sys_dlist_init(&_ready_q.q[ii]); } /* * prime the cache with the main thread since: * * - the cache can never be NULL * - the main thread will be the one to run first * - no other thread is initialized yet and thus their priority fields * contain garbage, which would prevent the cache loading algorithm * to work as intended */ _ready_q.cache = _main_thread; _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); #ifdef CONFIG_MULTITHREADING _new_thread(_idle_thread, _idle_stack, IDLE_STACK_SIZE, idle, NULL, NULL, NULL, K_LOWEST_THREAD_PRIO, K_ESSENTIAL); _mark_thread_as_started(_idle_thread); _add_thread_to_ready_q(_idle_thread); #endif initialize_timeouts(); /* perform any architecture-specific initialization */ kernel_arch_init(); }
_sys_device_do_config_level
设备初始化,Zephyr定义了四个级别的初始化,分别为:
#define _SYS_INIT_LEVEL_PRE_KERNEL_1 0 #define _SYS_INIT_LEVEL_PRE_KERNEL_2 1 #define _SYS_INIT_LEVEL_POST_KERNEL 2 #define _SYS_INIT_LEVEL_APPLICATION 3
_sys_device_do_config_level函数实现:
void _sys_device_do_config_level(int level) { struct device *info; for (info = config_levels[level]; info < config_levels[level+1]; info++) { struct device_config *device = info->config; device->init(info); #ifdef CONFIG_TIME_TRACK insert_pack_time_debug(ap_init_counter, device->init, level); ap_init_counter++; #endif } }
设备初始化接口定义:
SYS_INIT(netdog_work_q_init, APPLICATION, CONFIG_KERNEL_INIT_PRIORITY_DEFAULT); #define SYS_INIT(init_fn, level, prio) \ DEVICE_INIT(_SYS_NAME(init_fn), "", init_fn, NULL, NULL, level, prio) #define DEVICE_INIT(dev_name, drv_name, init_fn, data, cfg_info, level, prio) \ DEVICE_AND_API_INIT(dev_name, drv_name, init_fn, data, cfg_info, \ level, prio, NULL) #define DEVICE_AND_API_INIT(dev_name, drv_name, init_fn, data, cfg_info, \ level, prio, api) \ DEVICE_DEFINE(dev_name, drv_name, init_fn, \ device_pm_control_nop, data, cfg_info, level, \ prio, api) #define DEVICE_DEFINE(dev_name, drv_name, init_fn, pm_control_fn, \ data, cfg_info, level, prio, api) \ \ static struct device_config _CONCAT(__config_, dev_name) __used \ __attribute__((__section__(".devconfig.init"))) = { \ .name = drv_name, .init = (init_fn), \ .device_pm_control = (pm_control_fn), \ .config_info = (cfg_info) \ }; \ static struct device _CONCAT(__device_, dev_name) __used \ __attribute__((__section__(".init_" #level STRINGIFY(prio)))) = { \ .config = &_CONCAT(__config_, dev_name), \ .driver_api = api, \ .driver_data = data \ }
switch_to_main_thread
切换到_main_thread开始运行:
static void switch_to_main_thread(void) { #ifdef CONFIG_ARCH_HAS_CUSTOM_SWAP_TO_MAIN _arch_switch_to_main_thread(_main_thread, _main_stack, MAIN_STACK_SIZE, _main); #else /* * Context switch to main task (entry function is _main()): the * current fake thread is not on a wait queue or ready queue, so it * will never be rescheduled in. */ _Swap(irq_lock()); #endif }
_main_thread线程入口为_main,_main函数完成:
- 平台相关的初始化以及_SYS_INIT_LEVEL_POST_KERNEL、SYS_INIT_LEVEL_APPLICATION级别设备初始化,这些初始化会创建相关的平台与应用线程;
- 完成后进入main钩子函数(\os\zephyr\samples\xxx\src\main.c),本平台该函数实现为空;
- 该线程没有死循环入口,所以完成后自行退出,然后操作系统开始正式调度执行;
static void _main(void *unused1, void *unused2, void *unused3) { ARG_UNUSED(unused1); ARG_UNUSED(unused2); ARG_UNUSED(unused3); oss_event_init(); oss_icp_init(); oss_nv_init(); _reset_uart_termios(); ramdump_init(); amt_init(); zcat_ap_init(); _sys_device_do_config_level(_SYS_INIT_LEVEL_POST_KERNEL); /* Final init level before app starts */ _sys_device_do_config_level(_SYS_INIT_LEVEL_APPLICATION); #if defined(CONFIG_BOOT_DELAY) && CONFIG_BOOT_DELAY > 0 if (boot_delay > 0) { printk("delaying boot\n"); k_sleep(CONFIG_BOOT_DELAY); } PRINT_BOOT_BANNER(); #endif _init_static_threads(); #ifdef CONFIG_BOOT_TIME_MEASUREMENT /* record timestamp for kernel's _main() function */ extern u64_t __main_time_stamp; __main_time_stamp = (u64_t)k_cycle_get_32(); #endif extern void main(void); k_thread_priority_set(_main_thread, CONFIG_MAIN_THREAD_PRIORITY); main(); /* Terminate thread normally since it has no more work to do */ _main_thread->base.user_options &= ~K_ESSENTIAL; }
2. 中断响应
以CortexM0处理器为例,其异常列表如下:
异常类型 异常编号 描述 Reset 1 上电复位或系统复位 NMI 2 不可屏蔽中断 Hard fault 3 用于错误处理,系统检测到错误后被激活 SVCall 11 请求管理调用,在执行SVC指令被激活,主要用作操作系统 PendSV 14 可挂起服务(系统)调用 SysTick 15 系统节拍定时器异常,一般在OS种用作周期系统节拍异常 IRQ0-IRQ31 16-47 中断,可来自于外部,也可来自片上外设
查看linker.ld文件可以看到,Zephyr系统将上述异常对应的向量表分成了几部分,分别为exc_vector_table,IRQ_VECTOR_TABLE和SW_ISR_TABLE;(忽略openocd_dbg、kinetis_flash_config段,未使用):
_vector_start = .; KEEP(*(.exc_vector_table)) KEEP(*(".exc_vector_table.*")) KEEP(*(IRQ_VECTOR_TABLE)) KEEP(*(.openocd_dbg)) KEEP(*(".openocd_dbg.*")) /* Kinetis has to write 16 bytes at 0x400 */ SKIP_TO_KINETIS_FLASH_CONFIG KEEP(*(.kinetis_flash_config)) KEEP(*(".kinetis_flash_config.*")) #ifdef CONFIG_GEN_SW_ISR_TABLE KEEP(*(SW_ISR_TABLE)) #endif _vector_end = .;
第一部分,异常向量表exc_vector_table,位于(zephyr\arch\arm\core\cortex_m\vector_table.S),定义了1-15号异常对应的跳转PC地址:
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 .word __reserved .word __reserved .word __reserved .word __reserved .word __reserved .word __reserved .word __reserved .word __svc .word __reserved .word __reserved .word __pendsv #if defined(CONFIG_CORTEX_M_SYSTICK) .word _timer_int_handler #else .word __reserved #endif
第二部分,中断向量表IRQ_VECTOR_TABLE,定义了16-47号中断跳转PC地址,位于project\prj_XXX\temp\Isr_tables.c,这是一个正式编译前由gen_isr_tables.py Python脚本生成的文件;该向量表所有项都对应同一个函数即所有中断总入口,函数名为_isr_wrapper:
#define __irq_vector_table _GENERIC_SECTION(IRQ_VECTOR_TABLE) u32_t __irq_vector_table _irq_vector_table[34] = { 0xffc88cd, 0xffc88cd, 0xffc88cd, 0xffc88cd, 0xffc88cd, 0xffc88cd, 0xffc88cd, 0xffc88cd, 0xffc88cd, 0xffc88cd, 0xffc88cd, 0xffc88cd, 0xffc88cd, 0xffc88cd, 0xffc88cd, 0xffc88cd, 0xffc88cd, 0xffc88cd, 0xffc88cd, 0xffc88cd, 0xffc88cd, 0xffc88cd, 0xffc88cd, 0xffc88cd, 0xffc88cd, 0xffc88cd, 0xffc88cd, 0xffc88cd, 0xffc88cd, 0xffc88cd, 0xffc88cd, 0xffc88cd, 0xffc88cd, 0xffc88cd, };
第三部分,具体中断实现结构体SW_ISR_TABLE,同样位于project\prj_XXX\temp\Isr_tables.c,也是由Python脚本生成,该结构体由两项成员,分别为传入参数arg和中断入口isr:
#define __sw_isr_table _GENERIC_SECTION(SW_ISR_TABLE) struct _isr_table_entry { void *arg; void (*isr)(void *); }; struct _isr_table_entry __sw_isr_table _sw_isr_table[34] = { {(void *)0x10724, (void *)0xffb8479}, {(void *)0x10670, (void *)0xffb4b59}, {(void *)0x0, (void *)0xffc8759}, {(void *)0x1067c, (void *)0xffb4b59}, {(void *)0x0, (void *)0xffc8759}, {(void *)0x0, (void *)0xffb9661}, {(void *)0x0, (void *)0xffc8759}, {(void *)0x0, (void *)0xffc8759}, {(void *)0x10730, (void *)0xffb8aa9}, {(void *)0x10730, (void *)0xffb8a5d}, {(void *)0x106c4, (void *)0xffb52c9}, {(void *)0x2, (void *)0xffc6ad1}, {(void *)0x0, (void *)0xffc6ad1}, {(void *)0x1061c, (void *)0xffb66f5}, {(void *)0x10610, (void *)0xffb66f5}, {(void *)0x0, (void *)0xffc8759}, {(void *)0x0, (void *)0xffc8759}, {(void *)0x0, (void *)0xffc69e9}, {(void *)0x0, (void *)0xffc8759}, {(void *)0x10694, (void *)0xffb9b8b}, {(void *)0x0, (void *)0xffc72e1}, {(void *)0x106e8, (void *)0xffb6af3}, {(void *)0x0, (void *)0xffc8759}, {(void *)0x0, (void *)0xffc8759}, {(void *)0x0, (void *)0xffc8759}, {(void *)0x0, (void *)0xffc8759}, {(void *)0x0, (void *)0xffc8759}, {(void *)0x0, (void *)0xffc8759}, {(void *)0x10730, (void *)0xffb8959}, {(void *)0x0, (void *)0xffc8759}, {(void *)0x106b8, (void *)0xffb52c9}, {(void *)0x0, (void *)0xffc5e9d}, {(void *)0x0, (void *)0xffc8759}, {(void *)0x0, (void *)0xffc8759}, };
具体程序如何从_isr_wrapper总入口找到对应中断并进入对应中断入口,可以参考_isr_wrapper函数实现:
SECTION_FUNC(TEXT, _isr_wrapper) push {lr} /* lr is now the first item on the stack */ /* * All interrupts are disabled when handling idle wakeup. For tickless * idle, this ensures that the calculation and programming of the device * for the next timer deadline is not interrupted. For non-tickless idle, * this ensures that the clearing of the kernel idle state is not * interrupted. In each case, _sys_power_save_idle_exit is called with * interrupts disabled. */ cpsid i /* PRIMASK = 1 */ /* is this a wakeup from idle ? */ ldr r2, =_kernel /* requested idle duration, in ticks */ ldr r0, [r2, #_kernel_offset_to_idle] cmp r0, #0 beq _idle_state_cleared movs.n r1, #0 /* clear kernel idle state */ str r1, [r2, #_kernel_offset_to_idle] blx _sys_power_save_idle_exit _idle_state_cleared: cpsie i /* re-enable interrupts (PRIMASK = 0) */ mrs r0, IPSR /* get exception number */ ldr r1, =16 subs r0, r1 /* get IRQ number */ lsls r0, #3 /* table is 8-byte wide */ ldr r1, =_sw_isr_table add r1, r1, r0 /* table entry: ISRs must have their MSB set to stay * in thumb mode */ ldm r1!,{r0,r3} /* arg in r0, ISR in r3 */ blx r3 /* call ISR */ pop {r3} mov lr, r3 /* exception return is done in _IntExit() */ b _IntExit
中断定义与处理
中断定义接口如下:
IRQ_CONNECT(SI_TIM0_IRQ, CONFIG_TIMER_0_IRQ_PRI, timer_si_isr, DEVICE_GET(timer_si_0), 0);
中断号:SI_TIM0_IRQ
中断处理函数:timer_si_isr
定义完成后通过脚本解析,timer_si_isr的地址会被写入sw_isr_table;下面的中断处理路径,通过timer中断实现了系统tick的处理:
timer_si_isr
timer_systick_callback
_sys_clock_final_tick_announce
_sys_clock_tick_announce
_nano_sys_clock_tick_announce
handle_timeouts
_handle_expired_timeouts
_handle_one_expired_timeout
work_timeout