1. 程式人生 > >《linux核心完全剖析》筆記04-任務排程

《linux核心完全剖析》筆記04-任務排程

問題:

  • 任務排程在何時發生
  • 任務排程的基本策略是什麼
  • 任務切換時怎麼做到的

1. 隱含的睡眠佇列

建立睡眠等待佇列的原因,是因為有先後順序等待某項資源,然後要按順序喚醒程序,就要依照這裡隱含的佇列順序進行

sched.c第171行

static inline void __sleep_on(struct task_struct **p, int state)
{
    struct task_struct *tmp;

    if (!p)
        return;
    if (current == &(init_task.task))
        panic("task[0] trying to sleep"
); tmp = *p; *p = current; current->state = state; repeat: schedule(); //這裡的*p的內容,有可能不是上面的*p的內容即不是當前任務,也有可能是 //不是當前任務是因為在其它程序執行本函式時__sleep_on設定的 if (*p && *p != current) { (**p).state = 0; current->state = TASK_UNINTERRUPTIBLE; goto repeat; } if
(!*p) printk("Warning: *P = NULL\n\r"); if (*p = tmp) tmp->state=0; }

以下這張圖展示了這個佇列的大概樣子,上面方塊代表__sleep_on函式塊,需要了解這個機制,是因為很多其它的子系統也用到類似的方法,這也是瞭解睡眠機制的關鍵

隱含的睡眠佇列

2. 任務排程在何時發生

任務狀態發生改變的時候都會要重新排程,比如睡眠了一個程序(任務),自然就要重新選一個程序繼續執行,在者要是沒有任務發生改變時,時間中斷回撥函式,會在每10ms到來時執行一次

sched.c第324行

void do_timer(long cpl)
{
    ...
//current->counter > 0意思是當前任務還有分配給他的時間片 if ((--current->counter)>0) return; current->counter=0; if (!cpl) return; //重點:重新排程 schedule(); }

3. 任務排程的基本策略

sched.c第120行

void schedule(void)
{
    ...
/* this is the scheduler proper: */

    while (1) {
        c = -1;
        next = 0;
        i = NR_TASKS;
        p = &task[NR_TASKS];
        //挑選一個就緒態執行的程序且時間片最大的那個程序
        while (--i) {
            if (!*--p)
                continue;
            if ((*p)->state == TASK_RUNNING && (*p)->counter > c)
                c = (*p)->counter, next = i;
        }
        //沒有找到怎麼辦?你猜猜
        if (c) break;
        for(p = &LAST_TASK ; p > &FIRST_TASK ; --p)
            if (*p)
                (*p)->counter = ((*p)->counter >> 1) +
                        (*p)->priority;
    }
    //切換程序
    switch_to(next);
}

備註:

  • 選取超時時間最少也就是分配的時間片最多的那個程序進行切換
  • 排程效能與程序的數目成線性關係,程序越多效能越差

4. 切換任務的程式碼分析

先來看看TSS段描述符的格式:

段描述符格式

然後再來看看_TSS巨集,它是尋找GDT表中本程序的tss描述符的選擇符號值,每個任務包含一個ldt選擇符和tss選擇符

//每個任務有一個8位元組的tss選擇符和8位元組的ldt選擇子一共16位元組
//任務為n偏移2^4位元組
#define _TSS(n) ((((unsigned long) n)<<4)+(FIRST_TSS_ENTRY<<3))

切換程式碼分析

#define switch_to(n) {\
struct {long a,b;} __tmp; \
/*是不是當前任務,是就不用切換直接跳到下面標號為的地方*/
__asm__("cmpl %%ecx,_current\n\t" \
    "je 1f\n\t" \
    /*
    %dx裝載下面的_TSS(n),也就是tss段描述符的值
    放到%1,%1代表__tmp.b處
    */
    "movw %%dx,%1\n\t" \ 
    /*%ecx為儲存為切換出來的任務*/
    "xchgl %%ecx,_current\n\t" \
    /*長跳到__tmp.a處的任務,也就是上面tss儲存到__tmp.b的程序*/
    "ljmp %0\n\t" \
    "cmpl %%ecx,_last_task_used_math\n\t" \
    "jne 1f\n\t" \
    "clts\n" \
    "1:" \
    ::"m" (*&__tmp.a),"m" (*&__tmp.b), \
    "d" (_TSS(n)),"c" ((long) task[n])); \
}

結束語:

0.12版本的程序排程策略比較簡單,但給了我們理解程序排程的核心意思,最新的linux程式碼仍然使用switch_to巨集,只是已經做了非常的優化工作