1. 程式人生 > 其它 >Ch12 塊裝置I/O和緩衝區管理

Ch12 塊裝置I/O和緩衝區管理

Ch12 塊裝置I/O和緩衝區管理

12.1 塊裝置I/O緩衝區

  1. I/O緩衝的基本原理

    檔案系統使用一系列I/O緩衝區作為塊裝置的快取記憶體。

    • 當程序試圖讀取(dev, blk)標識的磁碟塊時,它首先在緩衝區快取中搜索分配給磁碟塊的緩衝區。
      • 如果該緩衝區存在並且包含有效資料,那麼它只需從緩衝區中讀取資料,而無須再次從磁碟中讀取資料塊。
      • 如果該緩衝區不存在,它會為磁碟塊分配一個緩衝區,將資料從磁碟讀人緩衝區,然後從緩衝區讀取資料。
    • 當某個塊被讀入時,該緩衝區將被儲存在緩衝區快取中,以供任意程序對同一個塊的下一次讀/寫請求使用。同樣,當程序寫入磁碟塊時,它首先會獲取一個分配給該塊的緩衝區。然後,它將資料寫入緩衝區,將緩衝區標記為髒,以延退寫入,並將其釋放到緩衝區快取中。由於髒緩衝區包含有效的資料,因此可以使用它來滿足對同一塊的後續讀/寫清求,而不會引起實際磁碟I/O,髒緩衝區只有
    • 在被重新分配到不同的塊時才會寫入磁碟。
  2. 同步寫入操作等待寫操作完成。它用於順序塊或可移動塊裝置,如USB驅動器。對於 隨機訪問裝置,例如硬碟,所有的寫操作都是延遲寫操作。在延遲寫操作中,dwrite(bp)將 緩衝區標記為髒,並將其釋放到緩衝區快取中。


12.2 I/O緩衝區管理演算法

Unix I/O緩衝區管理演算法最早出現在第6版Unix中(Ritchie和Thompson 1978 ;Lion 1996 )。

Unix緩衝區管理子系統由以下幾部分組成:

  1. I/O緩衝區:核心中的一系列NBUF緩衝區用作緩衝區快取。每個緩衝區用一個結構體表示。緩衝區結構體由兩部分組成:用於緩衝區管理的緩衝頭部分和用於資料塊的資料部分。

    typdef struct buf{
    struct buf *next_free; // freelist pointer
    struct buf *next_dev; // dev_list pointer
    int dev,blk; // assigned disk block;
    int opcode; // READ|WRITE
    int dirty; // buffer data modified
    int async; // ASYNC write flag
    int valid; // buffer data valid
    int busy; // buffer is in use
    int wanted; // some process needs this buffer
    struct semaphore lock=1; // buffer locking semaphore; value=1
    struct semaphore iodone=0; // for process to wait for I/O completion;
    char buf[BLKSIZE]; // block data area
    } BUFFER;
    BUFFER buf[NBUF], *freelist; // NBUF buffers and free buffer list
    
  2. 裝置表:每個塊裝置用一個裝置表結構表示。

    struct devtab{
    u16 dev; // major device number
    BUFFER *dev_list; // device buffer list
    BUFFER *io_queue; // device I/O queue
    } devtab[NDEV];
    
  3. 緩衝區初始化:當系統啟動時,所有I/O緩衝區都在空閒列表中,所有裝置列表和 I/O佇列均為空。

  4. 緩衝區列表

  5. Unix getblk/brelse algorithm

    1. 資料一致性:為確保資料一致性,getblk一定不能給同一個(dev, blk)分配多個緩衝區。這可以通過讓程序從休眠狀態喚醒後再次執行“重試迴圈“來實現。可以驗證分配的每個緩衝區都是唯一的一其次,髒緩衝區在重新分配之前被寫出來,這保證了資料的一致性
    2. 快取效果:快取效果可通過以下方法實現釋放的緩衝區保留在裝置列表中,以便 可能重用,標記為延遲寫入的緩衝區不會立即產生I/O,並且可以重用。緩衝區會被釋放到空閒列表的末尾,但分配是從空閒列表的前面開始的,這是基於LRU (最近最少使用)原則, 它有助於延長所分配緩衝區的使用期,從而提高它們的快取效果。
    3. 臨界區:裝置中斷處理程式可操作緩衝區列表,例如從裝置表的I/O佇列中刪除 bp,更改其狀態並呼叫brelse(bp)。所以,在getb汰和brelse中,裝置中斷在這些臨界區中會被遮蔽。這些都是隱含的,沒有在演算法中表現出來。
    /* getblk: return a buffer=(dev,blk) for exclusive use */
    BUFFER *getblk(dev,blk){
    while(1){
    (1). search dev_list for a bp=(dev, blk);
    360 12 Block Device I/O and Buffer Management
    (2). if (bp in dev_lst){
    if (bp BUSY){
    set bp WANTED flag;
    sleep(bp); // wait for bp to be released
    continue; // retry the algorithm
    }
    /* bp not BUSY */
    take bp out of freelist;
    mark bp BUSY;
    return bp;
    }
    (3). /* bp not in cache; try to get a free buf from freelist */
    if (freelist empty){
    set freelist WANTED flag;
    sleep(freelist); // wait for any free buffer
    continue; // retry the algorithm
    }
    (4). /* freelist not empty */
    bp = first bp taken out of freelist;
    mark bp BUSY;
    if (bp DIRTY){ // bp is for delayed write
    awrite(bp); // write bp out ASYNC;
    continue; // from (1) but not retry
    }
    (5). reassign bp to (dev,blk); // set bp data invalid, etc.
    return bp;
    }
    /** brelse: releases a buffer as FREE to freelist **/
    brelse(BUFFER *bp){
    if (bp WANTED)
    wakeup(bp); // wakeup ALL proc’s sleeping on bp;
    if (freelist WANTED)
    wakeup(freelist); // wakeup ALL proc’s sleeping on freelist;
    clear bp and freelist WANTED flags;
    insert bp to (tail of) freelist;
    }
    
  6. Unix演算法的缺點

    • 效率低下
    • 快取效果不可預知
    • 可能會出現飢餓
    • 該演算法使用只適用於單處理器系統的休眠/喚醒操作

12.3 新的I/O緩衝區管理演算法

P/V演算法

BUFFER *getblk(dev, blk)
{
while(1){
(1). P(free); // get a free buffer first
(2). if (bp in dev_list){
(3). if (bp not BUSY){
remove bp from freelist;
P(bp); // lock bp but does not wait
return bp;
}
// bp in cache but BUSY
V(free); // give up the free buffer
(4). P(bp); // wait in bp queue
return bp;
}
// bp not in cache, try to create a bp=(dev, blk)
(5). bp = frist buffer taken out of freelist;
P(bp); // lock bp, no wait
(6). if (bp dirty){
awrite(bp); // write bp out ASYNC, no wait
continue; // continue from (1)
}
(7). reassign bp to (dev,blk); // mark bp data invalid, not dirty
return bp;
} // end of while(1)
}
brelse(BUFFER *bp)
{
(8). if (bp queue has waiter){ V(bp); return; }
(9). if (bp dirty && free queue has waiter){ awrite(bp); return; }
(10). enter bp into (tail of) freelist; V(bp); V(free);
}