前面的博文中詳細講述了xv6的檔案系統,其中使用位圖塊來進行磁碟block的 管理,但是對於block內容進行讀寫則需要更底層的磁碟驅動程式,同時考慮到磁碟讀寫的速度非常慢(相對於記憶體讀寫),因此我們有必要對磁碟的資料塊進行快取。
整個磁碟的快取是採用類似物件池(object pool)的框架來實現的。
對於每個磁碟block,對應乙個對應的資料結構buffer,具體如下:
通過dev和sector就可以唯一確定該block的位址,從而進行讀寫。讀寫的粒度為block(512位元組),相應的資料儲存在data中。每個buffer含有乙個標誌flags,如果為b_busy說明這個buffer正在被乙個程序使用,如果b_valid則說明buffer中的資料已經準備好了,b_dirty說明是讀取磁碟資料還是將資料寫入磁碟。
一、磁碟驅動的實現
類似下圖,xv6維持了乙個磁碟請求佇列idequeue。它每次將頭部的buf傳送到磁碟裝置,而每次新加入的buffer放在佇列的尾部。
根據buffer中的元資料及標誌來讀寫扇區,對應的函式為iderw,它將新新增的buffer新增到請求佇列的末尾,並且這個程序因為這個buffer進入睡眠狀態。
iderw的關鍵**如下所示:
其中將buffer的資料及標誌傳送到磁碟裝置的**idestart如下:
如果操作時讀取,則在磁碟控制器資料準備好時會觸發乙個中斷通知中斷處理程式獲取資料;如果操作時寫入,則在資料成功寫入後觸發乙個中斷
二、磁碟中斷處理程式
xv6磁碟中斷處理程式呼叫ideintr來程序處理。它通過查詢請求佇列idequeue的第乙個buffer得知已發生的操作。如果是讀取操作,則將磁碟控制器中已經準備好的資料讀到data中。現在buffer中的資料已經準備好了,則設定為b_valid並清除b_dirty,並且喚醒因為這個buffer而進入睡眠狀態的程序。接著ideintr傳遞請求佇列中的下乙個buffer到磁碟裝置進行處理。
三、磁碟快取
xv6使用快取來同步各個程序之間訪問磁碟block,它通過bread中獨佔訪問來實現:即如果有兩個程序通過bread同時讀取具有相同dev和sector的未使用的磁碟block,那麼其中乙個程序會立即返回乙個已鎖住的buffer,而另乙個程序則需要等待前乙個程序訪問完後呼叫brelse時
傳送的解鎖訊號。
bread非常簡單,**如下:
bread呼叫bget來獲取並返回給定dev和sector的乙個已鎖住的buffer。任何時候只有乙個程序訪問bcache,並且只有成功得到乙個已鎖住的buffer才釋放這個自旋鎖,從而保證程序訪問磁碟的同步性。
它採用的演算法非常精妙:
1. 在bcache中使用next指標(見最上面的圖)從前往後掃瞄快取bcache,嘗試找到乙個和給定資料相同的buffer。若有並已經被別的程序使用(b_busy置位),則因為這個buffer進入睡眠狀態,否則,置位b_busy,從而返回乙個已鎖住的buffer。若沒有找到,則進入2
2. 在bcache中使用pre指標(見最上面的圖)從後往前掃瞄快取bcache,嘗試找到乙個未被使用的buffer(b_busy=0),如果找到則修改相應的dev和sector,並且置位b_busy,並且置位b_valid和b_dirty位,表明bread需要重新整理buffer中的資料而不是使用先前buffer中的資料。
--->需要特別注意的是如果找到了但是該buffer已使用而進入睡眠的程序,後來被喚醒了,則需要從新來過,因為有可能這個buffer被復用到別的dev或sector了,只好重新來過咯
整個過程如下:
一旦bread返回了乙個已鎖住的buffer給呼叫者,那麼呼叫者就可以獨佔的使用buffer來進行讀寫操作。當呼叫者處理完這個buffer,必須使用brelse來釋放它,brelse將這個buffer移動來列表的最前面,清除b_busy位並喚醒其他因為這個buffer而進入睡眠的程序。這個演算法就是經典的lru演算法,它的精妙之處在於考慮到程式的區域性性原理:因為這樣的移動,使得icache中最前面的buffer是最近使用的,而最後面的是最久未使用,從而從前往後查詢最近使用的buffer,從後往前查詢最久未使用的buffer,效率非常高。
四、思考及小結
如果乙個block快取,從磁碟中讀取時同步還是非同步?並簡述過程
這是乙個非同步的過程,其過程如下:
1)首先呼叫bread,在icache中找到乙個未被使用(b_busy)的buffer。
2) 修改該buffer的dev和sector,並置位b_busy,從而返回乙個已鎖住的buffer
3)將該buffer加入到請求列表idequeue的末尾,並因為這個buffer而進入睡眠狀態
4)當輪到改buffer時,idestart將該buffer的資料及標誌傳送到磁碟裝置
5)磁碟裝置處理完成後,產生乙個中斷
6)磁碟中斷處理程式ideintr從磁碟控制器讀取已準備好的資料到data中,並設定buffer的標誌位b_valid,並喚醒因為這個buffer而進入睡眠的程序
7)呼叫者處理完buffer後,最後呼叫brelse釋放這個buffer。並喚醒因為這個buffer而進入睡眠的程序。
XV6記憶體布局
1 規定系統最大物理記憶體為16mb。2 應用程式使用0 640k虛擬記憶體,640k 1m是對映io空間,1m以上的高位記憶體只有核心可以使用,4064 4096最高32m位址空間對映到不同的裝置。3 每個應用程式都有自己的頁表,頁表的前160項 0 640k 記憶體是自己對映的,從640k到最高...
Xv6 多程序程式設計
參考 xv6 riscv book 1.1 processes and memory 系統呼叫 描述int fork 建立乙個程序 通過複製當前程序 返回子程序 pid int exit int status 終止當前程序,status 會被報告給 wait 無返回值 int wait int st...
安裝並啟動xv6
從github上拉取xv6的原始碼 git clone git sudo apt get install qemu輸入下面的命令 objdump i第二行應該輸出 elf32 i386 輸入下面的命令,gcc m32 print libgcc file name應輸出 usr lib gcc i48...