1. 程式人生 > >linux程序排程解析

linux程序排程解析

1排程器的啟動通常有兩種方式:
A. 主動式
在核心應用中直接呼叫schedule()。這通常發生在因等待核心事件而需要將程序置於掛起(休眠)狀態的時候--這時應該主動請求排程以方便其他程序使用CPU。下面就是一個主動排程的例子:
/* 節選自[drivers/input/mousedev.c] mousedev_read() */
add_wait_queue(&list->mousedev->wait, &wait);
current->state = TASK_INTERRUPTIBLE;
while (!list->ready) {
if (file->f_flags & O_NONBLOCK) {
retval = -EAGAIN;
break;
}
if (signal_pending(current)) {
retval = -ERESTARTSYS;
break;
}
schedule();
}
current->state = TASK_RUNNING; /* 這一句實際上可以省略,因為程序的狀態在喚醒過程中就已經恢復到TASK_RUNNING了 */
remove_wait_queue(&list->mousedev->wait, &wait);
其過程通常可分為四步:
將程序新增到事件等待佇列中;
置程序狀態為TASK_INTERRUPTIBLE(或TASK_UNINTERRUPTIBLE);
在迴圈中檢查等待條件是否滿足,不滿足則呼叫schedule(),滿足了就退出迴圈;
將程序從事件等待佇列中刪除。
從"排程器工作流程"中我們知道,排程器會將處於休眠狀態的程序從就緒佇列中刪除,而只有就緒佇列中的程序才有可能被排程到。將該程序重新放到就緒佇列中的動作是在事件發生時的"喚醒"過程中完成的。在以上所示的滑鼠驅動中,滑鼠中斷將呼叫mousedev_event()函式,該函式的最後就會使用wake_up_interruptible()喚醒等待滑鼠事件的所有程序。wake_up_interruptible()將最終呼叫try_to_wake_up()函式:
/* 節選自[kernel/sched.c] */
static inline int try_to_wake_up(struct task_struct * p, int synchronous)
{
unsigned long flags;
int success = 0;
spin_lock_irqsave(&runqueue_lock, flags);
p->state = TASK_RUNNING;
if (task_on_runqueue(p))
goto out;
add_to_runqueue(p); /* 新增到就緒佇列中 */
if (!synchronous || !(p->cpus_allowed & (1 << smp_processor_id())))
reschedule_idle(p); /* 這種情況下呼叫wake_up(),synchronous總為0,此時,*/
/* 如果本CPU不適合執行該程序,則需要呼叫reschedule_idle()尋找合適的CPU */
success = 1;
out:
spin_unlock_irqrestore(&runqueue_lock, flags);
return success;
}
這時啟動schedule()就是被動的了。 

2 程序排程方法

一〉排程策略

排程策略考慮的幾個因素:

1>IO型任務與cpu密集型任務

   linux為IO密集型任務提供高優先順序,cpu密集型任務提供低優先順序,使得IO密集型任務在IO完成時得到迅速反映

2>程序優先順序:

linux實施了動態優先順序排程策略。有兩種優先順序引數:nice value和 real time 優先順序。nice value由使用者指定,範圍在-19--20之間,real time優先順序從0--99。擁有實時優先順序的程序比基於nice value的程序優先順序高,用於需要高度實時性的程序。基於這些優先順序引數,linux在執行時動態調整程序的優先順序

3)時間片

程序連續執行的一段時間稱為時間片。長的時間片使得系統的互動性降低,過短的時間片增加了程序切換的開銷,同時不利於cache的作用發揮。linux給高優先順序的程序較長的時間片,低優先順序的程序較短的時間片。

二〉資料結構

runqueue結構維護了一個cpu上的所有程序的資訊。任何程序只屬於一個runqueue。runqueue中包含active的優先順序佇列和iexpire優先順序佇列。對renqueue進行讀寫操作時,需要先對之加鎖。

優先順序對列資料結構為

struct prio_array{

 int nr_active; //該結構中所有程序的個數

 unsigned long bitmap[BITMAP_SIZE];//優先順序點陣圖,用於快速查詢下一個執行的程序

struct list_head queue[MAX_PRIO];//優先順序對列向量。向量中的每個元素為對應優先順序的程序佇列頭

};

