就緒態和運行態之間的切換
當前占用CPU的進程,只有調用了schedule()函數,才會由運行態轉變為就緒態,schedule()函數選擇狀態為TASK_RUNNING的進程,
然后調用switch函數,將cpu切換到所選定的進程。
schedule()函數可能會在以下三種情況下調用:
(1) 用戶態時發生時鐘中斷
如果當前進程是用戶態進程,并且當前進程的時間片用完,那么中斷處理函數do_timer()就會調用schedule()函數,
這相當于用戶態的進程被搶斷了。
如果當前的進程屬于內核態進程,那么該進程是不會被搶占的。schedule() 函數不是系統調用,用戶程序不能直接調用,
但是放在時間中斷函數中,就能夠調用。所以在時間中斷中調用schedule()是必要的,這樣就保證用戶進程不會永久地占有CPU。
(2)系統調用時,相應的sys_xxxx()函數返回之后。
這種情況是為了處理運行在內核態的進程,應用程序一般是通過系統調用進入內核態,因此,linux系統調用處理函數在結束的時候,
int 0x80 中斷函數會檢查當前進程的時間片和狀態,如果時間片用完或者進程的狀態不為RUNNING ,就會調用schedule()函數。
由此可見,如果系統的某個系統調用處理函數或者中斷處理異常永遠不退出,那么整個系統就會死鎖,任何進程都無法運行。
(3)在睡眠函數內
當進程等待的資源還不可用的時候,它就進入了睡眠狀態,并且調用schedule()函數再次調用CPU。
#define switch_to(n) {\
// __tmp用來構造ljmp的操作數。該操作數由4字節偏移和2字節選擇符組成。當選擇符
// 是TSS選擇符時,指令忽略4字節偏移。
// __tmp.a存放的是偏移,__tmp.b的低2字節存放TSS選擇符。高兩字節為0。
// ljmp跳轉到TSS段選擇符會造成任務切換到TSS選擇符對應的進程。
// ljmp指令格式是 ljmp 16位段選擇符:32位偏移,但如果操作數在內存中,順序正好相反。

// %0 內存地址 __tmp.a的地址,用來放偏移
// %1 內存地址 __tmp.b的地址,用來放TSS選擇符
// %2 edx 任務號為n的TSS選擇符
// %3 ecx task[n]

