最近在看Zephyr内核代码的时候,深入的看了一下调度加解锁的实现,虽然代码比较简练,但实现原理上比较有意思,这里做一个简单的记录。
下面就是加解锁调度的主要代码,可以看到,就是在对sched_spinlock上锁的情况下对sched_locked字段进行加减完成
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | static inline void z_sched_lock(void) { --_current->base.sched_locked; compiler_barrier(); } void k_sched_lock(void) { LOCKED(&sched_spinlock) { z_sched_lock(); } } void k_sched_unlock(void) { LOCKED(&sched_spinlock) { __ASSERT(_current->base.sched_locked != 0U, ""); __ASSERT(!arch_is_in_isr(), ""); ++_current->base.sched_locked; update_cache(0); } z_reschedule_unlocked(); } |
LOCKED(&sched_spinlock)的宏定义在kernel/include/kernel_internal.h
1 2 3 4 | #define LOCKED(lck) for (k_spinlock_key_t __i = {}, \ __key = k_spin_lock(lck); \ !__i.key; \ k_spin_unlock(lck, __key), __i.key = 1) |
对于锁调度,展开来看就是
1 2 3 4 | for (k_spinlock_key_t __i = {}, __key = k_spin_lock(&sched_spinlock); !__i.key; k_spin_unlock(&sched_spinlock, __key), __i.key = 1) { z_sched_lock(); } |
这里非常巧妙的利用了for循环,初始化表达式来做中断lock,增量表达式做中断unlock,同时让退出条件i.key 满足,在下一次循环的时候判断i.key不满足就退出循环,功能相当于是。
1 2 3 | k_spinlock_key_t __key = k_spin_lock(&sched_spinlock) z_sched_lock(); k_spin_unlock(&sched_spinlock, __key) |
通常的思维我们会想到用一个全局的变量来表示锁调度,而zephyr内核锁调度是对线程自己的sched_locked进行设置达成:
先看sched_locked的变化,创建线程时sched_locked被初始化为0
1 2 3 4 | z_init_thread_base() { thread_base->sched_locked = 0U; } |
在结构体中sched_locked是无符号的8bit整型uint8_t sched_locked
因此z_sched_lock对其减一,就变成了0xFF, k_sched_unlock加一又变回0x0,那么就是:
- sched_locked = 0xFF 表示锁调度
- sched_locked = 0x0 表示未锁调度
但直接在代码中搜索sched_locked,不会发现调度器在对sched_locked进行判断,那么锁调度是如何生效的呢。我们再来看sched_locked所在的结构体1 2 3 4 5 6 7 8 9 10 11 12
union { struct { #if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ uint8_t sched_locked; int8_t prio; #else /* LITTLE and PDP */ int8_t prio; uint8_t sched_locked; #endif }; uint16_t preempt; };
preempt和sched_locked共用内存,sched_locked刚好被放到preemt的高位,当调度被锁后,preempt的值就是0xFFXX
1 2 3 4 5 6 7 8 9 | #define _NON_PREEMPT_THRESHOLD 0x0080U /* highest value of _thread_base.preempt at which a thread is preemptible */ #define _PREEMPT_THRESHOLD (_NON_PREEMPT_THRESHOLD - 1U) static inline int is_preempt(struct k_thread *thread) { /* explanation in kernel_struct.h */ return thread->base.preempt <= _PREEMPT_THRESHOLD; } |
preempt只有在小于_PREEMPT_THRESHOLD(也就是0x7F)时,该线程才能被抢占, 因此一旦sched_locked被设置为0xFF, preempt 0xFFXX必定大于_PREEMPT_THRESHOLD使得当前正在执行的线程不能被抢占而达到锁调度的目的。