第12章 塊裝置I/O和緩沖區管理
12.1 塊裝置I/O緩沖區
讀寫普通檔案的算法:
依賴于兩個關鍵操作get_block 和 put_block,這兩個操作将磁盤塊讀寫到記憶體緩沖區中。
I/O緩沖的基本原理:
檔案系統使用一系列I/O緩沖區作為塊裝置的緩存記憶體。當程序試圖讀取(dev,blk)辨別的磁盤塊時,他首先在緩沖區緩存中搜尋配置設定給磁盤塊的緩沖區。如果緩沖區中存在并且包含有效資料,那麼它隻需要從緩沖區中讀取資料,而無需再次從磁盤中讀取資料塊。如果該緩沖區不存在,他會為磁盤塊配置設定一個緩沖區,将資料從磁盤讀入到緩沖區,然後從緩沖區讀取資料。
當程序寫入磁盤塊時,他首先會擷取一個配置設定給該塊的緩沖區。然後将資料寫入緩沖區,将緩沖區标記為髒,以延遲寫入,并将起釋放到緩沖區緩存中,并将其釋放到緩沖區緩存中。
BUFFER緩沖區的結構類型(return bp;)

getblk(dev,blk)從緩沖區緩存中配置設定一個指定給(dev,blk)的緩沖區
bread(dev,blk)傳回一個包含有效資料的緩沖區指針
brelse(bp)将緩沖區釋放回緩沖區緩存
同步寫入:用于順序塊或可移動裝置,如USB驅動器。
延遲寫入:對于随機通路裝置,所有寫操作都是延遲寫操作。在延遲寫操作中,dwrite(bp)将緩沖區标記為髒,并将其釋放到緩沖區緩存中。
髒緩沖區隻有在被重新配置設定到不同的磁盤塊時才會被寫入磁盤。
12.2 Unix I/O緩沖區管理算法
Unix緩沖區區管理子系統
(1)I/O緩沖區:核心中的一系列NBUF緩沖區用作緩沖區緩存。每一個緩沖區用結構體辨別。
(2)裝置表:每個塊裝置用一個裝置表結構表示。
(3)緩沖區初始化:當系統啟動時,所有I/O緩沖區都在空閑清單中,所有裝置清單和I/O隊列均為空。
(4)緩沖區清單:當緩沖區配置設定給(dev,blk)時,它會被插入裝置表的dev_list中。如果緩沖區目前正在使用,則會将其标記為USY并從空閑清單中删除。繁忙緩沖區也可能回在裝置表的I/O隊列中。
(5)Unix getblk/brelse算法
Unix算法的具體說明
(1)資料一緻性:為了確定資料一緻性,getblk一定不能給同一個( dev, blk)配置設定多個緩沖區。這可以通過讓程序從休眠狀态喚醒後再次執行“重試循環”來實作。讀者可以驗證配置設定的每個緩沖區都是唯一的。其次,髒緩沖區在重新配置設定之前被寫出來,這保證了資料的一緻性。
(2)緩存效果:緩存效果可通過以下方法實作。釋放的緩沖區保留在裝置清單中,以便可能重用。标記為延遲寫入的緩沖區不會立即産生IO,并且可以重用。緩沖區會被釋放到空閑清單的末尾,但配置設定是從空閑清單的前面開始的。這是基于LRU(最近最少使用)原則,它有助于延長所配置設定緩沖區的使用期,進而提高它們的緩存效果。
(3)臨界區:裝置中斷處理程式可操作緩沖區清單,例如從裝置表的I/O隊列中删除bp,更改其狀态并調用brelse(bp)。是以,在getblk和 brelse中,裝置中斷在這些臨界區中會被屏蔽。這些都是隐含的,但沒有在算法中表現出來。
Unix算法的缺點
雖然Unix算法非常簡單和簡潔,但它也有以下缺點。
(1)效率低下;該算法依賴于重試循環。例如,釋放緩沖區可能會喚醒兩組程序:需要釋放的緩沖區的程序,以及隻需要空閑緩沖區的程序。由于隻有一個程序可以擷取釋放的緩沖區,是以,其他所有被喚醒的程序必須重新進入休眠狀态。從休眠狀态喚醒後,每個被喚醒的程序必須從頭開始重新執行算法,因為所需的緩沖區可能已經存在。這會導緻過多的程序切換。
(2)緩存效果不可預知:在Unix算法中,每個釋放的緩沖區都可被擷取。如果緩沖區由需要空閑緩沖區的程序擷取,那麼将會重新配置設定緩沖區,即使有些程序仍然需要目前的緩沖區。
(3)可能會出現饑餓:Unix算法基于“自由經濟”原則,即每個程序都有嘗試的機會,但不能保證成功。是以,可能會出現程序饑餓。
(4)該算法使用隻适用于單處理器系統的休眠/喚醒操作。-
12.3 新的I/O緩沖區管理算法
在信号量上使用P/V來實作程序同步,而不是使用休眠/喚醒。與休眠/喚醒相比,信号量的主要優點時:
(1)計數信号量可用來表示可用資源的數量,例如:空閑緩沖區的數量。
(2)當多個程序等待一個資源時,信号量上的V操作隻會釋放一個等待程序,該程序不必重試,因為它保證擁有資源。
使用信号量的緩沖區管理算法
假設有一個單處理器核心(一次運作一個程序)。使用計數信号量上的P/V來設計滿足以下要求的新的緩沖區管理算法:
(1)保證資料一緻性。
(2)良好的緩存效果。
(3)高效率:沒有重試循環,沒有不必要的程序“喚醒”。(4)無死鎖和饑餓。
首先定義以下信号量
12.4 PV算法
empty[s2] = m;
full[s2] = 0;
while(1)//寫程序
{
for(i = 0; i< s2; i++)
{
P(empty[i]);
}
P(mutex);
消息放入緩沖區;
V(mutex);
for(i = 0; i< s2; i++)
{
V(full[i]);
}
}
while(1)//讀程序
{
P(full[i]);
P(mutex);
讀取緩沖區;
V(mutex);
V(empty[i]);
}
【特點】:
- (1)緩沖區唯一性。
- (2)無重試循環。
- (3)無不必要喚醒。
- (4)緩存效果。
openeuler實踐
perror ( )函數
perror(s) 用來将上一個函數發生錯誤的原因輸出到标準裝置(stderr)。參數 s 所指的字元串會先列印出,後面再加上錯誤原因字元串。此錯誤原因依照全局變量errno的值來決定要輸出的字元串。
源代碼:
運作截圖
問題與解決思路
問題:為什麼要設立輸出緩沖區,直接輸出結果不是更快麼,而且這樣還比較節省記憶體?
解答:為什麼要有輸入輸出緩沖區
這篇部落格中寫到:資料緩沖是因為資料被輸入後在處理的時候需要一定的時間,為了輸入接着輸出,需要緩沖,預先處理一部分資訊,然後開始輸出,在輸出的同時處理後面的輸入和處理。
問題:setbuf()函數和setvbuf()函數的差別是什麼?
解答:setbuf()和setvbuf()函數的實際意義在于:使用者打開一個檔案後,可以建立自己的檔案緩沖區,而不必使用fopen()函數打開檔案時設定的預設緩沖區。這樣就可以讓使用者自己來控制緩沖區,包括改變緩沖區大小、定時重新整理緩沖區、改變緩沖區類型、删除流中預設的緩沖區、為不帶緩沖區的流開辟緩沖區等。
詳細連結:https://www.jb51.net/article/71720.htm