struct
{long a,b;} __tmp; \
__asm__("cmpl %%ecx,current\n\t" \ // 如果要切換的任務是當前任務
"je 1f\n\t" \ // 直接退出
"movw %%dx,%1\n\t" \ // 把TSS選擇符放入__tmp.b中
"xchgl %%ecx,current\n\t" \ // 讓current指向新進程的task_struct
"ljmp *%0\n\t" \ // 任務切換在這里發生,CPU會搞定一切
"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])); \
}

進程調度函數

/**//****************************************************************************/

/**//* 功能:進程調度。 */

/**//* 先對alarm和信號進行處理,如果某個進程處于可中斷睡眠狀態,并且收 */

/**//* 到信號,則把進程狀態改成可運行。之后在處可運行狀態的進程中挑選一個 */

/**//* 并用switch_to()切換到那個進程 */

/**//* 參數:(無) */

/**//* 返回:(無) */

/**//****************************************************************************/
void schedule(void)


{
int i,next,c;
struct task_struct ** p;


/**//* check alarm, wake up any interruptible tasks that have got a signal */
// 首先處理alarm信號,喚醒所有收到信號的可中斷睡眠進程
for(p = &LAST_TASK ; p > &FIRST_TASK ; --p)

if (*p)
{
// 如果進程設置了alarm,并且alarm已經到時間了

if ((*p)->alarm && (*p)->alarm < jiffies)
{
// 向該進程發送SIGALRM信號
(*p)->signal |= (1<<(SIGALRM-1));
(*p)->alarm = 0; // 清除alarm
}
//可屏蔽信號位圖BLOCKABLE定義在sched.c第24行,(~(_S(SIGKILL) | _S(SIGSTOP)))
// 說明SIGKILL和SIGSTOP是不能被屏蔽的。
// 可屏蔽信號位圖 & 當前進程屏蔽的信號位圖 = 當前進程實際屏蔽的信號位圖
// 當前進程收到的信號位圖 & ~當前進程實際屏蔽的信號位圖
// = 當前進程收到的允許相應的信號位圖
// 如果當前進程收到允許相應的信號,并且當前進程處于可中斷睡眠態
// 則把狀態改成運行態,參與下面的選擇過程
if (((*p)->signal & ~(_BLOCKABLE & (*p)->blocked)) &&
(*p)->state==TASK_INTERRUPTIBLE)
(*p)->state=TASK_RUNNING;
}


/**//* this is the scheduler proper: */
// 下面是進程調度的主要部分

while (1)
{
c = -1;
next = 0;
i = NR_TASKS;
p = &task[NR_TASKS];

while (--i)
{ // 遍歷整個task[]數組
if (!*--p) // 跳過task[]中的空項
continue;
// 尋找剩余時間片最長的可運行進程,
// c記錄目前找到的最長時間片
// next記錄目前最長時間片進程的任務號
if ((*p)->state == TASK_RUNNING && (*p)->counter > c)
c = (*p)->counter, next = i;
}
// 如果有進程時間片沒有用完c一定大于0。這時退出循環,執行 switch_to任務切換
if (c) break;
// 到這里說明所有可運行進程的時間片都用完了,則利用任務優先級重新分配時間片。
// 這里需要重新設置所有任務的時間片,而不光是可運行任務的時間片。
// 利用公式:counter = counter/2 + priority
for(p = &LAST_TASK ; p > &FIRST_TASK ; --p)
if (*p)
(*p)->counter = ((*p)->counter >> 1) +
(*p)->priority;
// 整個設置時間片過程結束后,重新進入進程選擇過程
}
// 當的上面的循環退出時,說明找到了可以切換的任務
switch_to(next);
}

2 運行態和睡眠態之間的轉化
當進程等待資源或者事件的時候,就進入了睡眠狀態,有兩種不同的睡眠狀態, 不可中斷睡眠狀態和可中斷睡眠狀態。
處于可中斷睡眠狀態的進程,不光可以由wake_up 喚醒,還可以由信號喚醒,在schedule()函數中,會把處于可中斷睡眠狀態的并且接收到信號的
進程變為運行狀態。linux0.11的可中斷睡眠狀態可以由以下三中函數進入:
(1)調用interruptiable_sleep_on()函數、
(2)調用sys_pause()函數。
(3)調用sys_waitpid()函數。
進程要進入不可中斷睡眠狀態,必須調用sleep_on()函數。進程調用wake_up()函數,將處于不可中斷狀態的進程喚醒。

/**//****************************************************************************/

/**//* 功能:當前進程進入不可中斷睡眠態,掛起在等待隊列上 */

/**//* 參數:p 等待隊列頭 */

/**//* 返回:(無) */

/**//****************************************************************************/
void sleep_on(struct task_struct **p)


{
struct task_struct *tmp; // tmp用來指向等待隊列上的下一個進程

if (!p) // 無效指針,退出
return;
if (current == &(init_task.task)) // 進程0不能睡眠
panic("task[0] trying to sleep");
tmp = *p; // 下面兩句把當前進程放到等待隊列頭,等待隊列是以堆棧方式
*p = current; // 管理的。后到的進程等在前面
current->state = TASK_UNINTERRUPTIBLE; // 進程進入不可中斷睡眠狀態
schedule(); // 進程放棄CPU使用權,重新調度進程
// 當前進程被wake_up()喚醒后,從這里開始運行。
// 既然等待的資源可以用了,就應該喚醒等待隊列上的所有進程,讓它們再次爭奪
// 資源的使用權。這里讓隊列里的下一個進程也進入運行態。這樣當這個進程運行
// 時,它又會喚醒下下個進程。最終喚醒所有進程。
if (tmp)
tmp->state=0;
}

以下是喚醒函數

/**//****************************************************************************/

/**//* 功能:喚醒等待隊列上的頭一個進程 */

/**//* 參數:p 等待隊列頭 */

/**//* 返回:(無) */

/**//****************************************************************************/
void wake_up(struct task_struct **p)


{

if (p && *p)
{
(**p).state=0; // 把隊列上的第一個進程設為運行態
*p=NULL; // 把隊列頭指針清空,這樣失去了都其他等待進程的跟蹤。
// 一般情況下這些進程遲早會得到運行。
}
}
