基於Linux0.11源代碼的操作系統內核典型處理過程分析1

基於Linux0.11源代碼的操作系統內核典型處理過程分析1

---進程1執行setup得到硬盤分區表信息

一、背景

       操作系統內核的實現複雜性毋庸置疑,其內部各個模塊間,軟件硬件間的相互協作處理十分複雜,再加上不同進程的切換調度,內核態和用戶態之間的相互轉換,使得理解其工作原理變得很困難,總有種不識廬山真面目,只緣身在此山中的感覺。

對此,我個人在學習和實踐的過程中間走了很多彎路,不敢說現在已經清楚,但至少在如何更好地學習和掌握OS原理和重要的實現細節上有一些方法性的感悟,其中最有效的一點還是確定一條需要理清楚的主幹,然後把這條主幹上的重要邏輯部分的作用搞清楚,再進一步把每個邏輯部分中的重要實現原理和具體實現方式及關鍵細節搞清楚,按照這個順序來一步一步積累,雖然OS的內涵博大精深,但當把想要理解的模塊的關鍵主幹抓住了,剩下的基於不同情況下的特殊處理方式和不同OS版本的具體實現上的差別都比較好入手理解和學習了。

       因此這裏我把自己學習開發OS的過程中發現的一些典型處理邏輯提取出來,整理成資料,實踐和總結的過程也讓自己受益不少,對很多東西也有種豁然開朗的感覺,希望對大家也能有用。

 

二、setup獲取硬盤分區表信息過程分析

1.  setup執行上下文:setup是由進程1執行的過程,實際上是進程10號進程創建出來後要做的第一件事情,雖然setup裏包含了獲取硬盤分區信息表信息,建立虛擬盤和安裝根文件系統設備等很多工作,但這裏只分析到獲取硬盤分區表信息過程,個人認爲這已經是一個可以獨立分析和理解的典型過程之一。

 

進程0 -->  進程1 --> setup <--> sys_setup

(          用戶態        )     ( 內核態 )

 

 

        可以看出,setup的實際工作是由其對應的系統調用處理函數sys_setup完成的,也即setup入口時是工作在用戶態,隨後由系統調用進入內核態執行,待OS執行完sys_setup過程處理完相應的請求後返回到用戶態,即我們看到的setup()函數返回。關於系統調用的處理流程又是一個典型且重要的過程,我會再另外總結自己的學習心得,這裏暫時放一下,並不影響我們要理解的操作過程。

 

2. sys_setup 獲取硬盤分區表信息過程概要分析

    sys_setup源代碼中關於獲得硬盤分區表信息的處理細節很多,我個人覺得最重要的是理清其如何從一塊硬盤裏得到相應的硬盤分區表信息,至於對硬盤的類型判斷和是否存在第二塊硬盤涉及的相關處理在本文中不再涉及,這不是要關心的重點。

主要流程如下:

1)     獲取硬盤自身參數信息

 這一步的作用很明顯,如果要讀硬盤分區表信息,先要知道即將處理的硬盤自身的基本信息,這樣才能對硬盤操作。而這些信息已經在OS最初啓動初期setup.s調用BIOS中斷得到並保存在物理內存0x90080開始的地方了。

 

2)     從緩衝區鏈表中獲取一個緩衝區塊

        這一步的作用是先準備好一塊內存,即將來得到的硬盤分區表信息存放的地方。獲得這個緩衝區塊後,系統會把它標識鎖上,這樣別的進程就不會有機會操作,相當於臨界資源。

 

3)     準備一條要獲取硬盤分區表信息的請求,將其放到硬盤請求等待隊列中,進程1睡眠

        Linux對設備的操作是封裝成請求(request)放到請求隊列中統一調度執行的,原因很簡單,設備屬於有限資源,要使用的人(進程)很多,要求也五花八門,因此統一請求的格式和統一調度管理是很必要的。這裏應當注意,request的數目不是無限的,在Linux0.11request最多隻有32個,如果請求數已經滿了,則當前的進程就得睡眠等待了。

        請求發出後,進程1會進入不可中斷的睡眠模式,等待所請求的信息完成,除了顯示的wake_up重置其狀態外,進程調度不會有機會重新執行它。

 

