《Unix/Linux系統程式設計》第十二章學習筆記-20191304商蘇赫
阿新 • • 發佈:2021-11-18
塊裝置I/O和緩衝區管理
I/O緩衝的基本原理
- 讀取磁碟資料時,首先在緩衝區的快取中搜索,若緩衝區存在且包含有效資料則直接從緩衝區讀取。若緩衝區不存在則為該磁碟塊分配一個緩衝區,將資料讀入緩衝區,再從緩衝區讀取資料。同時會將分配的緩衝區儲存在緩衝區快取中。
- 磁碟塊被寫入時,首先為該磁碟塊分配緩衝區,並將資料寫入該緩衝區,將緩衝區標記為髒。髒緩衝區可以滿足對同一塊的後續讀/寫請求,因此不會引起實際磁碟I/O。當髒緩衝區重新分配到不同塊時才會被寫入磁碟。
相關虛擬碼:
BUFFER *bread(dev,blk) { BUFFER *bp = getblk(dev,blk); //為磁碟塊分配緩衝區 if (bp data valid) //如果該緩衝區包含有效資料 return bp; //則返回該緩衝區 bp->opcode = READ; //否則將資料讀入該緩衝區 strat_io(bp); wait for I/O completion; return bp; } write_block(dev, blk, data) //對磁碟塊進行寫入 { BUFFER *bp = bread(dev,blk); write data to bp; (synchronous write)? bwrite(bp) : dwrite(bp); } bwrite (BUFFER *bp) //同步寫入 { bp->opcode = WRITE; start_io(bp); wait for I/O completion; brelse(bp); } dwrite(BUFFER *bp) //延遲寫入 { mark bp dirty for delay_write; brelse(bp); }
同步寫入等待寫操作完成,用於順序快或者可移動塊裝置。
延遲寫入即上文提到的髒緩衝區,只有髒緩衝區重新分配到不同的磁碟塊才會被寫入磁碟。
I/O佇列,包含等待I/O操作的緩衝區。虛擬碼:
start_io(BUFFER *bp)
{
enter bp into device I/O queue;
if (bp is first buffer in I/O queue)
issue I/O command for bp to device;
}
Unix I/O緩衝區管理演算法
Unix緩衝區管理子系統
-
I/O緩衝區:緩衝區結構體由用於緩衝區管理的緩衝頭部分和用於資料塊的資料部分組成。
typdef struct buf{
struct buf *next_free;
struct buf *next_dev;
int dev,blk;
int opcode;
int dirty;
int async;
int valid;
int busy;
int wanted;
struct semaphore lock=1;
struct semaphore iodone=0;
char buf[BLKSIZE];
}BUFFER;
BUFFER buf[NBUF], *freelist;
-
裝置表:其中dev_list包含當前分配給該裝置的I/O緩衝區。io_queue包含裝置上等待I/O操作的緩衝區。
struct devtab{
u16 dev;
BUFFER *dev_list;
BUFFER *io_queue;
}devtab[NDEV];
-
緩衝區初始化:系統啟動時,所有I/O緩衝區都在空閒列表中,所有裝置列表和I/O佇列均為空。
-
緩衝區列表:緩衝區分配給磁碟時,將被插入裝置表的dev_list中。此時若緩衝區正在使用,處於繁忙,則將其從空閒列表刪除,而繁忙的緩衝區也可能在裝置表的io_queue中。當其不在繁忙時,會將其釋放回空閒列表,仍保留在dev_list中。像前文中一樣,當其重新分配時才可能從一個dev_list更改到另一個dev_list。
-
Unix getblk/brelse演算法:
BUFFER *getblk(dev,blk){
while(1){
(1).search dev_list for a bp=(dev,blk); //為標識的磁碟塊分配緩衝區
(2).if (bp in dev_list){ //若緩衝區在裝置表的dev_list中
if (bp BUSY){ //若該緩衝區處於繁忙狀態
set bp WANTED flag;
sleep(bp); //等待該緩衝區釋放
continue; //重試該演算法
}
take bp out of freelist; //若該緩衝區處於空閒狀態,將緩衝區從空閒列表中刪除
mark bp BUSY; //將該緩衝區標記為繁忙
reurn bp;
}
(3).
if (freelist empty){ //若沒有緩衝區處於空閒狀態
set bp WANTED flag;
sleep(freelist); //等待一個空閒狀態的緩衝區
continue; //重試該演算法
}
/*若存在空閒狀態的緩衝區*/
(4).
bp =first bp taken out of freelist; //分配空閒列表最前面的緩衝區
mark bp BUSY; //將其標記為繁忙
if (bp DIRTY){ //若為延遲寫入
awrite(bp);
continue;
}
(5).
reassign bp to (dev,blk); //重新分配時,將緩衝區資料寫入磁碟
return bp;
}
}
brelse (BUFFER *bp){
if (bp WANTED){
wakeup(bp);
if (freelist WANTED)
wakeup(freelist);
clear bp and freelist WANTED flags;
insert bp to (tail of) freelist;
}
Unix演算法說明
- 資料一致性:getblk()不能給一個同一個表示的磁碟塊分配多個緩衝區,同時髒緩衝區在重新分配之前被寫出來。
- 緩衝效果:緩衝區釋放回空閒列表,仍保留在dev_list中;延遲寫入的緩衝區不會立即引起實際磁碟I/O;釋放的緩衝區在空閒列表末尾,分配是從空閒列表前面開始。
- 臨界區:在getblk和brelse中,裝置中斷在臨界區中會背遮蔽。
Unix演算法缺點
- 效率低下:依賴於重試迴圈。
- 快取效果不可預知
- 飢餓現象:每個程序都有嘗試的機會,但不能保證成功。
- 只適用於單處理系統的休眠/喚醒操作
在訊號量上使用PV來實現程序同步,將用緩衝區本身來表示每個緩衝區的訊號量。
PV演算法
BUFFER *getblk(dev,blk)
{
while(1){
(1). p(free); //首先獲取一個空閒緩衝區
(2). if (bp in dev_list){ //若該緩衝區在裝置表的dev_list中
(3). if (bp not BUSY){ //且處於空閒狀態
remove from freelist; //將其從空閒列表中刪除
P(bp); //lock bp not wait
return bp;
}
//若緩衝區存在快取內且繁忙
V(free); //放棄空閒緩衝區
(4). P(bp); //在緩衝佇列中等待
return bp;
}
//緩衝區不在快取中,為磁碟建立一個緩衝區
(5). bp = first buffer taken out of freelist;
P(bp); //lock bp no wait
(6). if (bp dirty){ //若為髒緩衝區
awrite(bp); //緩衝區寫入磁碟
continue;
}
(7). reassign bp to (dev,blk); //重新分配
return bp;
}
}
brelse (BUFFER *bp)
{
(8).if (bp queue has waiter) {V(bp); return; }
(9).if (bp dirty && freee queue has waiter){ awrite(bp); return;}
(10).enter bp into (tail of) freelist; V(bp); V(free);
}
- 緩衝區唯一性
- 無重試迴圈
- 無不必要喚醒
- 快取效果
- 無死鎖和飢餓
PV演算法缺點
- 1.一旦沒有空閒狀態緩衝區,所有請求程序都將被阻塞在getblk()中的(1)。
- 2.當程序從空閒列表訊號量佇列中喚醒時,可能發現所需緩衝區已經存在,但處於繁忙,此時將在(4)處被阻塞。