Zephyr内核调度之锁调度分析

本文分析Zerphyr如何实现调度加解锁。

最近在看Zephyr内核代码的时候,深入的看了一下调度加解锁的实现,虽然代码比较简练,但实现原理上比较有意思,这里做一个简单的记录。

下面就是加解锁调度的主要代码,可以看到,就是在对sched_spinlock上锁的情况下对sched_locked字段进行加减完成

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

#define LOCKED(lck) for (k_spinlock_key_t __i = {},			\
					  __key = k_spin_lock(lck);	\
			!__i.key;					\
			k_spin_unlock(lck, __key), __i.key = 1)

对于锁调度,展开来看就是

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不满足就退出循环,功能相当于是。

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

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所在的结构体
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

#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使得当前正在执行的线程不能被抢占而达到锁调度的目的。