Zephyr學習(五)線程和調度
前面說過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學習(五)線程和調度