最近在看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
26static 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.h1
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
4for (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
3k_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被初始化为01
2
3
4z_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
12union {
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的值就是0xFFXX1
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使得当前正在执行的线程不能被抢占而达到锁调度的目的。