// 本文部分内容来自网络
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