4)     硬盤設備執行相應的請求獲取所要求的信息

        這個過程也很複雜,我們忽略硬盤復位和錯誤檢查等細節,總的來說硬盤會讀取所要求的數據,每讀完一個扇區就發送一次中斷,這時相應的中斷處理函數就將本次讀的數據複製到第(1)步獲得的緩衝區中,然後檢查是否把所要求的所有扇區都讀完了,以調整緩衝區的相應指針準備下一次接收。對於Linux0.11獲取硬盤分區表信息來說需要讀的是一個塊,也就是2個扇區。

如果全部數據接收完畢則置請求等待的進程的狀態state爲就緒態。

 

5)    sys_setup執行完畢,從內核態返回進程1

 由於數據已經獲得完畢,進程的狀態被置爲就緒態(TASK_RUNNING),因此在下一次時鐘中斷到來時執行進程切換操作就會繼續執行進程1,之所以不會切換到別的進程是因爲此時系統除了特殊的進程0外就只有進程1。此時就會執行sys_setup從內核態返回的相關操作返回到進程1的用戶態了,這樣整個過程就執行完畢了。

 這裏需要指出的是,很多資料和帖子都把將進程的狀態state置爲0(TASK_RUNNING)稱爲喚醒該進程,我個人覺得這樣很容易造成誤解,因爲“喚醒”給人的感覺是這個進程已經得到了運行,但實際上它只是被置爲就緒態,表明了其可以被調度運行的資格,並沒有實際運行,即使在進程調度時也許即將切換的進程也未必就是這個進程,因此個人感覺還是稱呼其爲置爲就緒態更準確一些。

 

上面大致說明了進程1獲取硬盤分區表信息的主要流程,相應關鍵的具體函數

調用序列如下:

setup():              系統調用入口。

sys_setup():       實際的系統調用處理函數,會從內存中取出硬盤參數信息。

bread():             完成從設備(這裏是硬盤)上讀去相應信息到緩衝區的操作。

getblk():             獲取一塊緩衝區,返回其首地址。

ll_rw_block():     塊設備的讀寫函數,實際上是邏輯層,由它組織封裝設備請求操作,並不參與實際讀寫操作。

wait_on_buffer():  ll_rw_block最終的結果是發出請求,之後會返回到bread中,此時就需要等待緩衝區被填充,因此要將進程1通過sleep_on置爲不可中斷的睡眠模式

make_request():  實際封裝請求。

add_request():   將請求加入設備的請求隊列,對於本例它將立即得到執行。

do_hd_request(): 硬盤的實際請求處理函數。

hd_out():       硬盤的實際操作命令封裝函數,將觸發真正的硬盤操作。

read_intr():     硬盤讀操作完畢後的中斷處理函數,實現了將讀取的信息複製到緩衝區

end_request():  請求結束操作,如果請求處理完畢,則調用wake_up喚醒等待該請求的進程。

 

3.      一些重要細節

1)     本文討論的實際上是一個塊設備的典型操作流程,涉及到的主要是3方面:設備驅動操作、緩衝區處理、進程調度,雖然主要流程看起來比較簡單,但實際的處理細節上主要涉及到的各種分支情況基本上就屬於這3個方面,比如請求隊列已滿、所涉及的緩衝塊已存在等等,個人感覺先抓主幹,然後再參考這三個方面理細節就稍微容易一些。

 

2)     關於緩衝區的很多操作都是在關中斷方式下執行的,這樣做實際上是造成了臨界區的效果,目的是防止在臨界資源執行過程中被其他的進程或者中斷處理誤操作,典型的例子就是很多人討論的wait_on_buffer操作。

 

3)    關於請求隊列,事實上請求隊列的使用是包括兩部分的:

 一是整個請求隊列是有限的,申請時是從隊列中獲取空閒請求資源,且該隊列中預留了一定數目的讀請求,保證不會全是寫請求;

 二是封裝好實際請求之後,該請求被掛在相應設備的請求鏈表上,這樣每個設備能看見的只是自己要處理的請求鏈表,並不是整個請求隊列,具體可以參考add_request實現,它的第一個參數就是塊設備全局變量blk_dev的相應設備結構體地址,這個設備結構體裏包含了請求指針,用來索引與該設備相關的請求鏈表。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章