一.寫在前面
最近對zephyr這個系統很感興趣,因此業餘有時間的時候都在研究它的源碼,而光看代碼不去動手這不是我的風格,於是乎在網上淘了一塊STM32F103C8T6的核心板和一塊NRF52832的最小系統板。由於zephyr支持很多種開發板,因此一行代碼都不用修改就直接可以在這兩塊板子上跑起來。
插播:
在Zephyr中,經常能看到_CONCAT,這個是在common.h有定義。具體實現是
/* concatenate the values of the arguments into one */
#define _DO_CONCAT(x, y) x ## y
#define _CONCAT(x, y) _DO_CONCAT(x, y)
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()函數了。