Zephyr内核调度之代码分析4--线程启动和中止

本文分析调度时机中的两种:线程启动和中止。

前文参考 [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实现

void 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实现

void 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实现

int 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]中分析过.

内部函数分析 链接到标题

上一节中有提到一些和调度相关的内部函数,这里做展开分析

static 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