Zephyr内核调度之代码分析2--调度关键函数

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

本文分析调度会用到的关键函数。

前文参考
[1] Zephyr内核调度之代码分析–1线程流转
[2] Zephyr线程阻塞和超时机制分析
[3] Zephyr-OS内核上下文切换简述

注:本系列文章都基于单核CPU进行分析

在参考一文中我们看到了线程调度流转依赖于三个宏函数,为方便使用Zephyr又对这三个宏函数进行了封装,同时搭配其它的函数来完成调度。本文将列举并分析这些关键函数。
更广义的讲调度可以由下面三项中的任意1个或者几个组成:
1 调整调度列队中线程:添加,删除,调整顺序
2 选出最合适的线程
3 执行上下文切换

例如线程被挂起执行了1,线程如果获得资源变成就绪就执行了1和2,线程被调度占用了CPU根据情况是1,2,3。

调整调度列队中的线程

就绪列队

关键函数如下:
static ALWAYS_INLINE void queue_thread(void *pq, struct k_thread *thread)
static ALWAYS_INLINE void dequeue_thread(void *pq, struct k_thread *thread)
static inline bool z_is_thread_queued(struct k_thread *thread)
从函数名就能看出是对线程列队进行操作,下面分析其实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
static ALWAYS_INLINE void queue_thread(void *pq,
struct k_thread *thread)
{
//被加入到列队中的线程加上_THREAD_QUEUED标记
thread->base.thread_state |= _THREAD_QUEUED;
if (should_queue_thread(thread)) {
//将线程thread加入到列队pq中
_priq_run_add(pq, thread);
}
}

static ALWAYS_INLINE void dequeue_thread(void *pq,
struct k_thread *thread)
{
//线程从列队中取出时清掉_THREAD_QUEUED标记
thread->base.thread_state &= ~_THREAD_QUEUED;
if (should_queue_thread(thread)) {
//将线程thread从列队pq中移除
_priq_run_remove(pq, thread);
}
}

z_is_thread_queued通过检查_THREAD_QUEUED标记判断线程是否在列队中,代码非常简单就不再列出了。

等待列队

关键函数:
static void pend(struct k_thread *thread, _wait_q_t *wait_q, k_timeout_t timeout)
static inline void unpend_thread_no_timeout(struct k_thread *thread)
pend用于将线程加入到等待列队,unpend_thread_no_timeout用于将线程从等待列队中删除

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
static void pend(struct k_thread *thread, _wait_q_t *wait_q,
k_timeout_t timeout)
{
LOCKED(&sched_spinlock) {
//将线程thread加入到wait_q中
add_to_waitq_locked(thread, wait_q);
}

//等待超时设置
add_thread_timeout(thread, timeout);
}

static void add_to_waitq_locked(struct k_thread *thread, _wait_q_t *wait_q)
{
//将线程从ready queue中移除
unready_thread(thread);
z_mark_thread_as_pending(thread);

if (wait_q != NULL) {
//将线程加入到wait_q中,这里线程自己的管理结构体中的pended_on字段保存了wait_q,之后做线程unpend的时候就无需另外传入wait_q了
thread->base.pended_on = wait_q;
z_priq_wait_add(&wait_q->waitq, thread);
}
}

对于等待timeout的处理,可以参考[2].

1
2
3
4
5
6
7
static inline void unpend_thread_no_timeout(struct k_thread *thread)
{
//pended_on_thread取的就是存放wait_q的pended_on,这里将线程从wait_q中移除
_priq_wait_remove(&pended_on_thread(thread)->waitq, thread);
z_mark_thread_as_not_pending(thread);
thread->base.pended_on = NULL;
}

选出最合适的线程

就绪列队

