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互斥锁、工作队列、信号量等内容也就比较容易理解了。