三種排程策略
(1)SCHED_OTHER。SCHED_OTHER是面向普通程序的時間片輪轉策略。採用該策略時,系統為處於TASK_RUNNING狀態的每個程序分配一個時間片。當時間片用完時,程序排程程式再選擇下一個優先順序相對較高的程序,並授予CPU使用權。
(2)SCHED_FIFO。SCHED_FIFO策略適用於對響應時間要求比較高,執行所需時間比較短的實時程序。採用該策略時,各實時程序按其進入可執行佇列的順序依次獲得CPU。除了因等待某個事件主動放棄CPU,或者出現優先順序更高的程序而剝奪其CPU之外,該程序將一直佔用CPU執行。
(3)SCHED_RR。SCHED_RR策略適用於對響應時間要求比較高,執行所需時間比較長的實時程序。採用該策略時,各實時程序按時間片輪流使用CPU。當一個執行程序的時間片用完後,程序排程程式停止其執行並將其置於可執行佇列的末尾。

3程序排程依據
Linux只有一個可執行佇列,處於TASK_RUNNING狀態的實時程序和普通程序都加入到這個可執行佇列中。Linux的程序排程採用了動態優先順序和權值調控的方法,既可實現上述三種排程策略,又能保證實時程序總是比普通程序優先使用CPU。描述程序的資料結構task_struct中用以下幾個資料作為排程依據:
Struct task_struct {
    ……
    volatile long need_resched;  /*是否需要重新排程*/
  long counter;  /*程序當前還擁有的時間片*/
long nice;  /*普通程序的動態優先順序,來自UNIX系統*/
unsigned long policy; /*程序排程策略*/
unsigned long rt_priority;  /*實時程序的優先順序*/
……
};
counter的值是動態變化的,程序執行時,每一個時鐘滴答後,其值減1。當counter值為0時,表示該程序時間片已用完,該程序回到可執行佇列中,等待再次排程。
為保證實時程序優於普通程序,Linux採取加權處理法。在程序排程過程中,每次選取下一個執行程序時,排程程式首先給可執行佇列中的每個程序賦予一個權值weight。普通程序的權值就是其counter和優先順序nice的綜合,而實時程序的權值是它的rt_priority的值加1000,確保實時程序的權值總能大於普通程序。排程程式檢查可執行佇列中所有程序的權值,選取權值最大者作為下一個執行程序,保證了實時程序優先於普通程序獲得CPU。Linux使用核心函式goodness()對程序進行加權處理:
Static inline goodness
(struct task_struct * pint this_cpu, struct mm_struct *this_mm)
{
  Int weight;
  Weight=-1;
/*判斷如果任務的排程策略被置為SCHED_YIELD的話,則置權值為-1,返回。
系統呼叫SCHED_YIELD表示為“禮讓”程序,其權值為最低*/
 If (p->policy & SCHED_YIELD)
        goto out;
/*先對普通程序進行處理(由於多數是普通程序,這樣做有利於提高系統效率)*/
If (p->policy==SCHED_OTHER){
      weight=p->counter;         /*返回權值為程序的counter值*/
/*如果當前程序的counter為0,則表示當前程序的時間片已用完,直接返回*/
 If (! weight)
        Goto out;
#Ifdef CONFIG_SMP
 If (p->processor==this_cpu)
                 Weight+=PROC_CHANGE_PENALTY;
#Endif
/*對程序權值進行微調,如果程序的記憶體空間使用當前正在執行的程序的記憶體空間,
則權值額外加1*/
      If (p->mm==this_mm||! p->mm)
              Weight+=1;
/*將權值加上20與程序優先順序nice的差。普通程序的權值主要由counter值和nice值組成*/
 Weight+=20-p->nice;
  Goto out;
}
 /*對實時程序進行處理,返回權值為rt_priority+1000,確保優先順序高於普通程序*/
Weight=1000+p->rt_priority;
 Out:
return weight; 
}
從goodness()函式可以看出,對於普通程序,其權值主要取決於剩餘的時間配額和nice兩個因素。nice的規定取值範圍為19~-20,只有特權使用者才能把nice值設為負數,而表示式(20-p->nice)掉轉方向成為1~40。所以,綜合的權值在時間片尚未用完時基本上是兩者之和。如果是核心程序,或者其使用者空間與當前程序相同,則權值將額外加1作為獎勵。對於實時程序,其權值為1000+p->rt_priority,當p->counter達到0時該程序將移到佇列的尾部,但其優先順序仍不少於1000。可見當有實時程序就緒時,普通程序是沒機會執行的。
由此可以看出,通過goodness()函式,Linux從優先考慮實時程序出發,實現了多種排程策略的統一處理,其設計思想可謂非常巧妙。
<script src="http://pagead2.googlesyndication.com/pagead/show_ads.js" type="text/javascript"> </script>