緩沖IO
在介紹緩沖IO之前需要先了解一下常用的機械硬盤的原理與特點
一個機械硬盤中裝有多個盤片
每個盤片上有多個同心圓(磁道)
每個同心圓又由多個弧(扇區)組成,每個弧上都記錄了等量的數據(比方說512byte)
如果發起一個隨機讀寫請求,磁頭需要先找到對應的磁道,然后等待對應的扇區旋轉到磁頭正下方才能開始讀取數據(民用機械硬盤的轉速一般在5400或者7200RPM,工業界倒是經常使用10000RPM的機械硬盤。但是它們的尋道時間大概都在幾ms到十幾ms左右)
機械硬盤的順序讀寫很快(一般在100-200MB/s),但是隨機讀寫很慢(尋道時間在十幾ms,導致隨機讀寫的iops只有幾十)
假定我們不做任何額外的優化處理,在用戶發起讀數據請求的時候,直接調用硬盤驅動讀取磁盤數據並返回
設想一個場景:循環調用read方法讀取文件,但是每次只讀取較少的數據(比方說每次只讀一個byte)。那么每次read請求都對應於一次對磁盤的隨機讀寫(兩次讀請求之前需要重新尋道),也就是說read操作的tps只有幾十。
也就是說此時磁盤占用率為100%,但是只能提供不到100byte/s的數據讀取率,這顯然是不可接受的。
Linux對此有個很簡單的優化,就是在內核中維護一塊緩沖區(buffer cache),在用戶第一次調用read讀取數據的時候,無論用戶想要讀取的數據有多小,都會一次性從磁盤中加載一段數據放到緩沖區中,這樣用戶下一次調用read方法的時候可以直接從緩沖區中返回數據,不用再次訪問磁盤了。
write方法也是同理,用戶寫入的數據不是直接落盤,而是先寫到kernel中的緩沖區里,按照一定的策略批量刷盤。當然也可以調用flush方法強制將緩存區的數據落盤。
這個優化極大的提高了順序讀寫的效率。由於直接讀寫的是kernel中的緩沖區而不是磁盤,這種IO被稱為緩沖IO。
直接IO
一般來說,上面介紹的緩沖IO已經足夠應付日常需求了。但是像數據庫這種極度依賴IO的應用程序,為了追求極致的性能,往往更加願意自己直接操作磁盤。
直接IO可以直接將數據從磁盤復制到用戶空間,或者將數據從用戶空間寫到磁盤,減少了kernel中的緩沖區這一環節,這是直接IO可以提高性能的原理。
但是如果用得不好就悲劇了,所以直接IO只在少數場景下使用。
內存映射
先給出mmap的官方文檔
mmap方法會返回一個void *類型的指針ptr,它指向進程邏輯空間中的一個地址。
后續如果想要讀寫文件,無需調用read/write方法, 而是直接操作這個ptr指針即可。
用戶試圖向ptr指針指向的空間讀寫數據時,由於MMU無法在物理內存中找到對應的地址,會觸發一次缺頁中斷,OS會去硬盤中找到對應的數據並復制到內存中,然后用戶就能正常完成讀寫操作了。這個過程是由操作系統自動完成的。
為什么說內存映射效率比緩沖IO要高?
我們回憶一下緩沖IO的工作流程:
1. 用戶調用read方法
2. 調用系統調用,觸發中斷,進程從用戶態進入內核態
3. 從硬盤中讀取數據並復制到kernel緩沖區
4. 將數據從kernel緩存區復制到用戶提供的byte數組中
5. 進程從內核態返回到用戶態
完成
從上面的流程中我們可以看到,調用一次read方法,最多可能會引起兩次用戶態與內核態之間的切換,以及兩次數據復制
而內存映射呢?
1. 用戶試圖訪問ptr指向的數據
2. MMU解析失敗,觸發缺頁中斷,程序從用戶態進入到內核態
3. 從硬盤中讀取數據並復制到進程空間中ptr指向的邏輯空間里
4. 進程從內核態返回到用戶態
完成
可以看出,試圖訪問內存映射文件,最多可能會引起兩次用戶態與內核態之間的切換,以及一次數據復制
也就是說,內存映射與緩沖IO相比,可以節省數據復制帶來的開銷,因此效率較高。
參考資料