本文分析调度时机中的两种:线程启动和中止。
前文参考
[1] Zephyr内核调度之调度方式与时机
[2] Zephyr线程阻塞和超时机制分析
[3] Zephyr用户模式-系统调用
[4] Zephyr内核调度之代码分析2-调度关键函数
[5] Zephyr内核调度之代码分析3–线程睡眠和挂起
在[1]中提到了17种调度时机,本文分析其中2种:涉及线程创建启动和中止引起的调度
- 启动线程
- k_thread_start
- 中止线程
- k_thread_abort
- 等待线程中止
- k_thread_join
由于系统调用的关系可被应用调用的内核函数实际实现对应到sched.c中(参考[3]):
k_thread_start->z_impl_k_thread_start
k_thread_abort->z_impl_k_thread_abort
k_thread_join->z_impl_k_thread_join
API实现分析
启动线程
在创建线程时可以立即使用k_thread_start启动线程,也可以在之后的任意时刻使用k_thread_start启动线程,z_impl_k_thread_start通过调用z_sched_start实现1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16void z_sched_start(struct k_thread *thread)
{
k_spinlock_key_t key = k_spin_lock(&sched_spinlock);
if (z_has_thread_started(thread)) {
k_spin_unlock(&sched_spinlock, key);
return;
}
//将线程重新加入到就绪列队中,并选出最优的线程
z_mark_thread_as_started(thread);
ready_thread(thread);
//检查是否需要上下文切换(被重新加入到就绪列队的线程处于最高优先级),如果需要就执行切换
z_reschedule(&sched_spinlock, key);
}
作为调度的关键函数dready_thread和z_reschedule在[]中已分析过。
顺便说一句如果创建线程时是通过指定timeout时间后自动启动,那么在timeout到来时将使用[]中的分析的超时到的机制将线程加入到就绪列队,并执行调度。
线程中止
z_impl_k_thread_abort由z_thread_abort实现1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17void z_thread_abort(struct k_thread *thread)
{
k_spinlock_key_t key = k_spin_lock(&sched_spinlock);
if ((thread->base.thread_state & _THREAD_DEAD) != 0U) {
k_spin_unlock(&sched_spinlock, key);
return;
}
//将线程从ready_q或timeout list中移除,将线程从wait_q中移除,将线程标识为_THREAD_DEAD,解除等待该线程中止的线程,选出最优的线程
end_thread(thread);
//如果未在isr内,进行上下文切换。ISR会在退出ISR的时候执行上下文切换
if (thread == _current && !arch_is_in_isr()) {
z_swap(&sched_spinlock, key);
__ASSERT(false, "aborted _current back from dead");
}
k_spin_unlock(&sched_spinlock, key);
}
作为调度的关键函数z_swap已经在[4]中分析过,end_thread后文分析。
等待线程中止
线程睡眠,是线程自己主动调用内核API让出CPU.k_msleep
直接通过k_sleep实现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
34int z_impl_k_thread_join(struct k_thread *thread, k_timeout_t timeout)
{
k_spinlock_key_t key = k_spin_lock(&sched_spinlock);
int ret = 0;
if ((thread->base.thread_state & _THREAD_DEAD) != 0U) {
//被等待的线程已经中止,立即返回成功
ret = 0;
} else if (K_TIMEOUT_EQ(timeout, K_NO_WAIT)) {
//如果不等待,且被等待的线程还未中止,返回失败
ret = -EBUSY;
} else if ((thread == _current) ||
(thread->base.pended_on == &_current->join_queue)) {
//不能等待自己中止
ret = -EDEADLK;
} else {
//不能在中断中等待超时
__ASSERT(!arch_is_in_isr(), "cannot join in ISR");
//将当前线程从ready_q中移除,加入到join_queue的wait_q中,如果被等待的线程中止,当前线程将从join_queue取出,并重新加入到ready_q
add_to_waitq_locked(_current, &thread->join_queue);
//将当前线程加入timeout list,如果超时时间到,从timeout list移除,并重新加入到ready_q
add_thread_timeout(_current, timeout);
//执行上下文切换。如果超时时间
ret = z_swap(&sched_spinlock, key);
return ret;
}
k_spin_unlock(&sched_spinlock, key);
return ret;
}
作为调度的关键函数z_swap和add_to_waitq_locked已经在[4]中分析过,add_thread_timeout在[2]中分析过.
内部函数分析
上一节中有提到一些和调度相关的内部函数,这里做展开分析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
36
37
38
39
40
41
42
43
44
45static void end_thread(struct k_thread *thread)
{
//已经中止的函数不做处理
if ((thread->base.thread_state & _THREAD_DEAD) == 0U) {
//改变线程状态
thread->base.thread_state |= _THREAD_DEAD;
thread->base.thread_state &= ~_THREAD_ABORTING;
//如果线程在ready_q中将其移除,参考[2]
if (z_is_thread_queued(thread)) {
dequeue_thread(&_kernel.ready_q.runq, thread);
}
//如果线程在等待内核对象,将其从wait_q中移除,参考[4]
if (thread->base.pended_on != NULL) {
unpend_thread_no_timeout(thread);
}
//将线程从timeout list中移除,参考[2]
(void)z_abort_thread_timeout(thread);
//通知等待该线程中止的线程退出等待
unpend_all(&thread->join_queue);
//选出最合适的线程,参考[2]
update_cache(1);
z_thread_monitor_exit(thread);
}
}
static inline void unpend_all(_wait_q_t *wait_q)
{
struct k_thread *thread;
//遍历wait_q,对每个线程做动作
while ((thread = z_waitq_head(wait_q)) != NULL) {
//从timeout list中移除,参考[4].
unpend_thread_no_timeout(thread);
(void)z_abort_thread_timeout(thread);
arch_thread_return_value_set(thread, 0);
//将线程加入到ready_q中,参考[5]
ready_thread(thread);
}
}
这里提的unpend_all(&thread->join_queue);
也就是会解除和join中add_to_waitq_locked 挂起的thread