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

// %0 內(nèi)存地址 __tmp.a的地址,用來放偏移
// %1 內(nèi)存地址 __tmp.b的地址,用來放TSS選擇符
// %2 edx 任務(wù)號(hào)為n的TSS選擇符
// %3 ecx task[n]

struct
{long a,b;} __tmp; \
__asm__("cmpl %%ecx,current\n\t" \ // 如果要切換的任務(wù)是當(dāng)前任務(wù)
"je 1f\n\t" \ // 直接退出
"movw %%dx,%1\n\t" \ // 把TSS選擇符放入__tmp.b中
"xchgl %%ecx,current\n\t" \ // 讓current指向新進(jìn)程的task_struct
"ljmp *%0\n\t" \ // 任務(wù)切換在這里發(fā)生,CPU會(huì)搞定一切
"cmpl %%ecx,last_task_used_math\n\t" \ // 除進(jìn)程第一次被調(diào)度外,以后進(jìn)程從就緒
// 態(tài)返回運(yùn)行態(tài)后,都從這里開始運(yùn)行。因
// 而返回到的是內(nèi)核運(yùn)行態(tài)。
"jne 1f\n\t" \
"clts\n" \
"1:" \
::"m" (*&__tmp.a),"m" (*&__tmp.b), \
"d" (_TSS(n)),"c" ((long) task[n])); \
}

進(jìn)程調(diào)度函數(shù)

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

/**//* 功能:進(jìn)程調(diào)度。 */

/**//* 先對(duì)alarm和信號(hào)進(jìn)行處理,如果某個(gè)進(jìn)程處于可中斷睡眠狀態(tài),并且收 */

/**//* 到信號(hào),則把進(jìn)程狀態(tài)改成可運(yùn)行。之后在處可運(yùn)行狀態(tài)的進(jìn)程中挑選一個(gè) */

/**//* 并用switch_to()切換到那個(gè)進(jìn)程 */

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

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

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


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


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

if (*p)
{
// 如果進(jìn)程設(shè)置了alarm,并且alarm已經(jīng)到時(shí)間了

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


/**//* this is the scheduler proper: */
// 下面是進(jìn)程調(diào)度的主要部分

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

while (--i)
{ // 遍歷整個(gè)task[]數(shù)組
if (!*--p) // 跳過task[]中的空項(xiàng)
continue;
// 尋找剩余時(shí)間片最長(zhǎng)的可運(yùn)行進(jìn)程,
// c記錄目前找到的最長(zhǎng)時(shí)間片
// next記錄目前最長(zhǎng)時(shí)間片進(jìn)程的任務(wù)號(hào)
if ((*p)->state == TASK_RUNNING && (*p)->counter > c)
c = (*p)->counter, next = i;
}
// 如果有進(jìn)程時(shí)間片沒有用完c一定大于0。這時(shí)退出循環(huán),執(zhí)行 switch_to任務(wù)切換
if (c) break;
// 到這里說明所有可運(yùn)行進(jìn)程的時(shí)間片都用完了,則利用任務(wù)優(yōu)先級(jí)重新分配時(shí)間片。
// 這里需要重新設(shè)置所有任務(wù)的時(shí)間片,而不光是可運(yùn)行任務(wù)的時(shí)間片。
// 利用公式:counter = counter/2 + priority
for(p = &LAST_TASK ; p > &FIRST_TASK ; --p)
if (*p)
(*p)->counter = ((*p)->counter >> 1) +
(*p)->priority;
// 整個(gè)設(shè)置時(shí)間片過程結(jié)束后,重新進(jìn)入進(jìn)程選擇過程
}
// 當(dāng)?shù)纳厦娴难h(huán)退出時(shí),說明找到了可以切換的任務(wù)
switch_to(next);
}

2 運(yùn)行態(tài)和睡眠態(tài)之間的轉(zhuǎn)化
當(dāng)進(jìn)程等待資源或者事件的時(shí)候,就進(jìn)入了睡眠狀態(tài),有兩種不同的睡眠狀態(tài), 不可中斷睡眠狀態(tài)和可中斷睡眠狀態(tài)。
處于可中斷睡眠狀態(tài)的進(jìn)程,不光可以由wake_up 喚醒,還可以由信號(hào)喚醒,在schedule()函數(shù)中,會(huì)把處于可中斷睡眠狀態(tài)的并且接收到信號(hào)的
進(jìn)程變?yōu)檫\(yùn)行狀態(tài)。linux0.11的可中斷睡眠狀態(tài)可以由以下三中函數(shù)進(jìn)入:
(1)調(diào)用interruptiable_sleep_on()函數(shù)、
(2)調(diào)用sys_pause()函數(shù)。
(3)調(diào)用sys_waitpid()函數(shù)。
進(jìn)程要進(jìn)入不可中斷睡眠狀態(tài),必須調(diào)用sleep_on()函數(shù)。進(jìn)程調(diào)用wake_up()函數(shù),將處于不可中斷狀態(tài)的進(jìn)程喚醒。

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

/**//* 功能:當(dāng)前進(jìn)程進(jìn)入不可中斷睡眠態(tài),掛起在等待隊(duì)列上 */

/**//* 參數(shù):p 等待隊(duì)列頭 */

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

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


{
struct task_struct *tmp; // tmp用來指向等待隊(duì)列上的下一個(gè)進(jìn)程

if (!p) // 無效指針,退出
return;
if (current == &(init_task.task)) // 進(jìn)程0不能睡眠
panic("task[0] trying to sleep");
tmp = *p; // 下面兩句把當(dāng)前進(jìn)程放到等待隊(duì)列頭,等待隊(duì)列是以堆棧方式
*p = current; // 管理的。后到的進(jìn)程等在前面
current->state = TASK_UNINTERRUPTIBLE; // 進(jìn)程進(jìn)入不可中斷睡眠狀態(tài)
schedule(); // 進(jìn)程放棄CPU使用權(quán),重新調(diào)度進(jìn)程
// 當(dāng)前進(jìn)程被wake_up()喚醒后,從這里開始運(yùn)行。
// 既然等待的資源可以用了,就應(yīng)該喚醒等待隊(duì)列上的所有進(jìn)程,讓它們?cè)俅螤?zhēng)奪
// 資源的使用權(quán)。這里讓隊(duì)列里的下一個(gè)進(jìn)程也進(jìn)入運(yùn)行態(tài)。這樣當(dāng)這個(gè)進(jìn)程運(yùn)行
// 時(shí),它又會(huì)喚醒下下個(gè)進(jìn)程。最終喚醒所有進(jìn)程。
if (tmp)
tmp->state=0;
}

當(dāng)前進(jìn)程的task_struct
|
以下是喚醒函數(shù)

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

/**//* 功能:?jiǎn)拘训却?duì)列上的頭一個(gè)進(jìn)程 */

/**//* 參數(shù):p 等待隊(duì)列頭 */

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

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


{

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