Android I/O 那些事兒

I/O 操作是編程離不開的話題,它不僅是讀寫那麼簡單,還涉及底層的文件系統和存儲設備。I/O 的快慢影響程序的執行效率,這篇文章主要介紹 Android 平臺 I/O 的方式和使用場景。

1. Linux I/O 的基本組成

衆所周知,Android 基於 Linux 系統,先介紹一些 Linux 上 I/O 的知識。

I/O 操作由應用程序、文件系統和磁盤共同完成,應用程序將 I/O 命令發送給文件系統,文件系統在合適的時間把 I/O 指令發送給磁盤。I/O 的流程如下圖:

CPU 和內存的速度比磁盤快得多,I/O 操作的瓶頸在於磁盤的性能。爲了降低磁盤對應用程序的影響,文件系統要進行各種各樣的優化。

文件系統

簡單來說,文件系統就是存儲和組織數據的方式。應用程序調用 read() 方法,系統會通過中斷從用戶空間進入內核空間,然後經過虛擬文件系統、具體文件系統、頁緩存。

  • 虛擬文件系統(VFS)。主要用於屏蔽具體的文件系統,爲應用程序的操作提供一個統一的接口。
  • 文件系統(File System)。ext4、F2FS 都是具體文件系統實現。每個文件系統都有適合自己的場景。
  • 頁緩存(Page Cache)。文件系統對數據的緩存,讀文件時先檢查頁緩存,如果命中就不去讀磁盤。

磁盤

磁盤指的是系統的存儲設備,常見的有機械硬盤、固態硬盤等。如果發現應用程序要讀的數據沒有在頁緩存中,這時候就需要真正向磁盤發起 I/O 請求。磁盤 I/O 的過程要先經過內核的通用塊層、I/O 調度層、設備驅動層,最後纔會交給具體的硬件設備處理。

  • 通用塊層。接收上層發出的磁盤請求,並最終發出 I/O 請求。它與 VPS 的作用類似。
  • I/O 調度層。根據設置的調度算法對請求合併和排序。不能接收到磁盤請求就立刻交給驅動層處理。
  • 塊設備驅動層。根據具體的物理設備,選擇對應的驅動程序,通過操控硬件設備完成最終的 I/O 請求。

2. Android 上的 I/O

Android 現在普遍使用的是 Linux 常用的 ext4 文件系統。F2FS(Flash-Friendly File System)是三星爲閃存研發的文件系統,它針對閃存進行了大量優化,F2FS 文件系統在小文件的隨機讀寫方面比 ext4 更快。隨着 Google、華爲的投入和使用,F2FS 應該會成爲 Android 主流的文件系統。

Android 手機使用閃存作爲存儲設備,也就是我們常說的 ROM。前幾年閃存通常使用 eMMC 標準,近年來採用性能更好的 UFS 2.0/2.1 標準。手機存儲也朝着體積更小、功耗更低、速度更快、容量更大的方向發展,閃存的隨機讀寫速度甚至比 SSD 還快。

手機變卡

Android 手機用久了會變卡,除了系統升級、設備折舊等因素,還和 I/O 有密切關係。I/O 操作變慢的原因有下面幾條:

  • 內存不足。系統回收 Page Cache 和 Buffer Cache 的內存,大部分的寫操作會直接落盤,導致性能低下。
  • 寫入放大。閃存重複寫入需要先進行擦除,一次寫入會引起整個塊數據的遷移,導致寫入時間非常久。
  • 設備性能差。在高負載的情況下容易出現瓶頸。

文件損壞

文件損壞是令人頭疼的問題,大多是由不正確的操作導致的。文件損壞的原因可以從應用程序、文件系統和磁盤三個角度來分析:

  • 應用程序。大部分的 I/O 方法都不是原子操作,文件的跨進程或者多線程寫入、使用一個已經關閉的文件描述符 fd 來操作文件,都有可能導致數據被覆蓋或者刪除。
  • 文件系統。雖說內核崩潰或者系統突然斷電都有可能導致文件系統損壞,不過文件系統也做了很多的保護措施。例如 system 分區保證只讀不可寫,增加異常檢查和恢復機制。
  • 磁盤。手機上使用的閃存是電子式的存儲設備,所以在資料傳輸過程可能會發生電子遺失等現象導致數據錯誤。

