Zephyr内核调度之锁调度分析

Creative Commons
本作品采用知识共享署名

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

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