《linux核心完全剖析》筆記04-任務排程
阿新 • • 發佈:2019-02-12
問題:
- 任務排程在何時發生
- 任務排程的基本策略是什麼
- 任務切換時怎麼做到的
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巨集,只是已經做了非常的優化工作