Linux核心原始碼情景分析-wait()、schedule()

來源:互聯網
上載者:User

標籤:

    父進程執行wait4,並調用schedule切換到子進程:

    wait4(child, NULL, 0, NULL);

    像其他系統調用一樣,wait4()在核心中的入口是sys_wait4(),代碼如下:

asmlinkage long sys_wait4(pid_t pid,unsigned int * stat_addr, int options, struct rusage * ru)//pid為子進程的進程號{int flag, retval;DECLARE_WAITQUEUE(wait, current);struct task_struct *tsk;if (options & ~(WNOHANG|WUNTRACED|__WNOTHREAD|__WCLONE|__WALL))return -EINVAL;add_wait_queue(current->wait_chldexit,&wait);repeat:flag = 0;current->state = TASK_INTERRUPTIBLE;//父進程設定為可中斷等待狀態read_lock(&tasklist_lock);tsk = current;do {//第一層迴圈struct task_struct *p; for (p = tsk->p_cptr ; p ; p = p->p_osptr) {//第二層迴圈,從最年輕的子進程開始沿著由各個task_struct結構中的指標p_osptr所形成的鏈,找尋與所等待對象的pid相符的子進程、或符合其他一些條件的子進程if (pid>0) {if (p->pid != pid)//找到pid相符的子進程continue;} else if (!pid) {if (p->pgrp != current->pgrp)continue;} else if (pid != -1) {if (p->pgrp != -pid)continue;}/* Wait for all children (clone and not) if __WALL is set; * otherwise, wait for clone children *only* if __WCLONE is * set; otherwise, wait for non-clone children *only*.  (Note: * A "clone" child here is one that reports to its parent * using a signal other than SIGCHLD.) */if (((p->exit_signal != SIGCHLD) ^ ((options & __WCLONE) != 0))    && !(options & __WALL))continue;flag = 1;//說明pid是當前進程的子進程號switch (p->state) {case TASK_STOPPED:if (!p->exit_code)continue;if (!(options & WUNTRACED) && !(p->ptrace & PT_PTRACED))continue;read_unlock(&tasklist_lock);retval = ru ? getrusage(p, RUSAGE_BOTH, ru) : 0; if (!retval && stat_addr) retval = put_user((p->exit_code << 8) | 0x7f, stat_addr);if (!retval) {p->exit_code = 0;retval = p->pid;}goto end_wait4;//子進程處於停止狀態,goto end_wait4case TASK_ZOMBIE:current->times.tms_cutime += p->times.tms_utime + p->times.tms_cutime;current->times.tms_cstime += p->times.tms_stime + p->times.tms_cstime;read_unlock(&tasklist_lock);retval = ru ? getrusage(p, RUSAGE_BOTH, ru) : 0;if (!retval && stat_addr)retval = put_user(p->exit_code, stat_addr);if (retval)goto end_wait4; retval = p->pid;if (p->p_opptr != p->p_pptr) {write_lock_irq(&tasklist_lock);REMOVE_LINKS(p);p->p_pptr = p->p_opptr;SET_LINKS(p);do_notify_parent(p, SIGCHLD);write_unlock_irq(&tasklist_lock);} elserelease_task(p);goto end_wait4;////子進程處於僵死狀態,goto end_wait4default:continue;//否則繼續第二層迴圈}}if (options & __WNOTHREAD)break;tsk = next_thread(tsk);//從同一個thread_group隊列中找到下一個線程的task_struct結構} while (tsk != current);read_unlock(&tasklist_lock);if (flag) {//如果pid不是當前進程的子進程,直接到end_wait4retval = 0;if (options & WNOHANG)goto end_wait4;retval = -ERESTARTSYS;if (signal_pending(current))goto end_wait4;schedule();goto repeat;}retval = -ECHILD;end_wait4:current->state = TASK_RUNNING;remove_wait_queue(¤t->wait_chldexit,&wait);return retval;}

    下列條件之一得到滿足時才結束,goto end_wait4:

    1、所等待的子進程的狀態變成TASK_STOPPED,TASK_ZOMBIE;

    2、所等待的子進程存在,可不在上述兩個狀態,而調用參數options中的WHONANG標誌位為1,或者當前進程接受到了其他的訊號;

    3、進程號pid的那個進程根本不存在,或者不是當前進程的子進程。

    否則,當前進程將其自身的狀態設成TASK_INTERRUPTIBLE,並調用schedule()。


    schedule,代碼如下:

