Zephyr学习线程和调度

Posted lknlfy

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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互斥锁、工作队列、信号量等内容也就比较容易理解了。

 

以上是关于Zephyr学习线程和调度的主要内容,如果未能解决你的问题,请参考以下文章

线程学习知识点总结

Zephyr RTOS -- 学习笔记总述

Zephyr RTOS -- 学习笔记总述

Zephyr RTOS -- Scheduling

Zephyr RTOS -- Scheduling

Zephyr RTOS -- 线程简介