Zephyr學習(五)執行緒和排程
前面說過zephyr支援靜態和動態兩種方式建立執行緒,這裡分析動態建立的方式。應用程式通過呼叫k_thread_create()函式建立一個執行緒,實際上是呼叫_impl_k_thread_create()函式,定義在zephyr-zephyr-v1.13.0\kernel\thread.c:
1k_tid_t _impl_k_thread_create(struct k_thread *new_thread, 2k_thread_stack_t *stack, 3size_t stack_size, k_thread_entry_t entry, 4void *p1, void *p2, void *p3, 5int 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, 10prio, options); 11 12if (delay != K_FOREVER) { 13schedule_new_thread(new_thread, delay); 14} 15 16return new_thread; 17 }
第9行,呼叫_setup_new_thread()函式,在開發環境搭建裡已經分析過了。
第12行,傳進來的最後一個引數一般為K_NO_WAIT,即馬上參與排程,所以if條件成立。
第13行,呼叫schedule_new_thread()函式,定義在zephyr-zephyr-v1.13.0\kernel\thread.c:
1static void schedule_new_thread(struct k_thread *thread, s32_t delay) 2{ 3if (delay == 0) { 4k_thread_start(thread); 5} else { 6s32_t ticks = _TICK_ALIGN + _ms_to_ticks(delay); 7int key = irq_lock(); 8 9_add_thread_timeout(thread, NULL, ticks); 10irq_unlock(key); 11} 12 }
第3行,由於K_NO_WAIT的值就為0,所以if條件成立。
第4行,呼叫k_thread_start()函式,定義在zephyr-zephyr-v1.13.0\kernel\thread.c:
1void _impl_k_thread_start(struct k_thread *thread) 2{ 3int key = irq_lock(); /* protect kernel queues */ 4 5if (_has_thread_started(thread)) { 6irq_unlock(key); 7return; 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:
1int _reschedule(int key) 2{ 3if (_is_in_isr()) { 4goto noswap; 5} 6 7if (_get_next_ready_thread() != _current) { 8return _Swap(key); 9} 10 11noswap: 12irq_unlock(key); 13return 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 { 3u32_t vector = __get_IPSR(); 4 5/* IRQs + PendSV (14) + SYSTICK (15) are interrupts. */ 6return (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 { 3unsigned int ret; 4_update_time_slice_before_swap(); 5 6ret = __swap(key); 7 8return 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 */ 8SCB->ICSR |= SCB_ICSR_PENDSVSET_Msk; 9 10/* clear mask or enable all irqs to take a pendsv */ 11irq_unlock(0); 12 13return _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:
1SECTION_FUNC(TEXT, __pendsv) 2 3/* protect the kernel state while we play with the thread lists */ 4 5movs.n r0, #_EXC_IRQ_DEFAULT_PRIO 6msr BASEPRI, r0 7 8/* load _kernel into r1 and current k_thread into r2 */ 9ldr r1, =_kernel 10ldr r2, [r1, #_kernel_offset_to_current] 11 12/* addr of callee-saved regs in thread in r0 */ 13ldr r0, =_thread_offset_to_callee_saved 14add r0, r2 15 16/* save callee-saved + psp in thread */ 17mrs ip, PSP 18 19stmia 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*/ 28ldr v4, =_SCS_ICSR 29ldr v3, =_SCS_ICSR_UNPENDSV 30 31/* _kernel is still in r1 */ 32 33/* fetch the thread to run from the ready queue cache */ 34ldr r2, [r1, _kernel_offset_to_ready_q_cache] 35 36str 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 */ 48str v3, [v4, #0] 49 50/* Restore previous interrupt disable state (irq_lock key) */ 51ldr r0, [r2, #_thread_offset_to_basepri] 52movs.n r3, #0 53str r3, [r2, #_thread_offset_to_basepri] 54 55/* restore BASEPRI for the incoming thread */ 56msr BASEPRI, r0 57 58/* load callee-saved + psp from thread */ 59add r0, r2, #_thread_offset_to_callee_saved 60ldmia r0, {v1-v8, ip} 61 62msr PSP, ip 63 64/* exc return */ 65bx 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:
1void _impl_k_yield(void) 2{ 3__ASSERT(!_is_in_isr(), ""); 4 5if (!_is_idle(_current)) { 6LOCKED(&sched_lock) { 7_priq_run_remove(&_kernel.ready_q.runq, _current); 8_priq_run_add(&_kernel.ready_q.runq, _current); 9update_cache(1); 10} 11} 12 13if (_get_next_ready_thread() != _current) { 14_Swap(irq_lock()); 15} 16 }
裡面的函式都已經分析過了,這裡不再重複。
要成功將自己切換出去(讓出CPU)的前提是有優先順序比自己更高的並且已經就緒的執行緒。
接下來看一下執行緒的取消過程。應用程式呼叫k_thread_cancel()函式取消一個執行緒,定義在
zephyr-zephyr-v1.13.0\kernel\thread.c:
1int _impl_k_thread_cancel(k_tid_t tid) 2{ 3struct k_thread *thread = tid; 4 5unsigned int key = irq_lock(); 6 7if (_has_thread_started(thread) || 8!_is_thread_timeout_active(thread)) { 9irq_unlock(key); 10return -EINVAL; 11} 12 13_abort_thread_timeout(thread); 14_thread_monitor_exit(thread); 15 16irq_unlock(key); 17 18return 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:
1static inline int _abort_timeout(struct _timeout *timeout) 2{ 3if (timeout->delta_ticks_from_prev == _INACTIVE) { 4return _INACTIVE; 5} 6 7if (!sys_dlist_is_tail(&_timeout_q, &timeout->node)) { 8sys_dnode_t *next_node = 9sys_dlist_peek_next(&_timeout_q, &timeout->node); 10struct _timeout *next = (struct _timeout *)next_node; 11 12next->delta_ticks_from_prev += timeout->delta_ticks_from_prev; 13} 14sys_dlist_remove(&timeout->node); 15timeout->delta_ticks_from_prev = _INACTIVE; 16 17return 0; 18 }
第3行,如果執行緒沒有在延時或者休眠,則返回出錯。
第7行,如果執行緒不是在超時佇列的最後,則if條件成立。
第9行,取出執行緒的下一個節點。
第12行,將下一個節點的延時時間加上要取消的執行緒剩餘的延時時間。
第14行,將執行緒從超時佇列移除。
第15行,將執行緒的delta_ticks_from_prev設為_INACTIVE。
好了,到這裡執行緒的建立、取消和排程過程都分析完了。
搞明白最近這三篇隨筆,也就基本搞懂了zephyr核心的核心內容了,剩下的mutex互斥鎖、工作佇列、訊號量等內容也就比較容易理解了。