接上一篇聊聊Linux IO,先上一張全貌圖[4]:
由圖可見,從系統呼叫的介面再往下,Linux下的IO棧致大致有三個層次:
-
檔案系統層,以
write(2)
為例,核心複製了write(2)
引數指定的使用者態資料到檔案系統Cache中,並適時向下層同步 -
塊層,管理塊裝置的IO佇列,對IO請求進行合併、排序(還記得作業系統課程學習過的IO排程演演算法嗎?)
-
裝置層,透過DMA與記憶體直接互動,完成資料和具體裝置之間的互動
結合這個圖,想想Linux系統程式設計裡用到的Buffered IO
、mmap(2)
、Direct IO
,這些機制怎麼和Linux IO棧聯絡起來呢?上面的圖有點複雜,我畫一幅簡圖,把這些機制所在的位置新增進去:
這下一目瞭然了吧?傳統的Buffered IO
使用read(2)
讀取檔案的過程什麼樣的?假設要去讀一個冷檔案(Cache中不存在),open(2)
開啟檔案核心後建立了一系列的資料結構,接下來呼叫read(2)
,到達檔案系統這一層,發現Page Cache
中不存在該位置的磁碟對映,然後建立相應的Page Cache
並和相關的扇區關聯。然後請求繼續到達塊裝置層,在IO佇列裡排隊,接受一系列的排程後到達裝置驅動層,此時一般使用DMA方式讀取相應的磁碟扇區到Cache中,然後read(2)
複製資料到使用者提供的使用者態buffer中去(read(2)
的引數指出的)。
整個過程有幾次複製?從磁碟到Page Cache
算第一次的話,從Page Cache
到使用者態buffer就是第二次了。而mmap(2)
做了什麼?mmap(2)
直接把Page Cache
對映到了使用者態的地址空間裡了,所以mmap(2)
的方式讀檔案是沒有第二次複製過程的。那Direct IO
做了什麼?這個機制更狠,直接讓使用者態和塊IO層對接,直接放棄Page Cache
,從磁碟直接和使用者態複製資料。好處是什麼?寫操作直接對映行程的buffer到磁碟扇區,以DMA的方式傳輸資料,減少了原本需要到Page Cache
層的一次複製,提升了寫的效率。對於讀而言,第一次肯定也是快於傳統的方式的,但是之後的讀就不如傳統方式了(當然也可以在使用者態自己做Cache,有些商用資料庫就是這麼做的)。
除了傳統的Buffered IO
可以比較自由的用偏移+長度的方式讀寫檔案之外,mmap(2)
和Direct IO
均有資料按頁對齊的要求,Direct IO
還限制讀寫必須是底層儲存裝置塊大小的整數倍(甚至Linux 2.4還要求是檔案系統邏輯塊的整數倍)。所以介面越來越底層,換來錶面上的效率提升的背後,需要在應用程式這一層做更多的事情。所以想用好這些高階特性,除了深刻理解其背後的機制之外,也要在系統設計上下一番功夫。
(未完)