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

本文分析最后三种调度时间:时间片,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进行时间片处理

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

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]中更基础的调度流程和基础函数,即便调度时机有所增加和变化最后也会走到这些流程和函数,万变不离其中。