標籤:
Linux核心中進程的強制性調度,也就是非自願的、被動的、剝奪式的調度,主要是由時間引起的。前面講過這種調度發生在中斷,異常,系統調用從系統空間返回使用者空間的前夕,也就是在ret_with_reschedule可以看出,此時是否真的調用schedule(),最終還要取決於當前進程task_struct結構中的need_resched是否為1(非0),因此,問題就結為當前進程的need_resched是在什麼情況下才置成1的。主要有如下幾種情況:
1、在時鐘中斷的服務程式中,發現當前進程(連續)啟動並執行時間過長。
2、在喚醒一個睡眠中的進程是,發現被喚醒的進程比當前進程更有資格運行。
3、一個進程通過系統調用改變調度政策或禮讓。這種情況實際上應該被視為主動的、自願的調度,因此這樣的系統調用會引起立即調度。
對第一種情況,在Linux核心原始碼情景分析-中斷上半部一文中,do_timer中斷服務程式,調用了update_process_times,代碼如下:
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;elsekstat.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;} 如果此時在使用者態發生中斷,進入核心態,p->counter減為0,那麼p->need_resched就置為1,中斷返回時就會強制調度。
如果此時發生系統調用,進入核心態,再發生中斷,p->counter減為0,那麼p->need_resched就置為1,中斷返回後,然後系統調用返回時就會強制調度。
如果此時在使用者態發生異常,進入核心態,再發生中斷,p->counter減為0,那麼p->need_resched就置為1,中斷返回後,然後異常返回時就會強制調度。
對第二種情況,喚醒一個進程時,代碼如下:
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;//進程的狀態設定為TASK_RUNNINGif (task_on_runqueue(p))goto out;add_to_runqueue(p);//將進程掛入runqueuereschedule_idle(p);out:spin_unlock_irqrestore(&runqueue_lock, flags);}
static void reschedule_idle(struct task_struct * p){ ......int this_cpu = smp_processor_id();struct task_struct *tsk;tsk = cpu_curr(this_cpu);//擷取當前進程的task_struct資料結構if (preemption_goodness(tsk, p, this_cpu) > 1)//比較當前進程和被喚醒的進程的綜合權值tsk->need_resched = 1;//如果被喚醒的進程的綜合權值比當前進程的大,那麼強制調度}
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);} 如果發生了系統調用,進入核心態,發生上面的過程,那麼在系統調用返回時會強制調度。
如果在使用者態發生了異常,進入核心態,發生上面的過程,那麼在異常返回時會強制調度。
如果在使用者態發生了中斷,進入核心態,不能調用wake_up_process。
對於第三種情況,實際上應被視為自願的讓出。但是,從核心代碼的形式上看,也是通過相同的辦法,將當前進程的need_resched標誌置為1,使得在進程返回使用者空間前夕發生調度,所以也放在這一節。此類系統調用有兩個,一個是sched_setscheduler(),另一個是sched_yield()。
系統調用sched_setscheduler()的作用是改變進程的調度政策。使用者登入到系統後,第一個進程的適用調度政策為SCHED_OTHER,也就是預設為無即時要求的互動式應用。在fork()建立新進程時則將此進程適用的調度政策遺傳給了子進程。但是,使用者可以通過系統調用sched_setscheduler()改變其適用調度政策。
sched_setscheduler,核心態對應的代碼如下:
asmlinkage long sys_sched_setscheduler(pid_t pid, int policy, struct sched_param *param){return setscheduler(pid, policy, param);}asmlinkage long sys_sched_setparam(pid_t pid, struct sched_param *param){return setscheduler(pid, -1, param);}
static int setscheduler(pid_t pid, int policy, struct sched_param *param){struct sched_param lp;struct task_struct *p;int retval;retval = -EINVAL;if (!param || pid < 0)goto out_nounlock;retval = -EFAULT;if (copy_from_user(&lp, param, sizeof(struct sched_param)))//從使用者空間把sched_param結構拷貝到lpgoto out_nounlock;/* * We play safe to avoid deadlocks. */read_lock_irq(&tasklist_lock);spin_lock(&runqueue_lock);p = find_process_by_pid(pid);//通過pid找到task_structretval = -ESRCH;if (!p)goto out_unlock;if (policy < 0)//policy為-1policy = p->policy;//維持原來的政策else {retval = -EINVAL;if (policy != SCHED_FIFO && policy != SCHED_RR &&policy != SCHED_OTHER)//必須是這三種政策之一goto out_unlock;}/* * Valid priorities for SCHED_FIFO and SCHED_RR are 1..99, valid * priority for SCHED_OTHER is 0. */retval = -EINVAL;if (lp.sched_priority < 0 || lp.sched_priority > 99)goto out_unlock;if ((policy == SCHED_OTHER) != (lp.sched_priority == 0))//如果政策是SCHED_OTHER,sched_priority必須是0goto out_unlock;retval = -EPERM;if ((policy == SCHED_FIFO || policy == SCHED_RR) && !capable(CAP_SYS_NICE))goto out_unlock;if ((current->euid != p->euid) && (current->euid != p->uid) && !capable(CAP_SYS_NICE))goto out_unlock;retval = 0;p->policy = policy;p->rt_priority = lp.sched_priority;if (task_on_runqueue(p))move_first_runqueue(p);//從可執行進程隊列的當前位置移到隊列的前部,使其在調度時處於較為有利的地位current->need_resched = 1;//強制調度out_unlock:spin_unlock(&runqueue_lock);read_unlock_irq(&tasklist_lock);out_nounlock:return retval;}
另一個系統調用sched_yield(),使運行中的進程可以為其他進程"讓路",但並不進入睡眠。核心的實現sys_sched_yield,代碼如下:
asmlinkage long sys_sched_yield(void){/* * Trick. sched_yield() first counts the number of truly * ‘pending‘ runnable processes, then returns if it‘s * only the current processes. (This test does not have * to be atomic.) In threaded applications this optimization * gets triggered quite often. */int nr_pending = nr_running;#if CONFIG_SMPint i;// Substract non-idle processes running on other CPUs.for (i = 0; i < smp_num_cpus; i++)if (aligned_data[i].schedule_data.curr != idle_task(i))nr_pending--;#else// on UP this process is on the runqueue as wellnr_pending--;#endifif (nr_pending) {//正在等待的啟動並執行進程數/* * This process can only be rescheduled by us, * so this is safe without any locking. */if (current->policy == SCHED_OTHER)current->policy |= SCHED_YIELD;//SCHED_YIELD標誌位置1,在_schedule_tail清0current->need_resched = 1;//強制調度}return 0;}
第三種情況,只有發生在,使用sched_setscheduler()或者sched_yield()系統調用時,系統調用返回時會強制調度。
Linux核心原始碼情景分析-強制性調度