进程调度之7:need_resched与强制调度
2018-07-16 19:29
429 查看
date: 2014-11-02 13:16
在《进程的调度与切换》一节中,我们提到,强制调度的两个条件:
调度时机:系统调用返回到用户空间前夕,以及中断或者异常服务程序返回到用户空间前夕。可能会有人担心如下两种情况:其一如果进程“躲在”“安全地带”内核空间中不出来,调度器岂不只能干着急?好在内核的设计与实现避免了这个问题。其二:如果进程在用户空间运行,既不调用系统调用函数,也没有中断与异常发生,岂不是也无法进行强转调度?别忘了,系统的时钟中断在默默地坚守着哩。
必要条件:当前进程的need_resched字段必须非0。该字段必须由内核去设置,为了让调度器有效运转起来,内核必须“瞅准时机”“见缝插针”地设置该字段。设置的时机包括:
其一:在时钟中断服务程序中,当发现当前进程连续运行太长时间时;
其二:当唤醒一个睡眠中的进程,发现被唤醒的进程比当前进程更有资格运行时;
其三:一个进程通过系统调用改变调度政策(sched_setscheduler)或表示礼让(sched_yield)时。这种情况实际上应该被视为主动的、自愿的调度,因为这些系统调用在返回用户空间时会引起立即调度(而上面两种情况只是设置好了need_resched字段,至于何时能有“调度时机”还是个未知数)。
可见,唤醒一个进程只是将它的状态改为TASK_RUNNING然后加入可运行队列。
reschedule_idle()判断被唤醒的进程是否比当前进程更有资格运行,如果是,则设置need_resched字段,代码如下:
结构体sched_param表示调度参数,只有一个成员sched_priority表示实时优先级,取值范围为[0, 99]。对于SCHED_OTHER政策来说,该值必须为0。
这两个系统调用内核中都是调用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)。
在《进程的调度与切换》一节中,我们提到,强制调度的两个条件:
调度时机:系统调用返回到用户空间前夕,以及中断或者异常服务程序返回到用户空间前夕。可能会有人担心如下两种情况:其一如果进程“躲在”“安全地带”内核空间中不出来,调度器岂不只能干着急?好在内核的设计与实现避免了这个问题。其二:如果进程在用户空间运行,既不调用系统调用函数,也没有中断与异常发生,岂不是也无法进行强转调度?别忘了,系统的时钟中断在默默地坚守着哩。
必要条件:当前进程的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)。
相关文章推荐
- 进程调度API之should_resched
- 进程调度API之cond_resched
- 【操作系统】实验三 进程调度模拟程序
- 实验三、进程调度模拟程序实验
- 进程调度之HRN
- linux进程管理与调度(一)
- 调研进程调度算法
- 操作系统——进程调度之短进程优先
- Linux嵌入式 -- 内核 - 进程控制 和 调度
- Linux入门——进程调度
- Linux进程的管理与调度(四) -- Linux下的进程类别以及其创建方式
- Linux课程笔记 | 进程3——进程调度
- x86体系结构下Linux-2.6.26的进程调度和切换
- 进程调度模拟(C语言)
- Linux进程调度之CFS
- 操作系统回忆录:进程、线程、资源调度
- Linux内核设计与实现 读书笔记(4)进程的调度
- x86体系结构下Linux-2.6.26的进程调度和切换
- 使用动态优先权的进程调度算法的模拟
- 操作系统 进程调度之轮换调度(RR调度)