I/O 操作是編程離不開的話題,它不僅是讀寫那么簡單绞呈,還涉及底層的文件系統(tǒng)和存儲設(shè)備兔毙。I/O 的快慢影響程序的執(zhí)行效率柠辞,這篇文章主要介紹 Android 平臺 I/O 的方式和使用場景尘颓。
1. Linux I/O 的基本組成
眾所周知走触,Android 基于 Linux 系統(tǒng),先介紹一些 Linux 上 I/O 的知識疤苹。
I/O 操作由應(yīng)用程序互广、文件系統(tǒng)和磁盤共同完成,應(yīng)用程序?qū)?I/O 命令發(fā)送給文件系統(tǒng)卧土,文件系統(tǒng)在合適的時間把 I/O 指令發(fā)送給磁盤凸椿。I/O 的流程如下圖:
CPU 和內(nèi)存的速度比磁盤快得多,I/O 操作的瓶頸在于磁盤的性能隘庄。為了降低磁盤對應(yīng)用程序的影響囤官,文件系統(tǒng)要進(jìn)行各種各樣的優(yōu)化。
文件系統(tǒng)
簡單來說颤霎,文件系統(tǒng)就是存儲和組織數(shù)據(jù)的方式媳谁。應(yīng)用程序調(diào)用 read() 方法,系統(tǒng)會通過中斷從用戶空間進(jìn)入內(nèi)核空間友酱,然后經(jīng)過虛擬文件系統(tǒng)晴音、具體文件系統(tǒng)、頁緩存缔杉。
- 虛擬文件系統(tǒng)(VFS)锤躁。主要用于屏蔽具體的文件系統(tǒng),為應(yīng)用程序的操作提供一個統(tǒng)一的接口或详。
- 文件系統(tǒng)(File System)系羞。ext4、F2FS 都是具體文件系統(tǒng)實現(xiàn)霸琴。每個文件系統(tǒng)都有適合自己的場景椒振。
- 頁緩存(Page Cache)。文件系統(tǒng)對數(shù)據(jù)的緩存沈贝,讀文件時先檢查頁緩存杠人,如果命中就不去讀磁盤。
磁盤
磁盤指的是系統(tǒng)的存儲設(shè)備宋下,常見的有機(jī)械硬盤嗡善、固態(tài)硬盤等。如果發(fā)現(xiàn)應(yīng)用程序要讀的數(shù)據(jù)沒有在頁緩存中学歧,這時候就需要真正向磁盤發(fā)起 I/O 請求罩引。磁盤 I/O 的過程要先經(jīng)過內(nèi)核的通用塊層、I/O 調(diào)度層枝笨、設(shè)備驅(qū)動層袁铐,最后才會交給具體的硬件設(shè)備處理揭蜒。
- 通用塊層。接收上層發(fā)出的磁盤請求剔桨,并最終發(fā)出 I/O 請求屉更。它與 VPS 的作用類似。
- I/O 調(diào)度層洒缀。根據(jù)設(shè)置的調(diào)度算法對請求合并和排序瑰谜。不能接收到磁盤請求就立刻交給驅(qū)動層處理。
- 塊設(shè)備驅(qū)動層树绩。根據(jù)具體的物理設(shè)備萨脑,選擇對應(yīng)的驅(qū)動程序,通過操控硬件設(shè)備完成最終的 I/O 請求饺饭。
2. Android 上的 I/O
Android 現(xiàn)在普遍使用的是 Linux 常用的 ext4 文件系統(tǒng)渤早。F2FS(Flash-Friendly File System)是三星為閃存研發(fā)的文件系統(tǒng),它針對閃存進(jìn)行了大量優(yōu)化瘫俊,F(xiàn)2FS 文件系統(tǒng)在小文件的隨機(jī)讀寫方面比 ext4 更快鹊杖。隨著 Google、華為的投入和使用军援,F(xiàn)2FS 應(yīng)該會成為 Android 主流的文件系統(tǒng)仅淑。
Android 手機(jī)使用閃存作為存儲設(shè)備称勋,也就是我們常說的 ROM胸哥。前幾年閃存通常使用 eMMC 標(biāo)準(zhǔn),近年來采用性能更好的 UFS 2.0/2.1 標(biāo)準(zhǔn)赡鲜。手機(jī)存儲也朝著體積更小空厌、功耗更低、速度更快银酬、容量更大的方向發(fā)展嘲更,閃存的隨機(jī)讀寫速度甚至比 SSD 還快。
手機(jī)變卡
Android 手機(jī)用久了會變卡揩瞪,除了系統(tǒng)升級赋朦、設(shè)備折舊等因素,還和 I/O 有密切關(guān)系李破。I/O 操作變慢的原因有下面幾條:
- 內(nèi)存不足宠哄。系統(tǒng)回收 Page Cache 和 Buffer Cache 的內(nèi)存,大部分的寫操作會直接落盤嗤攻,導(dǎo)致性能低下毛嫉。
- 寫入放大。閃存重復(fù)寫入需要先進(jìn)行擦除妇菱,一次寫入會引起整個塊數(shù)據(jù)的遷移承粤,導(dǎo)致寫入時間非常久暴区。
- 設(shè)備性能差。在高負(fù)載的情況下容易出現(xiàn)瓶頸辛臊。
文件損壞
文件損壞是令人頭疼的問題仙粱,大多是由不正確的操作導(dǎo)致的。文件損壞的原因可以從應(yīng)用程序彻舰、文件系統(tǒng)和磁盤三個角度來分析:
- 應(yīng)用程序缰盏。大部分的 I/O 方法都不是原子操作,文件的跨進(jìn)程或者多線程寫入淹遵、使用一個已經(jīng)關(guān)閉的文件描述符 fd 來操作文件口猜,都有可能導(dǎo)致數(shù)據(jù)被覆蓋或者刪除。
- 文件系統(tǒng)透揣。雖說內(nèi)核崩潰或者系統(tǒng)突然斷電都有可能導(dǎo)致文件系統(tǒng)損壞济炎,不過文件系統(tǒng)也做了很多的保護(hù)措施。例如 system 分區(qū)保證只讀不可寫辐真,增加異常檢查和恢復(fù)機(jī)制须尚。
- 磁盤。手機(jī)上使用的閃存是電子式的存儲設(shè)備侍咱,所以在資料傳輸過程可能會發(fā)生電子遺失等現(xiàn)象導(dǎo)致數(shù)據(jù)錯誤耐床。
3. I/O 的三種方式
I/O 有三種方式:標(biāo)準(zhǔn) I/O、mmap 和 Direct I/O楔脯。
標(biāo)準(zhǔn) I/O
應(yīng)用程序平時用到 read/write 操作都屬于標(biāo)準(zhǔn) I/O撩轰,也就是緩存 I/O(Buffered I/O)。它的關(guān)鍵特性有:
- 對于讀操作昧廷,當(dāng)應(yīng)用程序讀取某塊數(shù)據(jù)時堪嫂,如果這塊數(shù)據(jù)已經(jīng)在頁緩存中,那么就不需要經(jīng)過物理讀盤操作木柬。
- 對于寫操作皆串,應(yīng)用程序會先將數(shù)據(jù)寫到頁緩存中去,不需要等全部數(shù)據(jù)被寫回磁盤眉枕,系統(tǒng)會定期將頁緩存中的數(shù)據(jù)刷到磁盤上恶复。
緩存 I/O 可以很大程度減少真正讀寫磁盤的次數(shù),從而提升性能速挑。但是延遲寫機(jī)制可能會導(dǎo)致數(shù)據(jù)丟失谤牡。在實際應(yīng)用中,如果某些數(shù)據(jù)非常重要梗摇,我們應(yīng)該采用同步寫機(jī)制拓哟。
讀操作時,數(shù)據(jù)會先從磁盤拷貝到 Page Cache 中伶授,然后再從 Page Cache 拷貝到應(yīng)用程序的用戶空間断序,這樣就會多一次內(nèi)存拷貝流纹。內(nèi)存相對磁盤是高速設(shè)備,即使多拷貝一次违诗,也比真正讀一次硬盤要快漱凝。
mmap
mmap 把文件映射到進(jìn)程的地址空間,提高了 I/O 的性能诸迟。
mmap 的優(yōu)點有:
- 減少系統(tǒng)調(diào)用茸炒。只需要一次 mmap() 系統(tǒng)調(diào)用,后續(xù)所有的調(diào)用像操作內(nèi)存一樣阵苇。
- 減少數(shù)據(jù)拷貝壁公。mmap 只需要從磁盤拷貝一次,由于做過內(nèi)存映射绅项,不需要再拷貝回用戶空間紊册。
- 可靠性高。mmap 把數(shù)據(jù)寫入頁緩存后快耿,跟緩存 I/O 的延遲寫機(jī)制一樣囊陡。
存在的缺點:
- 虛擬內(nèi)存增大。Apk掀亥、Dex撞反、so 都是通過 mmap 讀取。mmap 會導(dǎo)致虛擬內(nèi)存增大搪花,mmap 大文件容易出現(xiàn) OOM遏片。
- 磁盤延遲。mmap 通過缺頁中斷向磁盤發(fā)起真正的磁盤 I/O鳍侣,不能通過 mmap 消除磁盤 I/O 的延遲丁稀。
在 Android 中可以將文件通過 MemoryFile 或者 MappedByteBuffer 映射到內(nèi)存,然后進(jìn)行讀寫倚聚,使用這種方式對于小文件和頻繁讀寫操作的文件還是有一定優(yōu)勢的。
mmap 比較適合對同一塊區(qū)域頻繁讀寫的情況凿可,推薦使用 I/O 線程來操作惑折。用戶日志、數(shù)據(jù)上報都滿足這種場景枯跑,另外需要跨進(jìn)程同步的時候惨驶,mmap 也是一個不錯的選擇。Android 跨進(jìn)程通信有自己獨有的 Binder 機(jī)制敛助,它內(nèi)部也是使用 mmap 實現(xiàn)粗卜。
Direct I/O
一些數(shù)據(jù)庫自己實現(xiàn)了數(shù)據(jù)和索引的緩存管理,對頁緩存的依賴沒那么強(qiáng)烈纳击。它們想繞開頁緩存機(jī)制续扔,減少一次數(shù)據(jù)拷貝攻臀,它的數(shù)據(jù)也不會污染頁緩存。
直接 I/O 訪問文件方式減少了一次數(shù)據(jù)拷貝和一些系統(tǒng)調(diào)用的耗時纱昧,很大程度降低了 CPU 的使用率以及內(nèi)存的占用刨啸。負(fù)面影響就是讀寫操作都是同步執(zhí)行,導(dǎo)致應(yīng)用程序等待识脆。
4. 同步與異步 I/O
多線程阻塞式在 I/O 操作上的并沒有優(yōu)勢设联,I/O 操作的主要瓶頸在于磁盤帶寬。所以 I/O 操作不能開大量的線程灼捂。
NIO 是非阻塞 I/O离例,將 I/O 以事件的方式通知,可以減少線程切換的開銷悉稠。NIO 的最大作用不是減少讀取文件的耗時粘招,而是最大化提升應(yīng)用整體的 CPU 利用率。
另外偎球,非常推薦 Square 的 Okio洒扎,它支持同步和異步 I/O,也做了比較多的優(yōu)化衰絮。
I/O 優(yōu)化對提升應(yīng)用的體驗非常有用袍冷,希望上面所講的內(nèi)容對你有幫助。