进程调度之7:need_resched与强制调度

心已入冬 提交于 2019-12-09 14:29:03

date: 2014-11-02 13:16

在《进程的调度与切换》一节中,我们提到,强制调度的两个条件:

  1. 调度时机:系统调用返回到用户空间前夕,以及中断或者异常服务程序返回到用户空间前夕。可能会有人担心如下两种情况:其一如果进程“躲在”“安全地带”内核空间中不出来,调度器岂不只能干着急?好在内核的设计与实现避免了这个问题。其二:如果进程在用户空间运行,既不调用系统调用函数,也没有中断与异常发生,岂不是也无法进行强转调度?别忘了,系统的时钟中断在默默地坚守着哩。

  2. 必要条件:当前进程的need_resched字段必须非0。该字段必须由内核去设置,为了让调度器有效运转起来,内核必须“瞅准时机”“见缝插针”地设置该字段。设置的时机包括:

    • 其一:在时钟中断服务程序中,当发现当前进程连续运行太长时间时;
    • 其二:当唤醒一个睡眠中的进程,发现被唤醒的进程比当前进程更有资格运行时;
    • 其三:一个进程通过系统调用改变调度政策(sched_setscheduler)或表示礼让(sched_yield)时。这种情况实际上应该被视为主动的、自愿的调度,因为这些系统调用在返回用户空间时会引起立即调度(而上面两种情况只是设置好了need_resched字段,至于何时能有“调度时机”还是个未知数)。

1 时钟中断

时钟中断服务程序do_timer_interrupt()中调用do_timer(),对单CPU结构后者调用update_process_times()来调整当期进程与时间相关的一些运行参数,代码在<kernel/timer.c>中:

    /*
     * Called from the timer interrupt handler to charge one tick to the current 
     * process.  user_tick is 1 if the tick is user time, 0 for system.
     */
    void update_process_times(int user_tick)
    {
    	struct task_struct *p = current;
    	int cpu = smp_processor_id(), system = user_tick ^ 1;
    
    	update_one_process(p, user_tick, system, cpu);
    	if (p->pid) {
    		if (--p->counter <= 0) {
    			p->counter = 0;
    			p->need_resched = 1;
    		}
    		if (p->nice > 0)
    			kstat.per_cpu_nice[cpu] += user_tick;
    		else
    			kstat.per_cpu_user[cpu] += user_tick;
    		kstat.per_cpu_system[cpu] += system;
    	} else if (local_bh_count(cpu) || local_irq_count(cpu) > 1)
    		kstat.per_cpu_system[cpu] += system;
    }

2 wake_up_process()唤醒一个进程

    <kerne./sched.c>
    
    /*
     * Wake up a process. Put it on the run-queue if it's not
     * already there.  The "current" process is always on the
     * run-queue (except when the actual re-schedule is in
     * progress), and as such you're allowed to do the simpler
     * "current->state = TASK_RUNNING" to mark yourself runnable
     * without the overhead of this.
     */
    inline void wake_up_process(struct task_struct * p)
    {
    	unsigned long flags;
    
    	/*
    	 * We want the common case fall through straight, thus the goto.
    	 */
    	spin_lock_irqsave(&runqueue_lock, flags);
    	p->state = TASK_RUNNING;
    	if (task_on_runqueue(p))
    		goto out;
    	add_to_runqueue(p);
    	reschedule_idle(p);
    out:
    	spin_unlock_irqrestore(&runqueue_lock, flags);
    }

可见,唤醒一个进程只是将它的状态改为TASK_RUNNING然后加入可运行队列。

reschedule_idle()判断被唤醒的进程是否比当前进程更有资格运行,如果是,则设置need_resched字段,代码如下:

    /*
     * the 'goodness value' of replacing a process on a given CPU.
     * positive value means 'replace', zero or negative means 'dont'.
     */
    static inline int preemption_goodness(struct task_struct * prev, struct task_struct * p, int cpu)
    {
    	return goodness(p, cpu, prev->active_mm) – 
                   goodness(prev, cpu, prev->active_mm);
    }
    
    
    static void reschedule_idle(struct task_struct * p)
    {
    #ifdef CONFIG_SMP
        ...
    #else /* UP */
    	int this_cpu = smp_processor_id();
    	struct task_struct *tsk;
    
    	tsk = cpu_curr(this_cpu);
    	if (preemption_goodness(tsk, p, this_cpu) > 1)
    		tsk->need_resched = 1;
    #endif
    }

3 改变调度政策和“礼让”

用户登录到系统后,第一个进程的使用调度政策为SCHED_OTHER,即无实时要求的交互式引用。其后,通过fork创建子进程时,则将此调度政策遗传给子进程。但在子进程中可以通过sched_setscheduler()来改变调度政策或者调用sched_setparam()函数来改变实时调度政策的优先级。它们的原型为:

    int sched_setscheduler(pid_t pid, int policy, struct sched_param *);
    int sched_setparam(pid_t pid, struct sched_param *);

结构体sched_param表示调度参数,只有一个成员sched_priority表示实时优先级,取值范围为[0, 99]。对于SCHED_OTHER政策来说,该值必须为0。

    struct sched_param {
	    int sched_priority;
    };

这两个系统调用内核中都是调用setscheduler(),代码在<linux/sched.c>中,代码比较简单,这里不赘述。这里说明一点:如果pid所代表的进程在可执行队列中,那么这两个系统调用会将它们挪到可执行队列的队首,使得再下次调度时占优,然后将当前进程的need_resched字段置1,立即启动一次调度。

至于sched_yield(),使当前进程为其他进程“让路”,但当前进程并没有睡眠,也没有改变当前进程在可执行队列中的位置。sched_yield()只是通过设置当前进程的need_resched字段来启动一次调度。注意,只有在当前进程的调度政策为SCHED_OTHER时,才会在调度政策上设置SCHED_YIELD标志,使得下一次调度将不会再调度该进程。但在下一次调度之后,在__schedule_tail函数中会清除SCHED_YIELD标志,还进程“自由之身”。

与主动调度不同,强制调度在适当的时机将当前进程的need_resched字段置1,然后“眼巴巴”地等待调度时间的到来。也就是发现有调度的必要到调度真正发生有一个延迟,叫做调度延迟(dispatch latency)。

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!