本文分析调度时机中的线程睡眠和挂起。
前文参考
[1] Zephyr内核调度之调度方式与时机
[2] Zephyr线程阻塞和超时机制分析
[3] Zephyr用户模式-系统调用
[4] Zephyr内核调度之代码分析2-调度关键函数
在[1]中提到了17种调度时机,本文分析其中6种:涉及线程睡眠和挂起引起的调度
- 线程挂起
- k_thread_suspend
- 线程挂起恢复
- k_thread_resume
- 线程睡眠
- k_sleep
- k_msleep
- k_usleep
- 线程睡眠时间到
- z_thread_timeout
- 线程唤醒
- k_wakeup
- 线程主动释放CPU控制权
- k_yield
由于系统调用的关系可被应用调用的内核函数实际实现对应到sched.c中(参考[3]):
k_thread_suspend->z_impl_k_thread_suspend
k_thread_resume->z_impl_k_thread_resume
k_sleep->z_impl_k_sleep
k_usleep->z_impl_k_usleep
k_yield->z_impl_k_yield
k_wakeup->z_impl_k_wakeup
API实现分析
线程挂起
只有在挂起当前正在执行的线程时才会进行上下文切换1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20void z_impl_k_thread_suspend(struct k_thread *thread)
{
(void)z_abort_thread_timeout(thread);
LOCKED(&sched_spinlock) {
//将要挂起的线程从ready_q中移除
if (z_is_thread_queued(thread)) {
dequeue_thread(&_kernel.ready_q.runq, thread);
}
z_mark_thread_as_suspended(thread);
//重新最合适的线程
update_cache(thread == _current);
}
//如果挂起的线程是当前线程,执行上下文切换
if (thread == _current) {
z_reschedule_unlocked();
}
}
作为调度的关键函数dequeue_thread,update_cache已经在[4]中分析过。z_reschedule_unlocked后文分析
线程挂起恢复
被挂起的线程恢复时,只有被恢复的线程是最高优先级时才会进行上下文切换1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19void z_impl_k_thread_resume(struct k_thread *thread)
{
k_spinlock_key_t key = k_spin_lock(&sched_spinlock);
//不是被挂起的线程,不需要做恢复
if (!z_is_thread_suspended(thread)) {
k_spin_unlock(&sched_spinlock, key);
return;
}
//将线程重新加入到就绪列队中,并选出最优的线程
z_mark_thread_as_not_suspended(thread);
ready_thread(thread);
//检查是否需要上下文切换(被重新加入到就绪列队的线程处于最高优先级),如果需要就执行切换
z_reschedule(&sched_spinlock, key);
}
ready_thread和z_reschedule后文分析
线程睡眠
线程睡眠,是线程自己主动调用内核API让出CPU.k_msleep
直接通过k_sleep实现1
2
3
4static inline int32_t k_msleep(int32_t ms)
{
return k_sleep(Z_TIMEOUT_MS(ms));
}
sched.c中提供2个睡眠API:
- z_impl_k_sleep
- z_impl_k_usleep
z_impl_k_usleep会将时间单位转换为tick数,通过z_tick_sleep执行sleep1
2
3
4
5
6
7
8
9int32_t z_impl_k_usleep(int us)
{
int32_t ticks;
ticks = k_us_to_ticks_ceil64(us);
ticks = z_tick_sleep(ticks);
return k_ticks_to_us_floor64(ticks);
}
z_impl_k_sleep会先判断是不是永久sleep,永久的sleep会当做挂起处理,之后再通过z_tick_sleep执行sleep1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22int32_t z_impl_k_sleep(k_timeout_t timeout)
{
k_ticks_t ticks;
__ASSERT(!arch_is_in_isr(), "");
if (K_TIMEOUT_EQ(timeout, K_FOREVER)) {
k_thread_suspend(_current);
SYS_PORT_TRACING_FUNC_EXIT(k_thread, sleep, timeout, (int32_t) K_TICKS_FOREVER);
return (int32_t) K_TICKS_FOREVER;
}
ticks = timeout.ticks;
ticks = z_tick_sleep(ticks);
int32_t ret = k_ticks_to_ms_floor64(ticks);
return ret;
}
sleep单只对线程有效,在isr中执行会直接assert. 只要线程执行了sleep,就会让出CPU,必然会立即引发调度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 int32_t z_tick_sleep(k_ticks_t ticks)
{
uint32_t expected_wakeup_ticks;
//不允许在isr中sleep
__ASSERT(!arch_is_in_isr(), "");
//如果sleep 0个tick,当做yield处理让出CPU,后文分析k_yield
if (ticks == 0) {
k_yield();
return 0;
}
//计算timeout到期的tick
k_timeout_t timeout = Z_TIMEOUT_TICKS(ticks);
if (Z_TICK_ABS(ticks) <= 0) {
expected_wakeup_ticks = ticks + sys_clock_tick_get_32();
} else {
expected_wakeup_ticks = Z_TICK_ABS(ticks);
}
k_spinlock_key_t key = k_spin_lock(&sched_spinlock);
pending_current = _current;
//将当前thread从就绪列表中移除,并选出最优的线程
unready_thread(_current);
//将当前线程加入到timeout list中
z_add_thread_timeout(_current, timeout);
z_mark_thread_as_suspended(_current);
//进行上下文切换
(void)z_swap(&sched_spinlock, key);
__ASSERT(!z_is_thread_state_set(_current, _THREAD_SUSPENDED), "");
ticks = (k_ticks_t)expected_wakeup_ticks - sys_clock_tick_get_32();
if (ticks > 0) {
return ticks;
}
return 0;
}
作为调度的关键函数z_swap已经在[4]中分析过。z_add_thread_timeout在[2]中已经分析,unready_thread后文分析
线程睡眠时间到
从[2]的分析中我们知道,线程sleep时会通过z_add_thread_timeout加入到timer中,当sleep tick到的时候会呼叫z_thread_timeout1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23void z_thread_timeout(struct _timeout *timeout)
{
struct k_thread *thread = CONTAINER_OF(timeout,
struct k_thread, base.timeout);
LOCKED(&sched_spinlock) {
//检查线程在sleep期间是否被其它线程/ISR做了abort
bool killed = ((thread->base.thread_state & _THREAD_DEAD) ||
(thread->base.thread_state & _THREAD_ABORTING));
if (!killed) {
//检查线程是否因等内核对象超时,在之后内核对象引起的调度文章中分析
if (thread->base.pended_on != NULL) {
unpend_thread_no_timeout(thread);
}
z_mark_thread_as_started(thread);
z_mark_thread_as_not_suspended(thread);
//将线程重新加入到就绪列队中,并选出最优的线程
ready_thread(thread);
}
}
}
ready_thread在后文分析,由于z_thread_timeout是在tickISR中执行,在ISR退出时会判断是否要进行线程的上下文切换。
线程唤醒
处于睡眠状态的线程可以被提前唤醒1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24void z_impl_k_wakeup(k_tid_t thread)
{
//等待内核对象的线程不能被唤醒
if (z_is_thread_pending(thread)) {
return;
}
//将线程从timeout list中移除
if (z_abort_thread_timeout(thread) < 0) {
/* Might have just been sleeping forever */
if (thread->base.thread_state != _THREAD_SUSPENDED) {
return;
}
}
z_mark_thread_as_not_suspended(thread);
//将线程重新加入到就绪列队中,并选出最优的线程
z_ready_thread(thread);
//如果不是在IRS中唤醒线程,需要检查并执行上下文切换. 如果是在ISR中唤醒线程,在ISR退出时会判断是否要进行线程的上下文切换。
if (!arch_is_in_isr()) {
z_reschedule_unlocked();
}
}
z_ready_thread在后文分析
线程主动释放CPU控制权
k_yield主动释放CPU控制权,如果有更高或者同优先级的线程在就绪列队中必然引起调度1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21void z_impl_k_yield(void)
{
//ISR中不能进行yield
__ASSERT(!arch_is_in_isr(), "");
k_spinlock_key_t key = k_spin_lock(&sched_spinlock);
//用dequeue_thread将线程从就绪列队中取出,再用dequeue_thread重新加入,相当于会将当前线程排列在高优先级和同等优先级之后
if (!IS_ENABLED(CONFIG_SMP) ||
z_is_thread_queued(_current)) {
dequeue_thread(&_kernel.ready_q.runq,
_current);
}
queue_thread(&_kernel.ready_q.runq, _current);
//选出最优的线程
update_cache(1);
//执行上下文切换
z_swap(&sched_spinlock, key);
}
dequeue_thread,queue_thread,z_swap均是[4]中分析过的关键函数。
内部函数分析
上一节中有提到一些和调度相关的内部函数,这里做展开分析
z_ready_thread,ready_thread,unready_thread
z_ready_thread调用的就是ready_thread,因此只用分析ready_thread和unready_thread1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23static void ready_thread(struct k_thread *thread)
{
//判断thread没有在read_q中才做添加
if (!z_is_thread_queued(thread) && z_is_thread_ready(thread)) {
//将thread添加到ready_q中
queue_thread(&_kernel.ready_q.runq, thread);
//并选出合适的thread
update_cache(0);
}
}
static void unready_thread(struct k_thread *thread)
{
//判断thread在read_q中才做移除
if (z_is_thread_queued(thread)) {
//将thread从ready_q中移除
dequeue_thread(&_kernel.ready_q.runq, thread);
}
//并选出合适的thread
update_cache(thread == _current);
}
z_reschedule,z_reschedule_unlocked和上下文切换的调用关系如下:
z_reschedule->z_swap
z_reschedule_unlocked->z_reschedule_irqlock->z_swap_irqlock