深入了解Android?IO的底層原理
前言
最近在看《Linux內(nèi)核設(shè)計與實現(xiàn)》的時候,就想著要不把知識串聯(lián)一下吧。聊什么呢?今天先來聊聊 Android IO 的調(diào)用鏈路。說起 IO,這可真是一個很復(fù)雜的過程,里面涉及了很多內(nèi)容,先是軟件,最后到硬件,用一張圖來表示一下吧:
本文打算簡單得和大伙討論一下 IO 的流程。
一、應(yīng)用層
作為應(yīng)用開發(fā)者,我們通常是 IO 發(fā)起點,比如用戶說這本小說很好看,我要下載到本地,或者,這張圖拍的不錯,分享給你看一下。
雖然這些都是常見的 IO 場景,但是你知道有哪些 IO 嗎?
1. IO的分類
通常去使用 IO 的時候,我們會有很多種選擇,常見的有:
- 緩沖與非緩沖 IO
- 直接與非直接 IO
- 阻塞與非阻塞 IO
- 同步與異步 IO
大家平時可能也就聽過緩沖 IO 和 阻塞 IO,這些可能是我們平時開發(fā)可能涉及到的。
1.1 緩沖和直接
前兩種分類都是使用緩存的。
緩沖是針對標(biāo)準(zhǔn)庫的。
Linux 標(biāo)準(zhǔn)庫定義了很多操作系統(tǒng)的基礎(chǔ)服務(wù),比如輸入/輸出、字符串處理等等。Android 操作系統(tǒng)的標(biāo)準(zhǔn)庫是 Bionic,它可是應(yīng)用層聯(lián)系內(nèi)核的橋梁,我們也可以通過 NDK 訪問 Bionic。
使用標(biāo)準(zhǔn)庫進行 IO 我們稱為緩沖 IO,我們讀文件的時候,經(jīng)常遇到,讀完一行才會讓輸出,在 Android 內(nèi)部也做了類似的處理。
直接是針對內(nèi)核的。
使用 Binder 跨進程傳遞數(shù)據(jù)的時候,需要將數(shù)據(jù)從用戶空間傳遞到內(nèi)核空間,非直接 IO 也這樣,內(nèi)核空間會多做一層頁緩存,如果做直接 IO,應(yīng)用程序會直接調(diào)用文件系統(tǒng)。
緩沖和非直接 IO 就像 IO 調(diào)度的一級和二級緩存,為什么要做這么多緩存呢?因為操作磁盤本身就是消耗資源的,不加緩存頻繁 IO 不僅會耗費資源也會耗時。
1.2 阻塞和異步
同步和異步我想大家都了解什么意思。
阻塞 IO指的是當(dāng)用戶執(zhí)行讀寫的時候,線程會一直阻塞,數(shù)據(jù)準(zhǔn)備和將數(shù)據(jù)拷貝到用戶進程都是阻塞的:
Java 中的 NIO 是非阻塞 IO,當(dāng)用戶發(fā)起讀寫的時候,線程不會阻塞,之后,用戶可以通過輪詢或者接受通知的方式,獲取當(dāng)前 IO 調(diào)度的結(jié)果:
即使是非阻塞 IO,對于讀數(shù)據(jù)來說,也只有準(zhǔn)備數(shù)據(jù)的過程是異步,將數(shù)據(jù)從內(nèi)核拷貝到用戶進程這個過程還是同步的。所以非阻塞 IO 不能算是真正意義上的異步 IO。
真正的異步 IO 應(yīng)該是這樣的:
準(zhǔn)備數(shù)據(jù)和將數(shù)據(jù)拷貝從內(nèi)核到用戶進程都應(yīng)該是異步的,當(dāng)收到通知的的時候,我們已經(jīng)可以在應(yīng)用進程使用數(shù)據(jù)了。
2. IO流程
作為應(yīng)用層開發(fā),大家做 IO 的場景并不多,最多也就是使用 BufferedInputStream
和 BufferedOutputStream
讀寫文件,至于 NIO ,那就更少見了。
我們了解一下阻塞 IO 的讀調(diào)用流程。
二、sysCall系統(tǒng)調(diào)用
應(yīng)用層調(diào)完了,下面會直接進入內(nèi)核嗎?
除去直接 IO,大部分都不會!用戶空間和內(nèi)核之間隔著一個系統(tǒng)調(diào)用(sysCall),它的作用如下:
- 給用戶空間提供抽象的訪問硬件的接口:比如申請系統(tǒng)資源、操作設(shè)備讀寫等
- 保證系統(tǒng)的安全和穩(wěn)定:內(nèi)核可以對用戶進程的訪問做出一些裁決,防止用戶進程做出一些危害系統(tǒng)的事情
畢竟內(nèi)核很復(fù)雜,抽象出通用的接口,可以防止用戶空間的進程僭越,獲取到它不該獲取的內(nèi)容。
為了能夠讓應(yīng)用進程聯(lián)系上內(nèi)核,它會通過一個軟中斷,通知內(nèi)核,我想調(diào)用內(nèi)核中 sysCall 中的讀接口。
對于讀 IO,系統(tǒng)調(diào)用中有一個 sys_read
方法與之對應(yīng),內(nèi)核收到通知執(zhí)行該方法的時候,就會執(zhí)行虛擬文件系統(tǒng)的 read
方法。
三、虛擬文件系統(tǒng)
文件系統(tǒng)實在是太多了,比如我手機用戶空間的文件系統(tǒng)是 f2fs,系統(tǒng)空間的文件系統(tǒng)是 ext4。對于應(yīng)用程序來說,它就想調(diào)用個讀方法,不想管你手機的底層文件系統(tǒng)是什么!
虛擬文件系統(tǒng)就是來干這活的,它可以屏蔽具體的文件系統(tǒng),定義了一組所有文件系統(tǒng)都支持的數(shù)據(jù)結(jié)構(gòu)和標(biāo)準(zhǔn)接口。這樣,應(yīng)用層的程序員只需了解 VFS 提供的統(tǒng)一接口就行。
虛擬文件系統(tǒng)常被稱為 VFS(Virtual File System),下稱 VFS。
1. VFS結(jié)構(gòu)
VFS 采用的是面向?qū)ο蟮脑O(shè)計思路,它常常有下列的對象(C語言中的結(jié)構(gòu)體)構(gòu)成:
這些對象構(gòu)成了基本的虛擬文件系統(tǒng)。
不過,光有這些對象可不行,VFS 還得知道如何操作它們,所以,每個對象中還存在對應(yīng)的操作對象:
super_operation
對象:內(nèi)核針對超級塊所能調(diào)用的方法inode_operation
對象:內(nèi)核針對索引結(jié)點所能調(diào)用的方法dentry_operation
對象:內(nèi)核針對目錄項所能操作的方法file_operation
對象:內(nèi)核針對進程中打開的文件所能操作的方法
大伙最熟悉的應(yīng)該是文件,這是我們能夠在進程中實實在在能夠操作的,比如,在文件的 file_operation
中,就有我們熟悉的讀、寫、拷貝、打開、寫入磁盤等方法。
不知道大伙兒有沒注意到,我特意標(biāo)注了超級塊和索引節(jié)點存在于內(nèi)存和磁盤,而目錄項和文件只存在于內(nèi)存。
我的理解是對于磁盤,索引節(jié)點已經(jīng)足夠記錄文件信息,并不需要目錄項再來記錄層級關(guān)系;而對于內(nèi)存來說,為了節(jié)省內(nèi)存,只會把需要用到的文件和目錄項所用到的索引節(jié)點加入內(nèi)存,文件系統(tǒng)只有被掛載的時候超級塊才會被加入到內(nèi)存中。
目錄項、索引節(jié)點、文件和超級塊結(jié)構(gòu)圖:
上面的結(jié)構(gòu)圖還有幾點要注意一下:
- 目錄項不等于目錄這個概念,對于 /home/pic/a.jpg 來說,根目錄 / 、home 目錄、pic 目錄 和 a.jpg 都屬于目錄項
- 每個目錄項都會持有索引節(jié)點的指針
- 索引節(jié)點包含內(nèi)核在操作文件需要的全部信息,比如存在磁盤的位置等
- 進程中打開的文件持有目錄項
2. VFS中的緩存
結(jié)合本文中的第一張圖,我們會發(fā)現(xiàn),VFS 有目錄項緩存、索引節(jié)點緩存和頁緩存,目錄項和索引節(jié)點我們都知道什么意思,那頁緩存呢?
頁緩存是由 RAM 中的物理頁組成的,對應(yīng)著 ROM 上的物理地址。我們都知道,現(xiàn)在主流 Android 的 RAM 訪問速度高達是 8.5 GB/S,而 ROM 的訪問速度最高只有 6400 MB/S,所以訪問 RAM 的速度要遠(yuǎn)遠(yuǎn)快于 ROM,頁緩存的目的也在于此。
當(dāng)發(fā)起一個讀操作的時候,內(nèi)核會首先檢查需要的數(shù)據(jù)是否在頁緩存,如果在,直接從內(nèi)存中讀取,我們稱之為緩存命中;如果不在,那么內(nèi)核在讀取數(shù)據(jù)的時候,將讀到的數(shù)據(jù)放入頁緩存,需要注意的是,頁緩存可以存入全部文件內(nèi)容,也可以僅僅存幾頁。
3. IO流程
經(jīng)過系統(tǒng)調(diào)用,讀 IO 進入了 VFS。
就去找文件對象(VFS 中的),通過文件對象的 file_operation
對象,調(diào)用 read
方法,傳入讀取的數(shù)據(jù)量。不過 read
方法也是找到文件對象對應(yīng)的目錄項,目錄項又找到索引節(jié)點,畢竟,只有索引節(jié)點知道文件存在哪兒?
通過索引節(jié)點,內(nèi)核就能唯一確定一個文件,然后在頁緩存中尋找是否有自己需要的數(shù)據(jù),找到就直接返回。沒找到就去進行下一步的操作。
四、文件系統(tǒng)
VFS 定義了文件系統(tǒng)的統(tǒng)一接口,具體的實現(xiàn)了交給了文件系統(tǒng),超級塊里面的數(shù)據(jù)如何組織、目錄和索引結(jié)構(gòu)如何設(shè)計、怎么分配和清理數(shù)據(jù),這都是設(shè)計一個文件系統(tǒng)必須考慮的!
說白了,文件系統(tǒng)就是用來管理磁盤里的持久化的數(shù)據(jù)的,對于 Android 來說,最常見的就是 ext4 和 f2fs。
1. 文件系統(tǒng)結(jié)構(gòu)
因為文件系統(tǒng)是 VFS 的具體實現(xiàn),所以同樣有目錄項、索引節(jié)點和超級塊,上面的圖片用來描述文件系統(tǒng)也同樣適合。
拿早起 ext2 的系統(tǒng)結(jié)構(gòu)來講:
每一個 ext2 都由大量的塊組組成,每個塊組的結(jié)構(gòu)就跟上面的目錄項和索引節(jié)點中的圖一樣??梢钥吹?,在 inode 列表,存在著很多數(shù)據(jù)塊,塊是內(nèi)存中最小的尋址單元,見于磁盤中的章節(jié),一般可以設(shè)置帶大小為 2kb - 64kb 之間。
2. 文件系統(tǒng)的不同點
雖然大部分的文件系統(tǒng)也都有超級塊、索引節(jié)點和數(shù)據(jù)塊,但是各個文件系統(tǒng)的實現(xiàn)卻大不相同,這就導(dǎo)致了他們的側(cè)重點也不一樣。
拿 ext4 和 f2fs 來講:
- ext4連續(xù)讀取大文件更強,占用的空間更小
- f2fs隨機 IO 更快
說白了,也就是它們對于空閑空間分配和已有的數(shù)據(jù)管理方式不一致,不同的數(shù)據(jù)結(jié)構(gòu)和算法導(dǎo)致了不同的結(jié)果。
3. IO流程
這里的 IO 流程其實跟 VFS 差不多,畢竟文件系統(tǒng)是 VFS 的具體實現(xiàn)。
五、塊IO層
Linux 下面有兩大基本設(shè)備類型:
- 塊設(shè)備:能夠隨機訪問固定大小數(shù)據(jù)片的硬件設(shè)備,硬盤和閃存(下面介紹)就是常見的塊設(shè)備
- 字符設(shè)備:字符設(shè)備只能按照字符流的方式被有序訪問,比如鍵盤和串口
這兩個設(shè)備的區(qū)別就是是否能夠隨機訪問。拿屬于字符設(shè)備的鍵盤來說,當(dāng)我們輸入 Hello World
的時候,系統(tǒng)肯定不可以先得到得到 eholl wrodl
,這樣的話,輸出就亂套了。而對于閃存來說,常常是看完這個這些數(shù)據(jù)庫組成的圖片,又要讀間隔很遠(yuǎn)的數(shù)組塊的小說內(nèi)容,所以讀取的塊在磁盤上肯定不是連續(xù)的。
因為內(nèi)核管理塊設(shè)備實在太復(fù)雜了,所以就出現(xiàn)了管理塊設(shè)備的子系統(tǒng),就是上面說的文件系統(tǒng)。
1. 塊設(shè)備結(jié)構(gòu)
塊設(shè)備中常用的數(shù)據(jù)管理單位:
- 扇區(qū):設(shè)備的最小尋址單元
- 塊:文件系統(tǒng)的最小尋址單元,數(shù)倍大于扇區(qū)
- 片段:由數(shù)百至數(shù)千的塊組成
因為 Linux 中常常用的硬盤,這里我有點疑問,這里的管理單位是否和下面閃存管理單位一致?
2. IO過程
如果當(dāng)前有 IO 操作,內(nèi)核會建立一個 bio 結(jié)構(gòu)體的基本容器,它是由多個片段組成,每一個片段都是一小塊連續(xù)的內(nèi)存緩沖區(qū)。
之后,內(nèi)核會將這些 IO 請求保存在一個 request_queue 的請求隊列中。
如果按照 IO 請求產(chǎn)生的順序發(fā)向塊設(shè)備,性能肯定難以接受,所以內(nèi)核會按照磁盤地址對進入隊列之前提交的 IO 請求做合并與排序的預(yù)操作。
六、磁盤
移動設(shè)備中常用的持久化存儲是 Nand 閃存,UFS 又是 Nand 閃存中的佼佼者,其特點是速度更快、體積小和更省電。
當(dāng)今 Android 旗艦機基本上標(biāo)配 UFS 3.1,它們只是一塊兒很小的芯片:
閃存是一種非易失性存儲器,即使掉電了,數(shù)據(jù)也不會丟。閃存的存儲單元從小到大有:
- Cell(單元):是閃存存儲的最小單位,根據(jù)存儲的數(shù)量可以分為SLC(1bit/Cell)、MLC(2bit/Cell)、TLC(3bit/Cell)和QLC(4bit/Cell)
- Page(頁):由大量的 Cell 構(gòu)成,每個 Page 的大小通常是 16 kb,它是閃存能夠讀取的和寫入的最小單位
- Block(塊):每個塊由數(shù)百至數(shù)千的 Page 組成
- Plane(面):Plane 由數(shù)百至數(shù)千的 Black 組成
- Die(邏輯單元):每個 Die 由一個至多個 Plane,是閃存中可以執(zhí)行命令或者回報狀態(tài)的最小單元
對于每個 Cell 來說,是由一種類 NMOS 的雙層浮柵 MOS 管組成,大概是這樣:
對于 SLC(存儲1bit)來說:
- 如果需要1,在 P 極施加一個電壓,將電子吸出儲存單元
- 如果需要0,需要在頂層的控制極施加一個電壓,讓電子吸回存儲單元
這就構(gòu)成了數(shù)據(jù)存儲的最小單位,0和1!
總結(jié)
整個流程簡要的用一張圖來表示:
因為我對內(nèi)核也不是特別熟,文中難免有不對的地方,歡迎在評論區(qū)指正,如果覺得本文不錯,「點贊」是最好的肯定!
到此這篇關(guān)于深入了解Android IO的底層原理的文章就介紹到這了,更多相關(guān) Android IO原理內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Android實現(xiàn)五子棋游戲(局域網(wǎng)版)
這篇文章主要為大家詳細(xì)介紹了Android實現(xiàn)局域網(wǎng)版的五子棋游戲,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下2022-05-05Android ActivityManagerService啟動流程詳解
這篇文章主要介紹了Android ActivityManagerService啟動流程,AMS,即ActivityManagerService,是安卓java framework的一個服務(wù),運行在system_server進程。此服務(wù)十分重要,因為它管理著安卓的四大組件,是安卓APP開發(fā)者最常接觸到的一個服務(wù)2023-02-02協(xié)程作用域概念迭代RxTask?實現(xiàn)自主控制
這篇文章主要為大家介紹了協(xié)程作用域概念迭代RxTask實現(xiàn)自主控制詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2022-10-10