前一陣子公司一部門有人叫幫忙調查,說他們write系統調用基本上是個位數微秒就返回,或者說幾十us,但偶爾出現幾回write系統調用達到幾百毫秒和狀況。你們都知道,經過vfs進行write,都是寫寫到page cache中,而後內核線程再按期同步到磁盤。寫到內存應該是很快的纔對。剛開始,我作了許多設想,1)磁盤IO過重,內存中的髒數據達到必定比率後write必須同步寫到磁盤;2)那些耗時長的write是使用direct io,繞過了page cache;三、剛恰好write一個page時,read也在讀同一page,那個page被lock了,write要等它。後來每一種假設又都被本身推翻了。如下是經過vfs進行write系統調用的圖:函數
前幾天,使用systemtap進行一步步的定位,最後把可疑的點鎖定在__block_write_begin這個函數中(fs/buffer.c)。spa
先說明下這個函數是幹什麼的。generic_perform_write函數是把數據寫入磁盤的主要處理函數。它先調用address_space_operations. write_begin(其中會調用咱們上文提到的__block_write_begin函數),接下來把數據拷貝到上一步分配出來的page中,最後調用address_space_operations.write_end。上面所說的write_begin和write_end會具體根據不一樣的file system調用不一樣的函數,拿ext4來講,它的函數以下(有dellay allocation的話):線程
static const struct address_space_operations ext4_da_aops = { .readpage = ext4_readpage, .readpages = ext4_readpages, .writepage = ext4_writepage, .writepages = ext4_writepages, .write_begin = ext4_da_write_begin, .write_end = ext4_da_write_end, .bmap = ext4_bmap, .invalidatepage = ext4_da_invalidatepage, .releasepage = ext4_releasepage, .direct_IO = ext4_direct_IO, .migratepage = buffer_migrate_page, .is_partially_uptodate = block_is_partially_uptodate, .error_remove_page = generic_error_remove_page, };
能夠看到,ext4的write_begin是ext4_da_write_begin。Ext4_da_write_begin會先分配一個頁框,而後調用__block_write_begin分配緩衝區(爲何要分配緩衝區這裏就不說明了)。那麼,__block_write_begin分配緩衝區而已,爲何有時候要耗時那麼長呢?先看看代碼:code
int __block_write_begin(struct page *page, loff_t pos, unsigned len, get_block_t *get_block) { ...... if (!buffer_uptodate(bh) && !buffer_delay(bh) && !buffer_unwritten(bh) && (block_start < from || block_end > to)) { ll_rw_block(READ, 1, &bh); //這裏指定read操做,提交BIO *wait_bh++=bh; } ...... /* * If we issued read requests - let them complete. */ while(wait_bh > wait) { //這裏等待io操做完成 wait_on_buffer(*--wait_bh); if (!buffer_uptodate(*wait_bh)) err = -EIO; } }
write的基本單位是塊(由磁盤文件系統定義,默認爲4K)。這樣的話,page cache中一個page(恰好也爲4K)恰好就是一個塊。好比說write的地址是512,長度是20,page cache就分配了一個能寫 0~4095的頁page frame,這個page frame恰好對應一個磁盤塊,假如把要write的數據拷貝到這個page frame的 512~531,這樣的話0~511和532~4095是空的。可是下次write back的時候會把整塊數據都寫入磁盤,因此須要把這一整塊的數據都先從磁盤中讀出來,寫入page cache中,以防止0~511和532~4095被誤寫。這個讀操做應該就是耗時長的緣由了。orm