天天看點

UNIX核心(3):磁盤讀寫以及磁盤緩沖的利弊

原文轉自:http://blog.chinaunix.net/uid-7471615-id-83762.html

讀取磁盤塊

為了讀取一個磁盤塊,程序需要調用getblk來擷取緩沖。如果能在hash Q上找到該緩沖,那麼核心就能夠立刻獲得資料。否則,核心将向磁盤驅動送出一個讀請求并且休眠,直到資料就緒。

讀取資料塊的僞代碼如下:

BufferHeader * bread (int fs_blk_no)

{

getblk(fs_blk_no);

if (buffer的資料有效)

傳回該buffer;

發起讀磁盤請求;

sleep (讀磁盤完成事件);

}

Linux 0.99.15的實作如下:

struct buffer_head * bread(dev_t dev, int block, int size)

struct buffer_head * bh;

if (!(bh = getblk(dev, block, size))) { // 幾乎不可能發生

printk("VFS: bread: READ error on device %d/%d\n",

MAJOR(dev), MINOR(dev));

return NULL;

if (bh->b_uptodate)

return bh;

ll_rw_block(READ, 1, &bh); // 發起一個讀請求(該函數可以發起多個讀請求)

wait_on_buffer(bh); // sleep

brelse(bh); // 找到不合适的buffer

磁盤驅動将向磁盤控制器發出讀請求,控制器接受請求并開始與驅動傳遞資料。當資料傳輸完成,控制器發出一個中斷來通知CPU資料就緒。該中斷的處理例程将喚醒等待的程序。此時資料也處于可用狀态。需要使用資料的程序便能夠通路該緩沖中的資料。一旦使用完,程序便釋放緩沖。

預讀資料

為了提高性能,根據臨近原則,可以預讀下一個資料塊。此時采用異步讀,完成之後磁盤控制器發出一個中斷,在進行中斷過程中需要将包含有預讀資料的buffer釋放以供後續使用(如果不釋放該buffer,那麼就需要由發起異步讀的程序來釋放,而異步讀的政策就是,發出讀取請求後就不管了,因為請求者不需要該資料)。在異步讀進行中,首先讀取第一塊資料(與bread()一樣),然後讀取第二塊buffer。第一塊資料的讀取需要同步,而第二塊資料(預讀資料)為異步讀取,由中斷處理例程根據該讀取請求為異步而直接釋放該buffer。其僞代碼如下:

BufferHeader * breada(int blk_no_immediate, int blk_no_asyn)

if (blk_no_immediate不在緩沖中)

getblk(blk_no_immediate);

if (buffer的資料無效)

if (blk_no_asyn不在緩沖中)

getblk(blk_no_asyn);

else

brelse(getblk()傳回的buffer;

sleep (讀blk_no_immediate磁盤塊完成事件);

Linux 0.99.15的實作(省略一些“次要”代碼):

struct buffer_head * breada(dev_t dev,int first, ...)

va_list args;

unsigned int blocksize;

struct buffer_head * bh, *tmp;

va_start(args,first);

……

if (!(bh = getblk(dev, first, blocksize))) { // 幾乎不可能發生

printk("VFS: breada: READ error on device %d/%d\n",

if (!bh->b_uptodate)

while ((first=va_arg(args,int))>=0) { // 預讀一個或多個塊(視參數個數而定)

tmp = getblk(dev, first, blocksize);

if (tmp) {

if (!tmp->b_uptodate)

ll_rw_block(READA, 1, &tmp);

tmp->b_count--;

va_end(args);

wait_on_buffer(bh); // 等待讀blk_no_immediate磁盤塊完成事件

brelse(bh);

return (NULL);

由于預讀操作完成後會由中斷處理例程來釋放buffer,這樣,将導緻freelist的不完整性。是以,在brelse()中,freelist不僅需要加鎖保護,還需要屏蔽中斷(此處僅僅需要屏蔽磁盤中斷)。

寫磁盤塊

類似地,核心通知磁盤驅動要寫一個buffer到磁盤上,磁盤驅動将安排一個I/O。如果是同步寫,那麼發出寫請求的程序将進入睡眠狀态直到操作完成。如果是異步寫,那麼送出請求的程序将不會等待。同樣,該buffer将在中斷處理例程中被釋放。

需要注意的是delay-write和異步寫的差別。delay-write表示該buffer被辨別為延遲寫,然後釋放該buffer。等待該buffer被重新配置設定時才會請求磁盤驅動排程一個寫操作。這樣的話,如果另一個程序對該buffer又作了修改,就隻有一個寫操作,而不是同/異步寫中的兩個操作,這也達到了節約I/O資源的目的。

由于異步寫操作同樣需要由中斷處理例程來釋放緩沖,磁盤中斷必須屏蔽掉。

寫磁盤操作的僞代碼如下:

void bwrite(BufferHeader * input)

發起寫磁盤請求;

if (同步I/O)

sleep (I/O完成事件);

brelse (input);

else if (delay-write)

标記該buffer并将其放在freelist的首部;

在實際的實作中(如Linux0.99.15),需要考慮到定期同步buffer與磁盤等,是以實作都比較複雜,此處就不加以讨論。大家要是有興趣可以自己研究下。

使用磁盤緩沖的利與弊

利:

統一磁盤通路接口,使系統設計變得簡單。

程式員無需考慮資料對齊。

減少磁盤通路量,進而減少擁堵,增加了系統的吞吐量并且減少了通路時間。(想象一下北京車少了,但是每輛車裝的人多了會怎樣,呵呵)

保證磁盤資料的完整性。

弊:

延遲寫機制在系統崩潰時将導緻資料錯誤。

無法确定資料在何時會真正寫到磁盤上(甚至fflush()都無法保證)。

額外的資料拷貝(使用者程序<-->核心<-->磁盤)将導緻大資料量時性能下降。

參考:

The Design of The UNIX Operation System, by Maurice J. Bach

Linux Kernel Source Code v0.99.15, by Linus Torvalds

Copyleft (C) raof01.

本文可以用于除商業外的所有用途。此處“用途”包括(但不限于)拷貝/翻譯(部分或全部),不包括根據本文描述來産生代碼及思想。若用于非商業,請保留此權利聲明,并标明文章原始位址和作者資訊;若要用于商業,請與作者聯系([email protected]),否則作者将使用法律來保證權利。

繼續閱讀