1. 程式人生 > >Zephyr學習(五)線程和調度

Zephyr學習(五)線程和調度

upd return line not des oba fin mon cancel

前面說過zephyr支持靜態和動態兩種方式創建線程,這裏分析動態創建的方式。應用程序通過調用k_thread_create()函數創建一個線程,實際上是調用_impl_k_thread_create()函數,定義在zephyr-zephyr-v1.13.0\kernel\thread.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.0\kernel\thread.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.0\kernel\thread.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.0\kernel\sched.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()函數,判斷是否處於中斷上下文,在中斷裏是不允許線程切換的,定義在arch\arm\include\kernel_arch_func.h:

#define _is_in_isr() _IsInIsr()

實際上調用的是_IsInIsr()函數,定義在zephyr-zephyr-v1.13.0\arch\arm\include\cortex_m\exc.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.0\kernel\include\ksched.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.0\kernel\include\kswap.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.0\kernel\sched.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.0\arch\arm\core\swap.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.0\arch\arm\core\swap_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.0\kernel\sched.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.0\kernel\thread.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.0\kernel\include\timeout_q.h:

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

實際上調用的是_abort_timeout()函數,定義在zephyr-zephyr-v1.13.0\kernel\include\timeout_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學習(五)線程和調度