原文轉自: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]),否則作者将使用法律來保證權利。