date: 2014-11-02 13:16linux
在《進程的調度與切換》一節中,咱們提到,強制調度的兩個條件:安全
調度時機:系統調用返回到用戶空間前夕,以及中斷或者異常服務程序返回到用戶空間前夕。可能會有人擔憂以下兩種狀況:其一若是進程「躲在」「安全地帶」內核空間中不出來,調度器豈不僅能乾着急?好在內核的設計與實現避免了這個問題。其二:若是進程在用戶空間運行,既不調用系統調用函數,也沒有中斷與異常發生,豈不是也沒法進行強轉調度?別忘了,系統的時鐘中斷在默默地堅守着哩。函數
必要條件:當前進程的need_resched字段必須非0。該字段必須由內核去設置,爲了讓調度器有效運轉起來,內核必須「瞅準時機」「見縫插針」地設置該字段。設置的時機包括:this
時鐘中斷服務程序do_timer_interrupt()中調用do_timer(),對單CPU結構後者調用update_process_times()來調整當期進程與時間相關的一些運行參數,代碼在<kernel/timer.c>中:spa
/* * 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; }
<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字段,代碼以下:rest
/* * 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 }
用戶登陸到系統後,第一個進程的使用調度政策爲SCHED_OTHER,即無實時要求的交互式引用。其後,經過fork建立子進程時,則將此調度政策遺傳給子進程。但在子進程中能夠經過sched_setscheduler()來改變調度政策或者調用sched_setparam()函數來改變實時調度政策的優先級。它們的原型爲:code
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)。