1. 程式人生 > >linux0.11程序睡眠sleep_on函式和喚醒wake_up函式分析

linux0.11程序睡眠sleep_on函式和喚醒wake_up函式分析

核心中的這兩個函式主要用於訪問資源時的同步操作。高速緩衝區的訪問就是其中的一個例子:如果兩個程序都要訪問同一個緩衝塊,那麼其中的一個程序就必然睡眠等待,直到該緩衝塊被釋放才可訪問。趙炯博士所著的linux0.11核心完全註釋一書中也是對該問題進行詳細的討論,但是我在閱讀這部分內容的時候存在一些疑問,在此發表下自己的見解。

先貼出這兩個函式

void sleep_on(struct task_struct **p)
{
	struct task_struct *tmp;

	if (!p)
		return;
	if (current == &(init_task.task))
		panic("task[0] trying to sleep");
	tmp = *p;
	*p = current;
	current->state = TASK_UNINTERRUPTIBLE;
	schedule();
	if (tmp)
		tmp->state=0;
}

void wake_up(struct task_struct **p)
{
	if (p && *p) {
		(**p).state=0;
		*p=NULL;
	}
}
我們以塊裝置請求項佇列的同步操作作為例子來分析。

首先我們已經知道程序在訪問塊裝置的時候是通過一箇中間層去呼叫相應的驅動程式完成塊裝置的讀寫操作,這個中間層也即塊裝置請求項佇列request,linux0.11一共為這個佇列分配了32項。但是如果系統中有大量塊裝置訪問請求的時候,32項也是不夠用的,也就是說再有程序訪問塊裝置的話,該程序就必須睡眠等待,直到請求項佇列中出現空項。因此,linux在核心中定義了一個等待請求項的任務結構指標wait_for_request,該指標指向因request資源不足而睡眠等待的任務。

為了更加清晰的說明問題,這裡假定request佇列已滿,並且此時還有2個程序也要請求塊裝置操作。首先是程序A在請求過程中,發現request佇列已滿,則該程序就會進入睡眠狀態,具體會呼叫sleep_on(&wait_for_request)函式。在sleep函式中首先斷言p值和current的有效性,然後語句tmp=*p會將區域性指標tmp指向wait_for_request(首次呼叫為NULL),語句*p=current會將wait_for_request指向當前程序,最後將當前進行狀態置為不可中斷狀態並schedule,到此程序A將被掛起。注意程序A掛起時的程式碼位置,下次恢復執行後將從schedule語句的下一條語句繼續執行,也即tmp判斷語句處。到這裡,sleep_on函式使得程序A被掛起之外,還改變兩個任務結構指標變數的值,一個是全域性的等待任務指標wait_for_request,它現在指向任務A的程序結構體,另一個是程序A的區域性指標tmp,它指向為NULL。

分析到這裡可能還是不能看出更多的端倪,好繼續往下分析。任務A一直因申請不到資源處於等待睡眠狀態,而這時又有一個程序B執行塊裝置請求,由於請求項佇列處於滿狀態,因此程序B同樣需要呼叫sleep_on函式進行睡眠等待,由於任務A已經修改了wait_for_request指標的值(指向程序A),所以程序B在執行到schedul語句的時候,其tmp指標將指向程序A,而wait_for_request指標此時將指向程序B。

到這裡很明顯可以推出:如果某個程序因無法請求到request資源而進入程序睡眠狀態時,全域性指標wait_for_request始終指向最後一個進入睡眠的程序,而每個程序中的區域性指標tmp將指向上一個睡眠的程序,其中第一個程序的tmp指向NULL。

另外一個非常值得注意的地方是:在sleep_on函式中,程序在呼叫schedule函式切換到其它程序後,而該函式並未返回,也就是說其區域性指標變數tmp仍然保留在該程序的堆疊上,當該程序恢復的時候將從schedule後面一條語句繼續執行。

到此程序A和程序B因無法申請到request資源而被掛起,而程序C這時完成了塊裝置的訪問操作,它將呼叫end_request函式釋放其佔用的request資源,同時將喚醒因無法申請到request資源的程序,具體將呼叫wake_up(&wait_for_request)。由於程序B比程序A後進入睡眠,所以wait_for_request指標指向程序B,因此很明顯wake_up函式的(**p).state=0語句會將程序B喚醒,除此之外還將wait_for_request置為NULL(初始狀態)。我們知道程序B恢復之後將繼續執行未完的sleep_on函式,也即判斷tmp指標項是否為NULL,前面已分析程序B的tmp指向程序A,因此tmp->state=0語句將會喚醒程序A。也就是說程序B幫助程序A獲得了執行的資格,另外由於程序A的tmp指標指向NULL,所以程序A直接從sleep_on函式返回。

到這裡也很明顯的推出:如果某一個程序呼叫wake_up(&wait_for_request)函式,那麼所有因請求request資源而進入睡眠的程序將反向依次恢復執行(最後一個睡眠的程序先恢復),最後一個睡眠的程序由wait_for_request指標進行恢復,後面睡眠程序由前一個睡眠程序的tmp指標進行恢復,直到恢復第一個睡眠的程序為止。這個過程就像多米諾骨牌一樣,一個程序恢復所有程序都恢復,而這個引線就是區域性指標tmp。至於恢復後的程序是否能夠再次請求到request資源就不再管了,如果還是請求不到,那繼續sleep_on就行了。

而趙博士認為每次呼叫wake_up僅恢復wait_for_request指標指向的最後睡眠的那個任務(在tmp判斷前應加上*p=tmp),所以認為linus對於sleep_on函式的實現存在bug,而經過以上的分析,可以看出linus的這種實現是可行的。