Zephyr内核调度之代码分析7---时间片,Timer和调度锁

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

本文分析最后三种调度时间:时间片,Timer,调度锁。

前文参考

[1] Zephyr内核调度之调度方式与时机
[2] Zephyr线程阻塞和超时机制分析
[3] Zephyr内核调度之锁调度分析
[4] Zephyr内核调度之代码分析2–调度关键函数
[5] Zephyr内核对象–k_timer简介
[6] Zephyr内核调度之锁调度分析
[7] Zephyr内核调度之代码分析3–线程睡眠和挂起
[8] Zephyr内核调度之代码分析6-同步和数据传递

在[1]中提到了17种调度时机,在前面的6篇文章中分析了其中14种,本文分析剩下的三种

  • 时间片到

    • z_time_slicer
  • 停止内核timer

    • z_timer_expiration_handler->z_ready_thread
    • z_impl_k_timer_status_sync->z_pend_curr
    • z_impl_k_timer_stop->z_ready_thread&z_reschedule_unlocked
  • 解锁调度

    • k_sched_unlock->update_cache&z_reschedule_unlocked

代码分析

时间片到

每个抢占式线程都有自己的时间片,相同优先级线程之间如果时间片用完了就在Tick中断退出时进行上下文切换,在[2]中分析过在tick中断中会调用z_time_slicer进行时间片处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
void z_time_slice(int ticks)
{

k_spinlock_key_t key = k_spin_lock(&sched_spinlock);

//检查是否支持时间片
if (slice_time && sliceable(_current)) {
if (ticks >= _current_cpu->slice_ticks) {
//线程的时间片已消耗完,将线程重新加入到ready_q中,由于是在ISR中执行,上下文切换会在退出ISR时进行
move_thread_to_end_of_prio_q(_current);
z_reset_time_slice();
} else {
//时间片未到,进行时间片剩余tick数更新
_current_cpu->slice_ticks -= ticks;
}
} else {
_current_cpu->slice_ticks = 0;
}
k_spin_unlock(&sched_spinlock, key);
}

move_thread_to_end_of_prio_q将线程从ready_q中取出再加入,这样就可以排到其它同优先级线程的后面,让其它同优先级的线程得到CPU

1
2
3
4
5
6
7
8
9
10
11
12
13
static void move_thread_to_end_of_prio_q(struct k_thread *thread)
{
//从ready_q中取出
if (z_is_thread_queued(thread)) {
dequeue_thread(&_kernel.ready_q.runq, thread);
}

//再次加入到read_q中
queue_thread(&_kernel.ready_q.runq, thread);

//选出最合适的线程
update_cache(thread == _current);
}

dequeue_thread/queue_thread/update_cache均是关键函数,参考[4]

内核Timer

不只是停止内核Timer,在线程等待内核timer和内核timer到期都会引发调度,在[5]中已经对k_timer进行分析,这里简单看一下调用关系
z_timer_expiration_handler->z_ready_thread,到期时会发生调度,由于z_timer_expiration_handler是在tick中断中执行,因此上下文切换会在退出ISR时发生
z_impl_k_timer_status_sync->z_pend_curr 等待k_timer过期会发生调度
z_impl_k_timer_stop->z_ready_thread&z_reschedule_unlocked 停止k_timer会发生调度
z_ready_thread,z_reschedule_unlocked,z_pend_cur在[7]和[8]中已经分析

解锁调度

解锁调度在[6]中已经分析,其使用的update_cache参考[4],z_reschedule_unlocked参考[8],本文就不再做说明

关于上下文切换时机

了解上下文切换时机有利于在应用/驱动开发时合理安排使用内核对象,在调试调优时快速定位调度引起的问题。虽然[1]中列出的17种引发调度的时机已经全部分析完毕,但是Zephyr在不停的进化迭代,会增加或者删除一些内核对象,调整内核的流程。
相应的调度时机也会变化。因此最重要的是了解[4]中更基础的调度流程和基础函数,即便调度时机有所增加和变化最后也会走到这些流程和函数,万变不离其中。