[进程管理] linux核心调度器

        linux核心调度器主要基于两个函数实现:周期性调度器函数和主调度器函数。这些函数会根据现有进程的优先级分配CPU时间,所以也称“优先调度”

         一、周期性调度器

        周期性调度器是在函数scheduler_tick(void),如果当前系统正在活动中,那么内核会按照CPU频率HZ自动调用该函数。如果运行队列中没有进程,则等待;如果电力不足,也会关闭该调度器以减少电能消耗。(基于内核版本3.1.6)

        该函数有下面两个主要任务。
                (1) 管理内核中与整个系统和各个进程的调度相关的统计量。
                (2) 激活负责当前进程的调度类的周期性调度方法。

void scheduler_tick(void)
{
	int cpu = smp_processor_id(); /* 获取CPU Id号*/
	struct rq *rq = cpu_rq(cpu);   /* 获取对应的运行队列 */
	struct task_struct *curr = rq->curr; /* 获取当前线程描述符 */

	sched_clock_tick();

	raw_spin_lock(&rq->lock);  /* 加锁 */
	update_rq_clock(rq);    /* 处理就绪队列始终的更新: 本质是增加struct rq 当前实例的时钟时间戳 */
	update_cpu_load_active(rq); /* 更新就绪队列的cpu_load[]数据: 本质是讲数组中先前存储的负荷值向后移动一个位置,将当前负荷记入数组的第一个位置 */
	curr->sched_class->task_tick(rq, curr, 0); /* 激活当前线程的调度类的周期性调度方法,不同的调度方法有不同的实现 */
	raw_spin_unlock(&rq->lock);/* 解锁 */

	perf_event_task_tick(); /* 与perf计数事件相关 */

#ifdef CONFIG_SMP
	rq->idle_at_tick = idle_cpu(cpu); /* 当前CPU是否空闲 */
	trigger_load_balance(rq, cpu); /* 如果到是时候进行周期性负载平衡则触发SCHED_SOFTIRQ */
#endif
}

        二、主调度器

        周期性调度器是在函数scheduler(void)中实现。

        1. 主要功能:从运行的队列中选择一个合适的线程,并进行线程切换;

        2. 代码分析:

asmlinkage void __sched schedule(void)
{
	struct task_struct *tsk = current; /* 获取当前线程的描述符 */

	sched_submit_work(tsk); /* 为了防止死锁 */
	__schedule(); /* 真正的主调度去实现方法 */
}


static void __sched __schedule(void)
{
	struct task_struct *prev, *next;
	unsigned long *switch_count;
	struct rq *rq;
	int cpu;

need_resched:
	preempt_disable();/* 禁止抢占 */
	cpu = smp_processor_id(); /* 获取当前CPU ID 号 */
	rq = cpu_rq(cpu); /* 获取当前CPU 对应的运行队列 */
	rcu_note_context_switch(cpu); /* 与RCU相关 */
	prev = rq->curr;

	schedule_debug(prev); /* 记录debug信息 */

	if (sched_feat(HRTICK))
		hrtick_clear(rq);

	raw_spin_lock_irq(&rq->lock);

	switch_count = &prev->nivcsw;
	if (prev->state && !(preempt_count() & PREEMPT_ACTIVE)) {
		if (unlikely(signal_pending_state(prev->state, prev))) {
			prev->state = TASK_RUNNING;
		} else {
			deactivate_task(rq, prev, DEQUEUE_SLEEP); /* 使进程停止活动,并最终调用具体的dequeue_task()方法 */
			prev->on_rq = 0;

			/*
			 * If a worker went to sleep, notify and ask workqueue
			 * whether it wants to wake up a task to maintain
			 * concurrency.
			 */
			if (prev->flags & PF_WQ_WORKER) { /* 一些并发性判断和操作,不影响整体调度流程 */
				struct task_struct *to_wakeup;

				to_wakeup = wq_worker_sleeping(prev, cpu);
				if (to_wakeup)
					try_to_wake_up_local(to_wakeup);
			}
		}
		switch_count = &prev->nvcsw;
	}

	pre_schedule(rq, prev);

	if (unlikely(!rq->nr_running))
		idle_balance(cpu, rq);/* 当前运行队列为空,调用idle_balance()试图从其他的CPU运行队列拉来一些进程 */

	put_prev_task(rq, prev); /* 通知调度器类当前运行的进程将要被另一个进程代替,为某些计数器更新计数提供实际 */
	next = pick_next_task(rq);/* 从运行队列中选择下一个运行线程 */
	clear_tsk_need_resched(prev);/* 清除当前进程描述符中的重调度标志TIF_NEED_RESCHED */
	rq->skip_clock_update = 0;

	if (likely(prev != next)) { /* 判断选择的next线程是不是原来的线程 */
		rq->nr_switches++;
		rq->curr = next;
		++*switch_count;

		context_switch(rq, prev, next); /* 负责执行底层的上下文切换 */
		/*
		 * The context switch have flipped the stack from under us
		 * and restored the local variables which were saved when
		 * this task called schedule() in the past. prev == current
		 * is still correct, but it can be moved to another cpu/rq.
		 */
		cpu = smp_processor_id(); /* 更新信息 */
		rq = cpu_rq(cpu);
	} else
		raw_spin_unlock_irq(&rq->lock);/* 如果依旧是原来的线程,则直接解锁 */

	post_schedule(rq);

	preempt_enable_no_resched();
	if (need_resched()) /* 判断是否设置了重调度位,如果设置了则重新搜索新的进程 */
		goto need_resched;
}


原文地址:https://www.cnblogs.com/youngerchina/p/5624640.html