3. I/O 的三種方式

I/O 有三種方式:標準 I/O、mmap 和 Direct I/O。

標準 I/O

應用程序平時用到 read/write 操作都屬於標準 I/O,也就是緩存 I/O(Buffered I/O)。它的關鍵特性有:

  • 對於讀操作,當應用程序讀取某塊數據時,如果這塊數據已經在頁緩存中,那麼就不需要經過物理讀盤操作。
  • 對於寫操作,應用程序會先將數據寫到頁緩存中去,不需要等全部數據被寫回磁盤,系統會定期將頁緩存中的數據刷到磁盤上。

緩存 I/O 可以很大程度減少真正讀寫磁盤的次數,從而提升性能。但是延遲寫機制可能會導致數據丟失。在實際應用中,如果某些數據非常重要,我們應該採用同步寫機制。

讀操作時,數據會先從磁盤拷貝到 Page Cache 中,然後再從 Page Cache 拷貝到應用程序的用戶空間,這樣就會多一次內存拷貝。內存相對磁盤是高速設備,即使多拷貝一次,也比真正讀一次硬盤要快。

mmap

mmap 把文件映射到進程的地址空間,提高了 I/O 的性能。

mmap 的優點有:

  • 減少系統調用。只需要一次 mmap() 系統調用,後續所有的調用像操作內存一樣。
  • 減少數據拷貝。mmap 只需要從磁盤拷貝一次,由於做過內存映射,不需要再拷貝回用戶空間。
  • 可靠性高。mmap 把數據寫入頁緩存後,跟緩存 I/O 的延遲寫機制一樣。

存在的缺點:

  • 虛擬內存增大。Apk、Dex、so 都是通過 mmap 讀取。mmap 會導致虛擬內存增大,mmap 大文件容易出現 OOM。
  • 磁盤延遲。mmap 通過缺頁中斷向磁盤發起真正的磁盤 I/O,不能通過 mmap 消除磁盤 I/O 的延遲。

在 Android 中可以將文件通過 MemoryFile 或者 MappedByteBuffer 映射到內存,然後進行讀寫,使用這種方式對於小文件和頻繁讀寫操作的文件還是有一定優勢的。

mmap 比較適合對同一塊區域頻繁讀寫的情況,推薦使用 I/O 線程來操作。用戶日誌、數據上報都滿足這種場景,另外需要跨進程同步的時候,mmap 也是一個不錯的選擇。Android 跨進程通信有自己獨有的 Binder 機制,它內部也是使用 mmap 實現。

Direct I/O

一些數據庫自己實現了數據和索引的緩存管理,對頁緩存的依賴沒那麼強烈。它們想繞開頁緩存機制,減少一次數據拷貝,它的數據也不會污染頁緩存。

直接 I/O 訪問文件方式減少了一次數據拷貝和一些系統調用的耗時,很大程度降低了 CPU 的使用率以及內存的佔用。負面影響就是讀寫操作都是同步執行,導致應用程序等待。

4. 同步與異步 I/O

多線程阻塞式在 I/O 操作上的並沒有優勢,I/O 操作的主要瓶頸在於磁盤帶寬。所以 I/O 操作不能開大量的線程。

NIO 是非阻塞 I/O,將 I/O 以事件的方式通知,可以減少線程切換的開銷。NIO 的最大作用不是減少讀取文件的耗時,而是最大化提升應用整體的 CPU 利用率。

另外,非常推薦 Square 的 Okio,它支持同步和異步 I/O,也做了比較多的優化。

I/O 優化對提升應用的體驗非常有用,希望上面所講的內容對你有幫助。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章