Zephyr学习(三)启动过程
一.写在前面
最近对zephyr这个系统很感兴趣,因此业余有时间的时候都在研究它的源码,而光看代码不去动手这不是我的风格,于是乎在网上淘了一块STM32F103C8T6的核心板和一块NRF52832的最小系统板。由于zephyr支持很多种开发板,因此一行代码都不用修改就直接可以在这两块板子上跑起来。
zepyhr是一个年轻的嵌入式实时系统,目前发展的很快,从源码里可以看到主要代码贡献者来自Wind River Systems、Intel和Nordic Semiconductor等。虽然zephyr是由C语言和汇编语言写成的,但是要完全掌握它还需要其他技能,比如python、cmake、dts等,而这几项技能恰恰是我之前都没怎么接触过的,所以在读到zephyr的编译过程这一块时有一种愕然止步的感觉,不过还好啦,不懂就学呗。
接下来我打算写一系列关于zephyr的随笔,涉及启动、线程、调度、驱动等,有时间的话还写一些网络协议相关的,比如蓝牙、thread等。
所使用的软、硬件平台如下:
软件:截止到目前为止最新的release版本(tag:v1.13.0)
硬件:STM32F103C8T6核心板(官方stm32_min_dev board),Cortex-M3(ARMv7-M)
二.启动过程分析
这篇随笔的目的就是要知道系统从上电到运行main()函数之前经历了什么过程。要知道启动过程,就得先从中断向量表入手,位于zephyr-zephyr-v1.13.0\arch\arm\core\cortex_m\ vector_table.S文件:
1 #include <board.h> 2 #include <toolchain.h> 3 #include <linker/sections.h> 4 #include <drivers/system_timer.h> 5 #include "vector_table.h" 6 7 _ASM_FILE_PROLOGUE 8 9 GDATA(_main_stack) 10 11SECTION_SUBSEC_FUNC(exc_vector_table,_vector_table_section,_vector_table) 12 13 /* 14 * setting the _very_ early boot on the main stack allows to use memset 15 * on the interrupt stack when CONFIG_INIT_STACKS is enabled before 16 * switching to the interrupt stack for the rest of the early boot 17 */ 18 .word _main_stack + CONFIG_MAIN_STACK_SIZE 19 20 .word __reset 21 .word __nmi 22 23 .word __hard_fault 24#if defined(CONFIG_ARMV6_M_ARMV8_M_BASELINE) 25 .word __reserved 26 .word __reserved 27 .word __reserved 28 .word __reserved 29 .word __reserved 30 .word __reserved 31 .word __reserved 32 .word __svc 33 .word __reserved 34#elif defined(CONFIG_ARMV7_M_ARMV8_M_MAINLINE) 35 .word __mpu_fault 36 .word __bus_fault 37 .word __usage_fault 38#if defined(CONFIG_ARM_SECURE_FIRMWARE) 39 .word __secure_fault 40#else 41 .word __reserved 42#endif /* CONFIG_ARM_SECURE_FIRMWARE */ 43 .word __reserved 44 .word __reserved 45 .word __reserved 46 .word __svc 47 .word __debug_monitor 48#else 49#error Unknown ARM architecture 50#endif /* CONFIG_ARMV6_M_ARMV8_M_BASELINE */ 51 .word __reserved 52 .word __pendsv 53#if defined(CONFIG_CORTEX_M_SYSTICK) 54 .word _timer_int_handler 55#else 56 .word __reserved 57#endif
第20行就是CPU上电(复位)后执行的第一个函数__reset(),定义在zephyr-zephyr-v1.13.0\arch\arm\core\cortex_m\ reset.S:
1 SECTION_SUBSEC_FUNC(TEXT,_reset_section,__reset) 2 3 /* 4 * The entry point is located at the __reset symbol, which 5 * is fetched by a XIP image playing the role of a bootloader, which jumps to 6 * it, not through the reset vector mechanism. Such bootloaders might want to 7 * search for a __start symbol instead, so create that alias here. 8 */ 9 SECTION_SUBSEC_FUNC(TEXT,_reset_section,__start) 10 11#if defined(CONFIG_PLATFORM_SPECIFIC_INIT) 12 bl _PlatformInit 13#endif 14 15 /* lock interrupts: will get unlocked when switch to main task */ 16 movs.n r0, #_EXC_IRQ_DEFAULT_PRIO 17 msr BASEPRI, r0 18 19#ifdef CONFIG_WDOG_INIT 20 /* board-specific watchdog initialization is necessary */ 21 bl _WdogInit 22#endif 23 24#ifdef CONFIG_INIT_STACKS 25 ldr r0, =_interrupt_stack 26 ldr r1, =0xaa 27 ldr r2, =CONFIG_ISR_STACK_SIZE 28 bl memset 29#endif 30 31 /* 32 * Set PSP and use it to boot without using MSP, so that it 33 * gets set to _interrupt_stack during initialisation. 34 */ 35 ldr r0, =_interrupt_stack 36 ldr r1, =CONFIG_ISR_STACK_SIZE 37 adds r0, r0, r1 38 msr PSP, r0 39 movs.n r0, #2 /* switch to using PSP (bit1 of CONTROL reg) */ 40 msr CONTROL, r0 41 /* 42 * When changing the stack pointer, software must use an ISB instruction 43 * immediately after the MSR instruction. This ensures that instructions 44 * after the ISB instruction execute using the new stack pointer. 45 */ 46 isb 47 48 b _PrepC
在这里说一下,凡是以CONFIG开头的宏都是可以通过ninja menuconfig命令来进行配置的,就像linux下的make menuconfig命令一样。
第12行,如果定义了平台相关初始化操作则调用_PlatformInit()函数,这里没有定义。
第16~17行,屏蔽所有可以配置优先级的中断,_EXC_IRQ_DEFAULT_PRIO在这里的值为16。4位优先级位,即可以配置16种优先级级别(0~15,值越小优先级越高)。
第21行,如果定义了看门狗初始化,则调用_WdogInit()函数,这里没有定义。
第25~28行,初始化栈的内容为0xaa,是通过调用memset()函数来初始化的。其中_interrupt_stack是数组名,CONFIG_ISR_STACK_SIZE是数组的大小。目前来看,zephyr的栈空间都是通过静态数组的方式定义的。
第35~37行,r0的值就指向栈的顶端(因为栈是满递减方式的)。
第38行,将r0的值设置到PSP。
第39~40行,由MSP切换到PSP。
第46行,SP切换后必须加isb指令(ARM手册里有说明)。
第48行,调用_PrepC()函数,这是一个C语言写的函数,定义在zephyr-zephyr-v1.13.0\arch\arm\core\cortex_m\ prep_c.c:
1 void _PrepC(void) 2 { 3 relocate_vector_table(); 4 enable_floating_point(); 5 _bss_zero(); 6 _data_copy(); 7 #ifdef CONFIG_BOOT_TIME_MEASUREMENT 8 __start_time_stamp = 0; 9 #endif 10 _Cstart(); 11 CODE_UNREACHABLE; 12}
第3行,中断向量表重定位,如下定义:
1 static inline void relocate_vector_table(void) 2 { 3 SCB->VTOR = VECTOR_ADDRESS & SCB_VTOR_TBLOFF_Msk; 4 __DSB(); 5 __ISB(); 6 }
其中VECTOR_ADDRESS的值根据是否定义了CONFIG_XIP不同而不同,如果定义了CONFIG_XIP,那么VECTOR_ADDRESS的值就位0(ROM的起始地址),如果没有定义CONFIG_XIP,那么VECTOR_ADDRESS的值就位RAM的起始地址。对于stm32_min_dev板子是定义了CONFIG_XIP的。
第4行,由于STM32F103C8xx不带浮点功能,所以enable_floating_point()函数实际上没有任何操作。
第5行,调用_bss_zero()函数,定义在zephyr-zephyr-v1.13.0\kernel\init.c:
1 void _bss_zero(void) 2 { 3 memset(&__bss_start, 0, 4 ((u32_t) &__bss_end - (u32_t) &__bss_start)); 5 #ifdef CONFIG_CCM_BASE_ADDRESS 6 memset(&__ccm_bss_start, 0, 7 ((u32_t) &__ccm_bss_end - (u32_t) &__ccm_bss_start)); 8 #endif 9 #ifdef CONFIG_APPLICATION_MEMORY 10 memset(&__app_bss_start, 0, 11 ((u32_t) &__app_bss_end - (u32_t) &__app_bss_start)); 12 #endif 13 }
即调用memset()函数,将全局未初始化变量清零。__bss_start、__bss_end是定义在链接脚本(zephyr-zephyr-v1.13.0\include\arch\arm\cortex_m\scripts\ linker.ld)里的。
第6行,调用_data_copy()函数,也是定义在zephyr-zephyr-v1.13.0\kernel\init.c:
1 void _data_copy(void) 2 { 3 (void)memcpy(&__data_ram_start, &__data_rom_start, 4 ((u32_t) &__data_ram_end - (u32_t) &__data_ram_start)); 5 }
即调用memcpy()函数,将全局已初始化变量从ROM拷贝到RAM里。__data_ram_start、__data_rom_start、__data_ram_end也是定义在链接脚本里的。
最后,第10行,调用_Cstart()函数,定义在zephyr-zephyr-v1.13.0\kernel\init.c:
1 FUNC_NORETURN void _Cstart(void) 2 { 3 struct k_thread *dummy_thread = NULL; 4 5 /* 6 * The interrupt library needs to be initialized early since a series 7 * of handlers are installed into the interrupt table to catch 8 * spurious interrupts. This must be performed before other kernel 9 * subsystems install bonafide handlers, or before hardware device 10 * drivers are initialized. 11 */ 12 13 _IntLibInit(); 14 15 /* perform any architecture-specific initialization */ 16 kernel_arch_init(); 17 18 /* perform basic hardware initialization */ 19 _sys_device_do_config_level(_SYS_INIT_LEVEL_PRE_KERNEL_1); 20 _sys_device_do_config_level(_SYS_INIT_LEVEL_PRE_KERNEL_2); 21 22 prepare_multithreading(dummy_thread); 23 switch_to_main_thread(); 24 25 /* 26 * Compiler can't tell that the above routines won't return and issues 27 * a warning unless we explicitly tell it that control never gets this 28 * far. 29 */ 30 31 CODE_UNREACHABLE; 32}
第13行,调用_IntLibInit()函数,定义在zephyr-zephyr-v1.13.0\arch\arm\core\irq_init.c:
1void _IntLibInit(void) 2{ 3 int irq = 0; 4 5 for (; irq < CONFIG_NUM_IRQS; irq++) { 6 NVIC_SetPriority((IRQn_Type)irq, _IRQ_PRIO_OFFSET); 7 } 8}
其中,第6行_IRQ_PRIO_OFFSET的值1。意思就是将所有中断的优先级设为1(复位后默认的优先级0)。
第16行,调用kernel_arch_init()函数,定义在zephyr-zephyr-v1.13.0\arch\arm\include\ kernel_arch_func.h:
1 static ALWAYS_INLINE void kernel_arch_init(void) 2 { 3 _InterruptStackSetup(); 4 _ExcSetup(); 5 _FaultInit(); 6 _CpuIdleInit(); 7 }
全都是函数调用。
第3行,调用_InterruptStackSetup()函数,定义在zephyr-zephyr-v1.13.0\arch\arm\include\cortex_m\ stack.h:
1 static ALWAYS_INLINE void _InterruptStackSetup(void) 2 { 3 u32_t msp = (u32_t)(K_THREAD_STACK_BUFFER(_interrupt_stack) + 4 CONFIG_ISR_STACK_SIZE); 5 6 __set_MSP(msp); 7 }
即重新设置MSP的值,还记得在__reset()函数里也是用同样的值设置了PSP吗?
第4行,调用_ExcSetup()函数,定义在zephyr-zephyr-v1.13.0\arch\arm\include\cortex_m\ exc.h:
1 static ALWAYS_INLINE void _ExcSetup(void) 2 { 3 NVIC_SetPriority(PendSV_IRQn, 0xff); 4 5 NVIC_SetPriority(SVCall_IRQn, _EXC_SVC_PRIO); 6 7 NVIC_SetPriority(MemoryManagement_IRQn, _EXC_FAULT_PRIO); 8 NVIC_SetPriority(BusFault_IRQn, _EXC_FAULT_PRIO); 9 NVIC_SetPriority(UsageFault_IRQn, _EXC_FAULT_PRIO); 10 11 /* Enable Usage, Mem, & Bus Faults */ 12 SCB->SHCSR |= SCB_SHCSR_USGFAULTENA_Msk | SCB_SHCSR_MEMFAULTENA_Msk | 13 SCB_SHCSR_BUSFAULTENA_Msk; 14}
设置各个异常的优先级,其中PendSV的优先级是最低的,SVCall、BusFault、UsageFault、MemoryManagement的优先级都为0。
第12~13行,使能BusFault、UsageFault、MemoryManagement异常中断。
回到kernel_arch_init()函数,第5行,调用_FaultInit()函数,定义在zephyr-zephyr-v1.13.0\arch\arm\core\fault.c:
void _FaultInit(void) { SCB->CCR |= SCB_CCR_DIV_0_TRP_Msk; }
使能硬件相关的出错,比如0作为除数的错误操作。
回到kernel_arch_init()函数,第6行,调用_CpuIdleInit ()函数,定义在zephyr-zephyr-v1.13.0\arch\arm\core\cpu_idle.S:
SECTION_FUNC(TEXT, _CpuIdleInit) ldr r1, =_SCB_SCR movs.n r2, #_SCR_INIT_BITS str r2, [r1] bx lr
将SCB_SCR寄存器的bit4置1,即当异常(中断)进入挂起状态后会被认为是一个WFE唤醒事件。
回到_Cstart()函数,第19~20行,都是调用_sys_device_do_config_level()函数,定义在zephyr-zephyr-v1.13.0\kernel\device.c:
1 extern struct device __device_init_start[]; 2 extern struct device __device_PRE_KERNEL_1_start[]; 3 extern struct device __device_PRE_KERNEL_2_start[]; 4 extern struct device __device_POST_KERNEL_start[]; 5 extern struct device __device_APPLICATION_start[]; 6 extern struct device __device_init_end[]; 7 8 static struct device *config_levels[] = { 9 __device_PRE_KERNEL_1_start, 10 __device_PRE_KERNEL_2_start, 11 __device_POST_KERNEL_start, 12 __device_APPLICATION_start, 13 /* End marker */ 14 __device_init_end, 15 }; 16 17 void _sys_device_do_config_level(int level) 18 { 19 struct device *info; 20 21 for (info = config_levels[level]; info < config_levels[level+1]; 22 info++) { 23 struct device_config *device = info->config; 24 25 device->init(info); 26 _k_object_init(info); 27 } 28 }
zephyr的设备(驱动)定义大部分都是使用DEVICE_AND_API_INIT这个宏,根据传入的参数,会把设备的结构体放入特定的段(section)里面,可以看到有四种类型(PRE_KERNEL_1、PRE_KERNEL_2、POST_KERNEL和APPLICATION),这也是系统跑起来时设备的初始化先后顺序。因此就可以知道,这里调用了PRE_KERNEL_1和PRE_KERNEL_2这两种类型的设备初始化函数。
回到_Cstart()函数,第22行,调用prepare_multithreading()函数,定义在zephyr-zephyr-v1.13.0\kernel\init.c:
1 static void prepare_multithreading(struct k_thread *dummy_thread) 2 { 3 ARG_UNUSED(dummy_thread); 4 5 /* _kernel.ready_q is all zeroes */ 6 _sched_init(); 7 8 _ready_q.cache = _main_thread; 9 10 _setup_new_thread(_main_thread, _main_stack, 11 MAIN_STACK_SIZE, bg_thread_main, 12 NULL, NULL, NULL, 13 CONFIG_MAIN_THREAD_PRIORITY, K_ESSENTIAL); 14 15 sys_trace_thread_create(_main_thread); 16 17 _mark_thread_as_started(_main_thread); 18 _ready_thread(_main_thread); 19 20 init_idle_thread(_idle_thread, _idle_stack); 21 _kernel.cpus[0].idle_thread = _idle_thread; 22 sys_trace_thread_create(_idle_thread); 23 24 initialize_timeouts(); 25 }
第6行,调用_sched_init()函数,初始化调度器,定义在zephyr-zephyr-v1.13.0\kernel\sched.c:
1 void _sched_init(void) 2 { 3 #ifdef CONFIG_SCHED_DUMB 4 sys_dlist_init(&_kernel.ready_q.runq); 5 #endif 6 7 #ifdef CONFIG_SCHED_SCALABLE 8 _kernel.ready_q.runq = (struct _priq_rb) { 9 .tree = { 10 .lessthan_fn = _priq_rb_lessthan, 11 } 12 }; 13 #endif 14 15 #ifdef CONFIG_SCHED_MULTIQ 16 for (int i = 0; i < ARRAY_SIZE(_kernel.ready_q.runq.queues); i++) { 17 sys_dlist_init(&_kernel.ready_q.runq.queues[i]); 18 } 19 #endif 20 21 #ifdef CONFIG_TIMESLICING 22 k_sched_time_slice_set(CONFIG_TIMESLICE_SIZE, 23 CONFIG_TIMESLICE_PRIORITY); 24 #endif 25 }
zephyr支持三种调度算法,分别是SCHED_DUMB(默认),即一个双向链表,SCHED_SCALABLE,即红黑树,SCHED_MULTIQ,即多队列。建议当线程数小于等于3时使用SCHED_DUMB,当线程数大于20时使用SCHED_SCALABLE。SCHED_SCALABLE的代码要比SCHED_DUMB多2K字节左右。SCHED_SCALABLE、SCHED_MULTIQ的速度都要比SCHED_DUMB快。另外,SCHED_MULTIQ是按优先级来存取的,目前最大只支持32个优先级。
由于默认是使用SCHED_DUMB,所以只关心第4行代码,就是初始化一个双向链表。zephyr中定义了很多结构体,如果全部拿出来分析的话,那篇幅就太大了,感兴趣的同学可以深入去学习,这里只是分析主要流程。
上面代码的第21~24行,时间片的初始化,当配置了时间片的值(大于0),并且线程的优先低于CONFIG_TIMESLICE_PRIORITY时,线程就会参与时间片轮转,即创建线程时会给它分配一个时间片,当时间片用完就把线程放到运行队列的最后。
回到prepare_multithreading()函数,第8行,_ready_q.cache永远指向下一个要投入运行的线程,这里是main线程。
第10~13行,调用_setup_new_thread()函数,每次创建线程时都会调用这个函数,定义在zephyr-zephyr-v1.13.0\kernel\thread.c:
1 void _setup_new_thread(struct k_thread *new_thread, 2 k_thread_stack_t *stack, size_t stack_size, 3 k_thread_entry_t entry, 4 void *p1, void *p2, void *p3, 5 int prio, u32_t options) 6 { 7 stack_size = adjust_stack_size(stack_size); 8 9 _new_thread(new_thread, stack, stack_size, entry, p1, p2, p3, 10 prio, options); 11 12 /* _current may be null if the dummy thread is not used */ 13 if (!_current) { 14 new_thread->resource_pool = NULL; 15 return; 16 } 17 18 new_thread->resource_pool = _current->resource_pool; 19 sys_trace_thread_create(new_thread); 20 }
第9~10行,调用_new_thread()函数,定义在zephyr-zephyr-v1.13.0\arch\arm\core\thread.c:
1 void _new_thread(struct k_thread *thread, k_thread_stack_t *stack, 2 size_t stackSize, k_thread_entry_t pEntry, 3 void *parameter1, void *parameter2, void *parameter3, 4 int priority, unsigned int options) 5 { 6 char *pStackMem = K_THREAD_STACK_BUFFER(stack); 7 8 _ASSERT_VALID_PRIO(priority, pEntry); 9 10 char *stackEnd = pStackMem + stackSize; 11 12 struct __esf *pInitCtx; 13 14 _new_thread_init(thread, pStackMem, stackEnd - pStackMem, priority, 15 options); 16 17 /* carve the thread entry struct from the "base" of the stack */ 18 pInitCtx = (struct __esf *)(STACK_ROUND_DOWN(stackEnd - 19 sizeof(struct __esf))); 20 21 pInitCtx->pc = (u32_t)_thread_entry; 22 23 /* force ARM mode by clearing LSB of address */ 24 pInitCtx->pc &= 0xfffffffe; 25 26 pInitCtx->a1 = (u32_t)pEntry; 27 pInitCtx->a2 = (u32_t)parameter1; 28 pInitCtx->a3 = (u32_t)parameter2; 29 pInitCtx->a4 = (u32_t)parameter3; 30 pInitCtx->xpsr = 31 0x01000000UL; /* clear all, thumb bit is 1, even if RO */ 32 33 thread->callee_saved.psp = (u32_t)pInitCtx; 34 thread->arch.basepri = 0; 35 36 /* swap_return_value can contain garbage */ 37 38 /* 39 * initial values in all other registers/thread entries are 40 * irrelevant. 41 */ 42}
第6行,得到线程栈的起始地址。
第10行,指向线程栈的最高地址。
第14~15行,调用_new_thread_init()函数,定义在zephyr-zephyr-v1.13.0\kernel\include\kernel_structs.h:
1 static ALWAYS_INLINE void _new_thread_init(struct k_thread *thread, 2 char *pStack, size_t stackSize, 3 int prio, unsigned int options) 4 { 5 ARG_UNUSED(pStack); 6 ARG_UNUSED(stackSize); 7 8 /* Initialize various struct k_thread members */ 9 _init_thread_base(&thread->base, prio, _THREAD_PRESTART, options); 10 11 /* static threads overwrite it afterwards with real value */ 12 thread->init_data = NULL; 13 thread->fn_abort = NULL; 14}
第9行,调用_init_thread_base()函数,定义在zephyr-zephyr-v1.13.0\kernel\thread.c:
1 void _init_thread_base(struct _thread_base *thread_base, int priority, 2 u32_t initial_state, unsigned int options) 3 { 4 /* k_q_node is initialized upon first insertion in a list */ 5 6 thread_base->user_options = (u8_t)options; 7 thread_base->thread_state = (u8_t)initial_state; 8 9 thread_base->prio = priority; 10 11 thread_base->sched_locked = 0; 12 13 /* swap_data does not need to be initialized */ 14 15 _init_thread_timeout(thread_base); 16}
第7行,设置线程的状态,有以下这些类型:
/* Not a real thread */ #define _THREAD_DUMMY (BIT(0)) /* Thread is waiting on an object */ #define _THREAD_PENDING (BIT(1)) /* Thread has not yet started */ #define _THREAD_PRESTART (BIT(2)) /* Thread has terminated */ #define _THREAD_DEAD (BIT(3)) /* Thread is suspended */ #define _THREAD_SUSPENDED (BIT(4)) /* Thread is present in the ready queue */ #define _THREAD_QUEUED (BIT(6))
第9行,设置线程的优先级。
第15行,调用_init_thread_timeout()函数,定义在zephyr-zephyr-v1.13.0\kernel\include\timeout_q.h:
1 static inline void _init_timeout(struct _timeout *t, _timeout_func_t func) 2 { 3 /* 4 * Must be initialized here and when dequeueing a timeout so that code 5 * not dealing with timeouts does not have to handle this, such as when 6 * waiting forever on a semaphore. 7 */ 8 t->delta_ticks_from_prev = _INACTIVE; 9 10 /* 11 * Must be initialized here so that k_wakeup can 12 * verify the thread is not on a wait queue before aborting a timeout. 13 */ 14 t->wait_q = NULL; 15 16 /* 17 * Must be initialized here, so the _handle_one_timeout() 18 * routine can check if there is a thread waiting on this timeout 19 */ 20 t->thread = NULL; 21 22 /* 23 * Function must be initialized before being potentially called. 24 */ 25 t->func = func; 26 27 /* 28 * These are initialized when enqueing on the timeout queue: 29 * 30 * thread->timeout.node.next 31 * thread->timeout.node.prev 32 */ 33 } 34 35 static ALWAYS_INLINE void 36 _init_thread_timeout(struct _thread_base *thread_base) 37 { 38 _init_timeout(&thread_base->timeout, NULL); 39 }
回到_new_thread()函数,第18~19行,由于CortexM系列的栈是以满递减方式增长的,所以这里将栈顶地址进行8字节向下对齐。
第21行,PC指向_thread_entry()函数入口地址,线程运行时不是直接调用线程函数的,而是调用_thread_entry()函数,再通过_thread_entry()函数调用真正的线程函数。
第24行,以ARM模式运行_thread_entry()函数。
第26~29行,线程入口函数和参数,这里只支持最多3个线程参数。
第30~31行,thumb位必须为1。
第33行,保存栈顶地址。
好了,可以回到prepare_multithreading()函数了,第17行,调用_mark_thread_as_started()函数,定义在zephyr-zephyr-v1.13.0\kernel\include\ ksched.h:
static inline void _mark_thread_as_started(struct k_thread *thread) { thread->base.thread_state &= ~_THREAD_PRESTART; }
刚才创建线程时把线程状态设为了_THREAD_PRESTART,这里就把它清掉了。
回到prepare_multithreading()函数了,第18行,调用_ready_thread()函数,定义在zephyr-zephyr-v1.13.0\kernel\include\ ksched.h:
1 static inline int _is_thread_prevented_from_running(struct k_thread *thread) 2 { 3 u8_t state = thread->base.thread_state; 4 5 return state & (_THREAD_PENDING | _THREAD_PRESTART | _THREAD_DEAD | 6 _THREAD_DUMMY | _THREAD_SUSPENDED); 7 8 } 9 10static inline int _is_thread_timeout_active(struct k_thread *thread) 11{ 12 return thread->base.timeout.delta_ticks_from_prev != _INACTIVE; 13} 14 15static inline int _is_thread_ready(struct k_thread *thread) 16{ 17 return !(_is_thread_prevented_from_running(thread) || 18 _is_thread_timeout_active(thread)); 19} 20 21static inline void _ready_thread(struct k_thread *thread) 22{ 23 if (_is_thread_ready(thread)) { 24 _add_thread_to_ready_q(thread); 25 } 26 27 sys_trace_thread_ready(thread); 28}
第23行,调用_is_thread_ready()函数,从前面的分析可以知道,_is_thread_prevented_from_running()函数返回值为0,而_is_thread_timeout_active()函数的返回值1,因此第23行的if条件成立,调用第24行的_add_thread_to_ready_q()函数,定义在
zephyr-zephyr-v1.13.0\kernel\ sched.c:
1 void _add_thread_to_ready_q(struct k_thread *thread) 2 { 3 LOCKED(&sched_lock) { 4 _priq_run_add(&_kernel.ready_q.runq, thread); 5 _mark_thread_as_queued(thread); 6 update_cache(0); 7 } 8 }
第4行,调用_priq_run_add()函数,对于使用SCHED_DUMB调度算法,实际上调用的是_priq_dumb_add()函数,定义在zephyr-zephyr-v1.13.0\kernel\ sched.c:
1 void _priq_dumb_add(sys_dlist_t *pq, struct k_thread *thread) 2 { 3 struct k_thread *t; 4 5 __ASSERT_NO_MSG(!_is_idle(thread)); 6 7 SYS_DLIST_FOR_EACH_CONTAINER(pq, t, base.qnode_dlist) { 8 if (_is_t1_higher_prio_than_t2(thread, t)) { 9 sys_dlist_insert_before(pq, &t->base.qnode_dlist, 10 &thread->base.qnode_dlist); 11 return; 12 } 13 } 14 15 sys_dlist_append(pq, &thread->base.qnode_dlist); 16 }
即遍历就绪队列链表,将当前线程按优先级由高到低插入到该链表中。
回到_add_thread_to_ready_q()函数,第5行,调用_mark_thread_as_queued()函数,定义在
zephyr-zephyr-v1.13.0\kernel\include\ ksched.h:
1 static inline void _set_thread_states(struct k_thread *thread, u32_t states) 2 { 3 thread->base.thread_state |= states; 4 } 5 6 static inline void _mark_thread_as_queued(struct k_thread *thread) 7 { 8 _set_thread_states(thread, _THREAD_QUEUED); 9 }
即设置线程的状态为_THREAD_QUEUED。
回到_add_thread_to_ready_q()函数,第6行,调用update_cache ()函数,定义在zephyr-zephyr-v1.13.0\kernel\ sched.c:
1 static void update_cache(int preempt_ok) 2 { 3 struct k_thread *th = next_up(); 4 5 if (should_preempt(th, preempt_ok)) { 6 _kernel.ready_q.cache = th; 7 } else { 8 _kernel.ready_q.cache = _current; 9 } 10 }
第3行,调用next_up()函数,定义在zephyr-zephyr-v1.13.0\kernel\ sched.c:
1 static struct k_thread *next_up(void) 2 { 3 struct k_thread *th = _priq_run_best(&_kernel.ready_q.runq); 4 5 return th ? th : _current_cpu->idle_thread; 6 }
第3行,调用_priq_run_best()函数,实际上调用的是_priq_dumb_best()函数,定义在zephyr-zephyr-v1.13.0\kernel\ sched.c:
struct k_thread *_priq_dumb_best(sys_dlist_t *pq) { return CONTAINER_OF(sys_dlist_peek_head(pq), struct k_thread, base.qnode_dlist); }
调用sys_dlist_peek_head()函数得到就绪队列的头节点,也即得到优先级最高的线程。
next_up()函数的第5行,前面已经将main线程加入到就绪队列里了,因此返回的就是main线程,而不是空闲线程,更可况空闲线程还没进行初始化(创建)呢。
回到update_cache()函数,第5行,调用should_preempt()函数,定义在zephyr-zephyr-v1.13.0\kernel\ sched.c:
1 static int should_preempt(struct k_thread *th, int preempt_ok) 2 { 3 /* Preemption is OK if it's being explicitly allowed by 4 * software state (e.g. the thread called k_yield()) 5 */ 6 if (preempt_ok) { 7 return 1; 8 } 9 10 /* Or if we're pended/suspended/dummy (duh) */ 11 if (!_current || !_is_thread_ready(_current)) { 12 return 1; 13 } 14 15 /* Otherwise we have to be running a preemptible thread or 16 * switching to a metairq 17 */ 18 if (_is_preempt(_current) || is_metairq(th)) { 19 return 1; 20 } 21 22 /* The idle threads can look "cooperative" if there are no 23 * preemptible priorities (this is sort of an API glitch). 24 * They must always be preemptible. 25 */ 26 if (_is_idle(_current)) { 27 return 1; 28 } 29 30 return 0; 31 }
第6行,因为传进来的preempt_ok的值为0,所以if条件不成立。
第11行,到目前为止,_current的值没有被初始化过,所以if条件成立,返回1。_current指向当前线程。
回到update_cache()函数,第6行,_kernel.ready_q.cache就指向了main线程。
好了,回到prepare_multithreading()函数,第20行,调用init_idle_thread()函数,定义在zephyr-zephyr-v1.13.0\kernel\init.c:
1 static void init_idle_thread(struct k_thread *thr, k_thread_stack_t *stack) 2 { 3 _setup_new_thread(thr, stack, 4 IDLE_STACK_SIZE, idle, NULL, NULL, NULL, 5 K_LOWEST_THREAD_PRIO, K_ESSENTIAL); 6 _mark_thread_as_started(thr); 7 }
里面调用的这两个函数前面已经分析过了。
prepare_multithreading()函数的第24行,调用initialize_timeouts()函数,定义在zephyr-zephyr-v1.13.0\kernel\init.c:
#define initialize_timeouts() do { \ sys_dlist_init(&_timeout_q); \ } while ((0))
即初始化_timeout_q这个双向链表。
到这里,prepare_multithreading()函数也分析完了,回到_Cstart()函数,第23行,调用switch_to_main_thread()函数,定义在zephyr-zephyr-v1.13.0\kernel\init.c:
static void switch_to_main_thread(void) { _arch_switch_to_main_thread(_main_thread, _main_stack, MAIN_STACK_SIZE, bg_thread_main); }
调用_arch_switch_to_main_thread()函数,定义在zephyr-zephyr-v1.13.0\arch\arm\include\ kernel_arch_func.h:
1 static ALWAYS_INLINE void 2 _arch_switch_to_main_thread(struct k_thread *main_thread, 3 k_thread_stack_t *main_stack, 4 size_t main_stack_size, k_thread_entry_t _main) 5 { 6 /* get high address of the stack, i.e. its start (stack grows down) */ 7 char *start_of_main_stack; 8 9 start_of_main_stack = 10 K_THREAD_STACK_BUFFER(main_stack) + main_stack_size; 11 12 start_of_main_stack = (void *)STACK_ROUND_DOWN(start_of_main_stack); 13 14 _current = main_thread; 15 16 /* the ready queue cache already contains the main thread */ 17 18 __asm__ __volatile__( 19 20 /* move to main() thread stack */ 21 "msr PSP, %0 \t\n" 22 23 /* unlock interrupts */ 24 "movs %%r1, #0 \n\t" 25 "msr BASEPRI, %%r1 \n\t" 26 27 /* branch to _thread_entry(_main, 0, 0, 0) */ 28 "mov %%r0, %1 \n\t" 29 "bx %2 \t\n" 30 31 /* never gets here */ 32 33 : 34 : "r"(start_of_main_stack), 35 "r"(_main), "r"(_thread_entry), 36 "r"(main_thread) 37 38 : "r0", "r1", "sp" 39 ); 40 41 CODE_UNREACHABLE; 42}
第9~12行,得到main线程的栈顶地址(8字节向下对齐后的)。
第14行,_current指向main线程。
第18行,通过C语言内嵌汇编,设置PSP的值为start_of_main_stack,设置BASEPRI寄存器的值为0,最后调用_thread_entry()函数,第一个参数为_main。
接下来看一下_thread_entry()函数,定义在zephyr-zephyr-v1.13.0\lib\ thread_entry.c:
1 FUNC_NORETURN void _thread_entry(k_thread_entry_t entry, 2 void *p1, void *p2, void *p3) 3 { 4 entry(p1, p2, p3); 5 6 k_thread_abort(k_current_get()); 7 8 /* 9 * Compiler can't tell that k_thread_abort() won't return and issues a 10 * warning unless we tell it that control never gets this far. 11 */ 12 13 CODE_UNREACHABLE; 14}
第4行,实际上调用的是bg_thread_main()函数,定义在zephyr-zephyr-v1.13.0\kernel\init.c:
1 static void bg_thread_main(void *unused1, void *unused2, void *unused3) 2 { 3 ARG_UNUSED(unused1); 4 ARG_UNUSED(unused2); 5 ARG_UNUSED(unused3); 6 7 _sys_device_do_config_level(_SYS_INIT_LEVEL_POST_KERNEL); 8 9 if (boot_delay > 0) { 10 printk("***** delaying boot " STRINGIFY(CONFIG_BOOT_DELAY) 11 "ms (per build configuration) *****\n"); 12 k_busy_wait(CONFIG_BOOT_DELAY * USEC_PER_MSEC); 13 } 14 PRINT_BOOT_BANNER(); 15 16 /* Final init level before app starts */ 17 _sys_device_do_config_level(_SYS_INIT_LEVEL_APPLICATION); 18 19 _init_static_threads(); 20 21 extern void main(void); 22 23 main(); 24 25 /* Terminate thread normally since it has no more work to do */ 26 _main_thread->base.user_options &= ~K_ESSENTIAL; 27 }
第7行,初始化POST_KERNEL类别的设备,前面已经分析过类似的了。
第9行,如果配置了启动延时,则调用k_busy_wait()函数,这是一个忙等待函数,会一致占用着CPU。
第14行,打印启动“横幅”。即打印出前面开发环境搭建随笔里hello world之前一行的打印。
第17行,初始化APPLICATION类别的设备,前面已经分析过类似的了。
第19行,调用_init_static_threads()函数,定义在zephyr-zephyr-v1.13.0\kernel\thread.c:
1 void _init_static_threads(void) 2 { 3 unsigned int key; 4 5 _FOREACH_STATIC_THREAD(thread_data) { 6 _setup_new_thread( 7 thread_data->init_thread, 8 thread_data->init_stack, 9 thread_data->init_stack_size, 10 thread_data->init_entry, 11 thread_data->init_p1, 12 thread_data->init_p2, 13 thread_data->init_p3, 14 thread_data->init_prio, 15 thread_data->init_options); 16 17 thread_data->init_thread->init_data = thread_data; 18 } 19 20 _sched_lock(); 21 22 /* 23 * Non-legacy static threads may be started immediately or after a 24 * previously specified delay. Even though the scheduler is locked, 25 * ticks can still be delivered and processed. Lock interrupts so 26 * that the countdown until execution begins from the same tick. 27 * 28 * Note that static threads defined using the legacy API have a 29 * delay of K_FOREVER. 30 */ 31 key = irq_lock(); 32 _FOREACH_STATIC_THREAD(thread_data) { 33 if (thread_data->init_delay != K_FOREVER) { 34 schedule_new_thread(thread_data->init_thread, 35 thread_data->init_delay); 36 } 37 } 38 irq_unlock(key); 39 k_sched_unlock(); 40}
zephyr支持两种创建线程的方式,分别是静态创建和动态创建,静态创建使用K_THREAD_DEFINE宏,动态创建则调用k_thread_create()函数。
第5行,遍历所有静态创建的线程,调用_setup_new_thread()函数。
第32行,遍历所有静态创建的线程,如果创建的静态线程延时不为K_FOREVER(也即线程需要延时一段时间之后才参与调度),那么就将该线程加入到超时队列里,具体过程将在后面的随笔里再分析,敬请期待。
最后就是调用我们最熟悉的main()函数了。