• 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.0archarmcorecortex_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.0archarmcorecortex_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.0archarmcorecortex_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.0kernelinit.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.0includearcharmcortex_mscripts linker.ld)里的。

    第6行,调用_data_copy()函数,也是定义在zephyr-zephyr-v1.13.0kernelinit.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.0kernelinit.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.0archarmcoreirq_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.0archarminclude 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.0archarmincludecortex_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.0archarmincludecortex_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.0archarmcorefault.c:

    void _FaultInit(void)
    {
        SCB->CCR |= SCB_CCR_DIV_0_TRP_Msk;
    }

    使能硬件相关的出错,比如0作为除数的错误操作。

    回到kernel_arch_init()函数,第6行,调用_CpuIdleInit ()函数,定义在zephyr-zephyr-v1.13.0archarmcorecpu_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.0kerneldevice.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.0kernelinit.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.0kernelsched.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.0kernel hread.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.0archarmcore hread.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.0kernelincludekernel_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.0kernel hread.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.0kernelinclude imeout_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.0kernelinclude 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.0kernelinclude 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.0kernel 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.0kernel 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.0kernelinclude 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.0kernel 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.0kernel 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.0kernel 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.0kernel 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.0kernelinit.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.0kernelinit.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.0kernelinit.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.0archarminclude 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 	
    "
    22
    23        /* unlock interrupts */
    24        "movs %%r1, #0 
    	"
    25        "msr BASEPRI, %%r1 
    	"
    26
    27        /* branch to _thread_entry(_main, 0, 0, 0) */
    28        "mov %%r0, %1 
    	"
    29        "bx %2 	
    "
    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.0lib 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.0kernelinit.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) *****
    ");
    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.0kernel hread.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()函数了。

  • 相关阅读:
    ASP.NET(C#)图片加文字、图片水印
    CMake构建Visual Studio中MFC项目的Unicode问题
    用Visual Studio 2008(VS)编译WebKit的r63513
    此时学习中
    ASP.NET进阶——初学者的提高(长期)
    继续努力
    程序员阿士顿的故事
    iOS 深拷贝和浅拷贝
    Javascript中this的取值
    Lisp的本质(The Nature of Lisp)
  • 原文地址:https://www.cnblogs.com/lknlfy/p/10326162.html
Copyright © 2020-2023  润新知