关键函数:
static void update_cache(int preempt_ok)
update_cache会从ready_q中选出最合适的线程,将选出最合适的线程放到_kernel.ready_q.cache中,接下来的上下文切换就会将CPU让给_kernel.ready_q.cache使用

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
27
28
29
30
31
static void update_cache(int preempt_ok)
{
//获取最合适的线程
struct k_thread *thread = next_up();

if (should_preempt(thread, preempt_ok)) {
//时间片的处理,在后续文章中分析说明
#ifdef CONFIG_TIMESLICING
if (thread != _current) {
z_reset_time_slice();
}
#endif
update_metairq_preempt(thread);
//如果当前线程可以被抢占,则将获取的最合适线程放再cache中
_kernel.ready_q.cache = thread;
} else {
//如果当前线程不可抢占,cache中维持为当前线程
_kernel.ready_q.cache = _current;
}

}

static ALWAYS_INLINE struct k_thread *next_up(void)
{
struct k_thread *thread;
//从ready_q中选出最合适的线程
thread = _priq_run_best(&_kernel.ready_q.runq);

//如果ready_q中没有线程,那么下一个占用CPU的就是idle_thread
return (thread != NULL) ? thread : _current_cpu->idle_thread;
}

注意这里只是

等待列队

关键函数:
struct k_thread *z_unpend1_no_timeout(_wait_q_t *wait_q)
struct k_thread *z_unpend_first_thread(_wait_q_t *wait_q)
z_unpend1_no_timeout将最合适的线程选择出来并从wait_q移除,但不关心其是否在timeout_list中
z_unpend_first_thread将最合适的线程选择出来并从wait_q中移除,同时中止timeout检查

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
27
28
29
30
31
32
33
34
35
struct k_thread *z_unpend1_no_timeout(_wait_q_t *wait_q)
{
struct k_thread *thread = NULL;

LOCKED(&sched_spinlock) {
//从wait_q中选出最合适的线程
thread = _priq_wait_best(&wait_q->waitq);

if (thread != NULL) {
//将最合适的线程从wait_q中移除
unpend_thread_no_timeout(thread);
}
}

return thread;
}

struct k_thread *z_unpend_first_thread(_wait_q_t *wait_q)
{
struct k_thread *thread = NULL;

LOCKED(&sched_spinlock) {
//从wait_q中选出最合适的线程
thread = _priq_wait_best(&wait_q->waitq);

if (thread != NULL) {
//将最合适的线程从wait_q中移除
unpend_thread_no_timeout(thread);
//中止该线程的timeout检查
(void)z_abort_thread_timeout(thread);
}
}

return thread;
}

执行上下文切换

执行上文切换,就是让update_cache选出保存在_kernel.ready_q.cache占用CPU。上下文切换和体系架构相关,在coretex-m系列可以由pendsv主动触发,也可以在其它中断服务返回的时候进行。
其关键函数为:
static ALWAYS_INLINE int z_swap(struct k_spinlock *lock, k_spinlock_key_t key)
static inline int z_swap_irqlock(unsigned int key)
static inline void z_swap_unlocked(void)
以上三个关键函数最后都调用到arch_swap,在参考[3]中详细分析了cortex-m上下文切换的代码,由于[3]的成文时间比较早,函数名已经发生变化,这里arch_swap就是[3]中的__swap函数。

小结

后续文章的分析调度代码的主要部分都是由本文分析的关键函数组成:
static ALWAYS_INLINE void queue_thread(void *pq, struct k_thread *thread)
static ALWAYS_INLINE void dequeue_thread(void *pq, struct k_thread *thread)
static inline bool z_is_thread_queued(struct k_thread *thread)
static void pend(struct k_thread *thread, _wait_q_t *wait_q, k_timeout_t timeout)
static inline void unpend_thread_no_timeout(struct k_thread *thread)
static void update_cache(int preempt_ok)
struct k_thread *z_unpend1_no_timeout(_wait_q_t *wait_q)
struct k_thread *z_unpend_first_thread(_wait_q_t *wait_q)
static ALWAYS_INLINE int z_swap(struct k_spinlock *lock, k_spinlock_key_t key)
static inline int z_swap_irqlock(unsigned int key)
static inline void z_swap_unlocked(void)