zoukankan      html  css  js  c++  java
  • Zephyr学习(五)线程和调度

    前面说过zephyr支持静态和动态两种方式创建线程,这里分析动态创建的方式。应用程序通过调用k_thread_create()函数创建一个线程,实际上是调用_impl_k_thread_create()函数,定义在zephyr-zephyr-v1.13.0kernel hread.c:

    1  k_tid_t _impl_k_thread_create(struct k_thread *new_thread,
    2                    k_thread_stack_t *stack,
    3                    size_t stack_size, k_thread_entry_t entry,
    4                    void *p1, void *p2, void *p3,
    5                    int prio, u32_t options, s32_t delay)
    6  {
    7      __ASSERT(!_is_in_isr(), "Threads may not be created in ISRs");
    8  
    9      _setup_new_thread(new_thread, stack, stack_size, entry, p1, p2, p3,
    10               prio, options);
    11 
    12     if (delay != K_FOREVER) {
    13         schedule_new_thread(new_thread, delay);
    14     }
    15 
    16     return new_thread;
    17 }

    第9行,调用_setup_new_thread()函数,在开发环境搭建里已经分析过了。

    第12行,传进来的最后一个参数一般为K_NO_WAIT,即马上参与调度,所以if条件成立。

    第13行,调用schedule_new_thread()函数,定义在zephyr-zephyr-v1.13.0kernel hread.c:

    1  static void schedule_new_thread(struct k_thread *thread, s32_t delay)
    2  {
    3      if (delay == 0) {
    4          k_thread_start(thread);
    5      } else {
    6          s32_t ticks = _TICK_ALIGN + _ms_to_ticks(delay);
    7          int key = irq_lock();
    8  
    9          _add_thread_timeout(thread, NULL, ticks);
    10         irq_unlock(key);
    11     }
    12 }

    第3行,由于K_NO_WAIT的值就为0,所以if条件成立。

    第4行,调用k_thread_start()函数,定义在zephyr-zephyr-v1.13.0kernel hread.c:

    1  void _impl_k_thread_start(struct k_thread *thread)
    2  {
    3      int key = irq_lock(); /* protect kernel queues */
    4  
    5      if (_has_thread_started(thread)) {
    6          irq_unlock(key);
    7          return;
    8      }
    9  
    10     _mark_thread_as_started(thread);
    11     _ready_thread(thread);
    12     _reschedule(key);
    13 }

    第5行,判断线程的状态是否不为_THREAD_PRESTART,如果是则直接返回。

    第10行,清除线程的_THREAD_PRESTART状态。

    第11行,前面已经分析过了。

    第12行,调用_reschedule()函数,定义在zephyr-zephyr-v1.13.0kernelsched.c:

    1  int _reschedule(int key)
    2  {
    3      if (_is_in_isr()) {
    4          goto noswap;
    5      }
    6  
    7      if (_get_next_ready_thread() != _current) {
    8          return _Swap(key);
    9      }
    10 
    11  noswap:
    12     irq_unlock(key);
    13     return 0;
    14 }

    第3行,调用_is_in_isr()函数,判断是否处于中断上下文,在中断里是不允许线程切换的,定义在archarmincludekernel_arch_func.h:

    #define _is_in_isr() _IsInIsr()

    实际上调用的是_IsInIsr()函数,定义在zephyr-zephyr-v1.13.0archarmincludecortex_mexc.h:

    1 static ALWAYS_INLINE int _IsInIsr(void)
    2 {
    3     u32_t vector = __get_IPSR();
    4 
    5     /* IRQs + PendSV (14) + SYSTICK (15) are interrupts. */
    6     return (vector > 13) || (vector && !(SCB->ICSR & SCB_ICSR_RETTOBASE_Msk));
    7 }

    即IPSR的值(当前中断号)大于13则认为是处于中断上下文。

    回到_reschedule()函数,第7行,调用_get_next_ready_thread()函数,定义在zephyr-zephyr-v1.13.0kernelincludeksched.h:

    static ALWAYS_INLINE struct k_thread *_get_next_ready_thread(void)
    {
        return _ready_q.cache;
    }

    前面也说过,_ready_q.cache始终指向的是下一个要投入运行的线程。

    所以,如果当前线程不是下一个要投入的线程,那么第8行,调用_Swap()函数,定义在zephyr-zephyr-v1.13.0kernelincludekswap.h:

    1 static inline unsigned int _Swap(unsigned int key)
    2 {
    3     unsigned int ret;
    4     _update_time_slice_before_swap();
    5 
    6     ret = __swap(key);
    7 
    8     return ret;
    9}

    第4行,调用_update_time_slice_before_swap()函数,定义在zephyr-zephyr-v1.13.0kernelsched.c:

    void _update_time_slice_before_swap(void)
    {
        /* Restart time slice count at new thread switch */
        _time_slice_elapsed = 0;
    }

    即将时间片清0,重新开始累加。

    回到_Swap()函数,第6行,调用__swap()函数,定义在zephyr-zephyr-v1.13.0archarmcoreswap.c:

    1 unsigned int __swap(int key)
    2 {
    3     /* store off key and return value */
    4     _current->arch.basepri = key;
    5     _current->arch.swap_return_value = _k_neg_eagain;
    6 
    7     /* set pending bit to make sure we will take a PendSV exception */
    8     SCB->ICSR |= SCB_ICSR_PENDSVSET_Msk;
    9 
    10    /* clear mask or enable all irqs to take a pendsv */
    11    irq_unlock(0);
    12
    13    return _current->arch.swap_return_value;
    14}

    第4~5行,保存key和返回值。

    第8行,置位pendsv中断。

    第11行,使能中断,此时就会产生pendsv中断。

    下面分析pendsv中断的处理流程,定义在zephyr-zephyr-v1.13.0archarmcoreswap_helper.S:

    1  SECTION_FUNC(TEXT, __pendsv)
    2  
    3      /* protect the kernel state while we play with the thread lists */
    4  
    5      movs.n r0, #_EXC_IRQ_DEFAULT_PRIO
    6      msr BASEPRI, r0
    7  
    8      /* load _kernel into r1 and current k_thread into r2 */
    9      ldr r1, =_kernel
    10     ldr r2, [r1, #_kernel_offset_to_current]
    11 
    12     /* addr of callee-saved regs in thread in r0 */
    13     ldr r0, =_thread_offset_to_callee_saved
    14     add r0, r2
    15 
    16     /* save callee-saved + psp in thread */
    17     mrs ip, PSP
    18 
    19     stmia r0, {v1-v8, ip}
    20 
    21     /*
    22      * Prepare to clear PendSV with interrupts unlocked, but
    23      * don't clear it yet. PendSV must not be cleared until
    24      * the new thread is context-switched in since all decisions
    25      * to pend PendSV have been taken with the current kernel
    26      * state and this is what we're handling currently.
    27      */
    28     ldr v4, =_SCS_ICSR
    29     ldr v3, =_SCS_ICSR_UNPENDSV
    30 
    31     /* _kernel is still in r1 */
    32 
    33     /* fetch the thread to run from the ready queue cache */
    34     ldr r2, [r1, _kernel_offset_to_ready_q_cache]
    35 
    36     str r2, [r1, #_kernel_offset_to_current]
    37 
    38     /*
    39      * Clear PendSV so that if another interrupt comes in and
    40      * decides, with the new kernel state baseed on the new thread
    41      * being context-switched in, that it needs to reschedules, it
    42      * will take, but that previously pended PendSVs do not take,
    43      * since they were based on the previous kernel state and this
    44      * has been handled.
    45      */
    46 
    47     /* _SCS_ICSR is still in v4 and _SCS_ICSR_UNPENDSV in v3 */
    48     str v3, [v4, #0]
    49 
    50     /* Restore previous interrupt disable state (irq_lock key) */
    51     ldr r0, [r2, #_thread_offset_to_basepri]
    52     movs.n r3, #0
    53     str r3, [r2, #_thread_offset_to_basepri]
    54 
    55     /* restore BASEPRI for the incoming thread */
    56     msr BASEPRI, r0
    57 
    58     /* load callee-saved + psp from thread */
    59     add r0, r2, #_thread_offset_to_callee_saved
    60     ldmia r0, {v1-v8, ip}
    61 
    62     msr PSP, ip
    63 
    64     /* exc return */
    65     bx lr

    CortexM进入中断时,CPU会自动将8个寄存器(XPSR、PC、LR、R12、R3、R2、R1、R0)压栈。

    第5~6行,相当于调用irq_lock()函数。

    第9~19行的作用就是将剩下的其他寄存器(R4、R5、R6、R7、R8、R9、R10、R11、PSP)也压栈。

    第28~29行,准备清pendsv中断标志。

    第34~36行,r2指向下一个要投入运行的线程,其中第36行,将_current指向要投入运行的线程。

    第48行,清pendsv中断标志。(不清也可以?)

    第51~53行,清要投入运行线程的basepri变量的值。

    第56行,恢复BASEPRI寄存器的值。

    第59~62行,恢复R4、R5、R6、R7、R8、R9、R10、R11、PSP寄存器。

    第65行,中断返回,自动将XPSR、PC、LR、R12、R3、R2、R1、R0寄存器弹出,即切换到下一个线程。

    应用程序也可以调用k_yield()函数主动让出CPU,定义在zephyr-zephyr-v1.13.0kernelsched.c:

    1  void _impl_k_yield(void)
    2  {
    3      __ASSERT(!_is_in_isr(), "");
    4  
    5      if (!_is_idle(_current)) {
    6          LOCKED(&sched_lock) {
    7              _priq_run_remove(&_kernel.ready_q.runq, _current);
    8              _priq_run_add(&_kernel.ready_q.runq, _current);
    9              update_cache(1);
    10         }
    11     }
    12 
    13     if (_get_next_ready_thread() != _current) {
    14         _Swap(irq_lock());
    15     }
    16 }

    里面的函数都已经分析过了,这里不再重复。

    要成功将自己切换出去(让出CPU)的前提是有优先级比自己更高的并且已经就绪的线程。

    接下来看一下线程的取消过程。应用程序调用k_thread_cancel()函数取消一个线程,定义在

    zephyr-zephyr-v1.13.0kernel hread.c:

    1  int _impl_k_thread_cancel(k_tid_t tid)
    2  {
    3      struct k_thread *thread = tid;
    4  
    5      unsigned int key = irq_lock();
    6  
    7      if (_has_thread_started(thread) ||
    8          !_is_thread_timeout_active(thread)) {
    9          irq_unlock(key);
    10         return -EINVAL;
    11     }
    12 
    13     _abort_thread_timeout(thread);
    14     _thread_monitor_exit(thread);
    15 
    16     irq_unlock(key);
    17 
    18     return 0;
    19 }

    第7~8行,如果线程都没开始运行过,则返回出错。如果线程不是在等待(延时或者休眠),也返回出错,即线程不能自己取消自己。

    第13行,调用_abort_thread_timeout()函数,定义在zephyr-zephyr-v1.13.0kernelinclude imeout_q.h:

    static inline int _abort_thread_timeout(struct k_thread *thread)
    {
        return _abort_timeout(&thread->base.timeout);
    }

    实际上调用的是_abort_timeout()函数,定义在zephyr-zephyr-v1.13.0kernelinclude imeout_q.h:

    1  static inline int _abort_timeout(struct _timeout *timeout)
    2  {
    3      if (timeout->delta_ticks_from_prev == _INACTIVE) {
    4          return _INACTIVE;
    5      }
    6  
    7      if (!sys_dlist_is_tail(&_timeout_q, &timeout->node)) {
    8          sys_dnode_t *next_node =
    9              sys_dlist_peek_next(&_timeout_q, &timeout->node);
    10         struct _timeout *next = (struct _timeout *)next_node;
    11 
    12         next->delta_ticks_from_prev += timeout->delta_ticks_from_prev;
    13     }
    14     sys_dlist_remove(&timeout->node);
    15     timeout->delta_ticks_from_prev = _INACTIVE;
    16 
    17     return 0;
    18 }

    第3行,如果线程没有在延时或者休眠,则返回出错。

    第7行,如果线程不是在超时队列的最后,则if条件成立。

    第9行,取出线程的下一个节点。

    第12行,将下一个节点的延时时间加上要取消的线程剩余的延时时间。

    第14行,将线程从超时队列移除。

    第15行,将线程的delta_ticks_from_prev设为_INACTIVE。

    好了,到这里线程的创建、取消和调度过程都分析完了。

    搞明白最近这三篇随笔,也就基本搞懂了zephyr内核的核心内容了,剩下的mutex互斥锁、工作队列、信号量等内容也就比较容易理解了。

  • 相关阅读:
    使用 typeScript 规范代码
    图片 剪切 上传
    hybrid
    resful
    区块链
    前端数据采集 埋点 追踪用户系列行为
    kafka生产消息的速度跟什么有关?
    引用:实际数据库需求变化及演变
    HBase学习
    使用scala开发spark入门总结
  • 原文地址:https://www.cnblogs.com/lknlfy/p/10327798.html
Copyright © 2011-2022 走看看