asmlinkage void schedule(void){struct schedule_data * sched_data;struct task_struct *prev, *next, *p;struct list_head *tmp;int this_cpu, c;if (!current->active_mm) BUG();//如果當前進程是個核心線程,那就沒有使用者空間,所以其mm指標為0,運行時就要暫時借用在它之前啟動並執行那個進程的active_mm,所以active_mm一定不等於0need_resched_back:prev = current;//當前進程賦值給prevthis_cpu = prev->processor;if (in_interrupt())//只能由進程在核心中主動調用,或者在當前進程從系統空間返回使用者空間的前夕被動地發生,而不能在一個中斷服務程式的內部發生goto scheduling_in_interrupt;release_kernel_lock(prev, this_cpu);/* Do "administrative" work here while we don‘t hold any locks */if (softirq_active(this_cpu) & softirq_mask(this_cpu))//處理非強制中斷goto handle_softirq;handle_softirq_back:/* * ‘sched_data‘ is protected by the fact that we can run * only one process per CPU. */sched_data = & aligned_data[this_cpu].schedule_data;spin_lock_irq(&runqueue_lock);/* move an exhausted RR process to be last.. */if (prev->policy == SCHED_RR)//見注釋1goto move_rr_last;move_rr_back:switch (prev->state) {case TASK_INTERRUPTIBLE://TASK_UNINTERRUPTIBLE和TASK_INTERRUPTIBLE的主要區別就在於此,TASK_UNINTERRUPTIBLE即使有訊號等待處理,也不將其修改成TASK_RUNNINGif (signal_pending(prev)) {//有訊號等待處理時要將其改成TASK_RUNNINGprev->state = TASK_RUNNING;break;}default:del_from_runqueue(prev);//sys_wait4中調用schedule時的狀態為TASK_INTERRUPTIBLE,所以這裡把這進程從可執行隊列中撤下來case TASK_RUNNING://如果是TASK_RUNNING,即繼續運行,那麼這裡不需要有什麼特殊處理}prev->need_resched = 0;//剛開始need_reshced清0/* * this is the scheduler proper: */repeat_schedule:/* * Default process to select.. */next = idle_task(this_cpu);//目前是進程0,指向已知最佳的候選進程c = -1000;//目前是最低的權值,指向這個進程的綜合權值if (prev->state == TASK_RUNNING)//如果當前進程想要繼續運行goto still_running;still_running_back:list_for_each(tmp, &runqueue_head) {//遍曆可執行隊列runqueue中的每個進程p = list_entry(tmp, struct task_struct, run_list);if (can_schedule(p, this_cpu)) {//單cpu中can_schedule永遠為1int weight = goodness(p, this_cpu, prev->active_mm);//進程所具有的權值if (weight > c)//挑選出權值最大的c = weight, next = p;}}/* Do we need to re-calculate counters? */if (!c)//如果當前已經選擇的進程(權值最高的進程)權值為0,那麼就要重新計算各個進程的時間配額,參考注釋2goto recalculate;/* * from this point on nothing can prevent us from * switching to the next task, save this fact in * sched_data. */sched_data->curr = next;        ......spin_unlock_irq(&runqueue_lock);if (prev == next)//挑選出來的next就是當前進程goto same_process;        ......kstat.context_swtch++;/* * there are 3 processes which are affected by a context switch: * * prev == .... ==> (last => next) * * It‘s the ‘much more previous‘ ‘prev‘ that is on next‘s stack, * but prev is set to (the just run) ‘last‘ process by switch_to(). * This might sound slightly confusing but makes tons of sense. */prepare_to_switch();{struct mm_struct *mm = next->mm;struct mm_struct *oldmm = prev->active_mm;if (!mm) {if (next->active_mm) BUG();next->active_mm = oldmm;atomic_inc(&oldmm->mm_count);enter_lazy_tlb(oldmm, next, this_cpu);} else {if (next->active_mm != mm) BUG();switch_mm(oldmm, mm, next, this_cpu);}if (!prev->mm) {prev->active_mm = NULL;mmdrop(oldmm);}}/* * This just switches the register state and the * stack. */switch_to(prev, next, prev);__schedule_tail(prev);same_process:reacquire_kernel_lock(current);if (current->need_resched)//前面已經把當前進程的need_resched清0,如果現在又成了非0,則一定發生了中斷並且情況發生了變化goto need_resched_back;return;recalculate:{struct task_struct *p;spin_unlock_irq(&runqueue_lock);read_lock(&tasklist_lock);for_each_task(p)//對所有進程的迴圈,對不在runqueue的進程,也提升其時間配額,參考注釋3p->counter = (p->counter >> 1) + NICE_TO_TICKS(p->nice);read_unlock(&tasklist_lock);spin_lock_irq(&runqueue_lock);}goto repeat_schedule;still_running:c = goodness(prev, this_cpu, prev->active_mm);//那麼挑選候選進程時以當前進程此刻的權值開始。這意味著,相對於權值相同的其它進程來說,當前進程優先next = prev;goto still_running_back;handle_softirq:do_softirq();goto handle_softirq_back;move_rr_last:if (!prev->counter) {//如果時間配額用完了prev->counter = NICE_TO_TICKS(prev->nice);move_last_runqueue(prev);//從可執行進程隊列runqueue中當前的位置上移到隊列的末尾,同時恢複其最初的時間配額,對於相同優先順序的進程,調度的時候排在前面的進程優先,所以這使隊列中具有相同優先順序的其它進程有了優勢}goto move_rr_back;scheduling_in_interrupt:printk("Scheduling in interrupt\n");BUG();return;}
    注釋1:

    為了適應各種不同應用的需要,核心在此基礎上實現了三種不同的政策:SCHED_FIFO、SCHED_RR以及SCHED_OTHER。每個進程都有自己使用的調度政策,並且進程還可以通過系統調用sched_setscheduler()設定自己使用的調度政策。其中SCHED_FIFO適合於時間性要求比較強、但每次運行所需的時間比較短的進程,即時的應用大都具有這樣的特點。SCHED_RR中的“RR”表示“Round Robin”,是輪流的意思,這種政策適合比較大、也就是每次運行需時較長的進程。而除此二者之外的SCHED_OTHER,則為傳統的調度政策,比較適合於互動分時應用。

    當前進程prev的調度政策為SCHED_RR,即輪換調度。SCHED_RR和SCHED_FIFO都是基於優先順序的調度政策,可是在怎樣調度具有相同優先順序的進程這個問題上二者有區別。調度策略為SCHED_FIFO的進程一旦受到調度而開始運行之後,就要一直運行到自願讓出或被優先順序更高的進程剝奪為止。對於每次受到調度時要求已耗用時間不長的進程,這樣並沒有什麼不妥。可是,如果是受到調度後可能會長時間啟動並執行進程,那樣就不公平了。這種不公正性是對具有相同優先順序的進程而言。所以,對這樣的進程應該實行SCHED_RR調度政策,這種政策在相同的優先順序上實行輪換調度。

 

    注釋2:

    此時所有runqueue的進程權值都為0,由於除init進程和調用了sched_yield()的進程以外,每個進程的權值最低為0,所以只要隊列中有其他就緒進程存在就不可能為負數。這裡要指出,隊裡中所有其他進程的許可權都已降到0,說明這些進程的調度政策都是SCHED_OTHER,因為若有政策為SCHED_FIFO或SCHED_RR的進程存在,則權值至少也有100。


    注釋3:

    for_each_task()是對所有進程的迴圈,而不是僅對就緒進程隊列的迴圈。對於不在就緒進程隊列中的非即時進程,這裡得到了提升其時間配額、從而提升其綜合權值的機會。不過,對綜合權值的這種提升是很有限的,每次重新計算都將原有的時間配額減半,再與NICE_TO_TICKS(p->nice)相加,這樣就決定了重新計算以後的綜合權值永遠也不可能達到NICE_TO_TICKS(p->nice)的兩倍。因此,即使經過很長時間的"韜光養晦",也不能達到可與即時進程競爭的地步(綜合權值至少是1000),所以只是對非即時進程之間的競爭有意義。至於即時進程,時間配額的增加並不會提升其綜合權值,而且對於SCHED_FIFO進程則連時間配額也是沒有意義的。

    









Linux核心原始碼情景分析-wait()、schedule()

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.