聊聊文件IO


聊聊文件IO


前言

在消息隊列中,消息消費利用零拷貝減少拷貝次數、採用內存映射技術來使文件的訪問像訪問內存那樣。那到底零拷貝原理是什麼呢?怎麼實現內存映射的?

零拷貝

為什麼使用零拷貝?使用零拷貝有什麼好處?零拷貝的意思是說不需要將數據從某處複製到特定的某一個區域,可以減少CPU在數據複製的消耗還有內存內存帶寬。

例子:對於文件下載功能實現來說,很多情況下很不斷地讀取文件內容到應用程序空間,再不斷地將數據寫到網卡發送給客戶端。


while((n = read(diskfd, buf, BUF_SIZE)) > 0)
 write(sockfd, buf , n);

對於以上的IO操作實際已經發生了多次的數據拷貝。

聊聊文件IO


數據拷貝流程:

1、DMA將磁盤文件拷貝到內核緩存

2、CPU控制將內核緩存數據拷貝到應用程序空間

3、CPU控制將應用程序數據拷貝到套接字的buffer

4、DMA將socket的buffer拷貝到網卡設備發送出去

其實這種情況下需要消耗兩次的CPU、DMA完成拷貝工作;對於應用程序來說,CPU資源是相對昂貴的,應該儘可能節省,是否減少利用cpu資源完成拷貝?

採用sendfile減少內核空間到用戶空間的拷貝:

聊聊文件IO


數據拷貝流程:

1、利用DMA拷貝磁盤數據到內核的buffer

2、在套接字buffer上追加當前要發送的數據在kernel buffer的位置還有偏移量

3、根據套接字buffer的位置還有偏移量信息,DMA將kernel buffer的數據拷貝到網卡發送出去

可以看到採用sendfile只需要完成兩次拷貝,不需要拷貝到用戶空間,而且並不消耗cpu資源,可以大幅提高程序的效率。但是由於此次IO的整個流程都在內核空間完成,這種效率是最高的,但是應用程序無法對文件資源進行操作,那該怎麼解決呢?有沒有什麼方式可以即減少數據的拷貝,同時可以操作數據呢?

採用mmap內存映射

聊聊文件IO


mmap(內存映射)是一個比sendfile昂貴但優於傳統I/O的方法。

我們的程序發起一次系統調用,將一個文件(或者文件的一部分)映射到虛擬地址空間的一部分,注意這時候沒有分配和映射到具體的物理內存空間,而是到第一次加載這個文件的時候,通過MMU把之前虛擬地址換算成物理地址,把文件加載進物理內存。

數據拷貝流程:

1、調用mmap,發生用戶空間到內核空間的上下文切換(第一次切換),通過DMA將磁盤數據拷貝到內核空間

2、mmap調用返回,發生內核空間到用戶空間的切換(第二次切換),並且用戶空間和內核空間共享這部分緩存區,用戶空間可以像操作緩衝區的數據一樣操作這部分數據

3、調用write,發生用戶空間到內核空間的切換(第三次上下文切換),CPU拷貝內核空間的緩衝區數據到套接字的buffer

4、write調用返回,發生內核空間到用戶空間的切換(第四次上下文切換),並且通過DMA將套接字的buffer拷貝到網卡發送出去

可以看到採用mmap發送數據,可以操作數據,發生四次的上下文切換還有3次的數據拷貝,但是明顯優於傳統的IO。

實現零複製的軟件通常依靠基於直接存儲器訪問(Direct Memory Access,DMA)的複製,以及通過內存管理單元(MMU)的內存映射。這些功能需要特定硬件的支持,並通常涉及到特定存儲器的對齊。

很多同學在看消息隊列的刷盤時候會看到按照4k的page cache來實現異步刷盤,那page cache具體實現是什麼呢?為什麼它可以提高刷盤效率?

Page Cache

page cache的目的是通過將數據存儲在物理內存使磁盤IO最小化。page cache的大小是動態的,可以增大到消耗所有的free memory, 可以縮小來減輕內存壓力。在page cache中的page可以包含許多不連續的物理disk blocks。

在 Linux 內核中,文件的每個數據塊最多隻能對應一個 Page Cache 項,它通過兩個數據結構來管理這些 Cache 項,一個是 radix tree,另一個是雙向鏈表。Radix tree 是一種搜索樹,Linux 內核利用這個數據結構來通過文件內偏移快速定位 Cache 項。


聊聊文件IO


實際使用page cache預讀取來提高順序讀的效率,利用預先加載數據減少磁盤io,但是對於隨機讀顯然採用page cache是不那麼合適的,因為多次的預加載反而降低讀取的效率。

聊聊文件IO

在前面的分析中,只針對數據拷貝效率進行說明,而對於文件數據怎麼讀取到內核空間呢?block io layer 對外提供通用的磁盤訪問接口,而block layer再往下就是某個device具體的driver用於加載磁盤的數據。

在調用filechannel的read加載的時候會觸發page cache預讀IO的方式,隨著讀取預讀範圍擴大直到佔滿空閒內存,這種方式能提高順序讀取的效率,page cache很有效果。而對於隨機讀取的方式,依舊存在其價值,減少了 Block IO Layed(近似理解為磁盤) 到 Page Cache 的 overhead。

作者:獨玄 路上小棧


分享到:


相關文章: