網站首頁 編程語言 正文
前言
最近在看《Linux內核設計與實現》的時候,就想著要不把知識串聯一下吧。聊什么呢?今天先來聊聊 Android IO 的調用鏈路。說起 IO,這可真是一個很復雜的過程,里面涉及了很多內容,先是軟件,最后到硬件,用一張圖來表示一下吧:
本文打算簡單得和大伙討論一下 IO 的流程。
一、應用層
作為應用開發者,我們通常是 IO 發起點,比如用戶說這本小說很好看,我要下載到本地,或者,這張圖拍的不錯,分享給你看一下。
雖然這些都是常見的 IO 場景,但是你知道有哪些 IO 嗎?
1. IO的分類
通常去使用 IO 的時候,我們會有很多種選擇,常見的有:
- 緩沖與非緩沖 IO
- 直接與非直接 IO
- 阻塞與非阻塞 IO
- 同步與異步 IO
大家平時可能也就聽過緩沖 IO 和 阻塞 IO,這些可能是我們平時開發可能涉及到的。
1.1 緩沖和直接
前兩種分類都是使用緩存的。
緩沖是針對標準庫的。
Linux 標準庫定義了很多操作系統的基礎服務,比如輸入/輸出、字符串處理等等。Android 操作系統的標準庫是 Bionic,它可是應用層聯系內核的橋梁,我們也可以通過 NDK 訪問 Bionic。
使用標準庫進行 IO 我們稱為緩沖 IO,我們讀文件的時候,經常遇到,讀完一行才會讓輸出,在 Android 內部也做了類似的處理。
直接是針對內核的。
使用 Binder 跨進程傳遞數據的時候,需要將數據從用戶空間傳遞到內核空間,非直接 IO 也這樣,內核空間會多做一層頁緩存,如果做直接 IO,應用程序會直接調用文件系統。
緩沖和非直接 IO 就像 IO 調度的一級和二級緩存,為什么要做這么多緩存呢?因為操作磁盤本身就是消耗資源的,不加緩存頻繁 IO 不僅會耗費資源也會耗時。
1.2 阻塞和異步
同步和異步我想大家都了解什么意思。
阻塞 IO指的是當用戶執行讀寫的時候,線程會一直阻塞,數據準備和將數據拷貝到用戶進程都是阻塞的:
Java 中的 NIO 是非阻塞 IO,當用戶發起讀寫的時候,線程不會阻塞,之后,用戶可以通過輪詢或者接受通知的方式,獲取當前 IO 調度的結果:
即使是非阻塞 IO,對于讀數據來說,也只有準備數據的過程是異步,將數據從內核拷貝到用戶進程這個過程還是同步的。所以非阻塞 IO 不能算是真正意義上的異步 IO。
真正的異步 IO 應該是這樣的:
準備數據和將數據拷貝從內核到用戶進程都應該是異步的,當收到通知的的時候,我們已經可以在應用進程使用數據了。
2. IO流程
作為應用層開發,大家做 IO 的場景并不多,最多也就是使用?BufferedInputStream
?和?BufferedOutputStream
?讀寫文件,至于 NIO ,那就更少見了。
我們了解一下阻塞 IO 的讀調用流程。
二、sysCall系統調用
應用層調完了,下面會直接進入內核嗎?
除去直接 IO,大部分都不會!用戶空間和內核之間隔著一個系統調用(sysCall),它的作用如下:
- 給用戶空間提供抽象的訪問硬件的接口:比如申請系統資源、操作設備讀寫等
- 保證系統的安全和穩定:內核可以對用戶進程的訪問做出一些裁決,防止用戶進程做出一些危害系統的事情
畢竟內核很復雜,抽象出通用的接口,可以防止用戶空間的進程僭越,獲取到它不該獲取的內容。
為了能夠讓應用進程聯系上內核,它會通過一個軟中斷,通知內核,我想調用內核中 sysCall 中的讀接口。
對于讀 IO,系統調用中有一個?sys_read
?方法與之對應,內核收到通知執行該方法的時候,就會執行虛擬文件系統的?read
?方法。
三、虛擬文件系統
文件系統實在是太多了,比如我手機用戶空間的文件系統是 f2fs,系統空間的文件系統是 ext4。對于應用程序來說,它就想調用個讀方法,不想管你手機的底層文件系統是什么!
虛擬文件系統就是來干這活的,它可以屏蔽具體的文件系統,定義了一組所有文件系統都支持的數據結構和標準接口。這樣,應用層的程序員只需了解 VFS 提供的統一接口就行。
虛擬文件系統常被稱為 VFS(Virtual File System),下稱 VFS。
1. VFS結構
VFS 采用的是面向對象的設計思路,它常常有下列的對象(C語言中的結構體)構成:
這些對象構成了基本的虛擬文件系統。
不過,光有這些對象可不行,VFS 還得知道如何操作它們,所以,每個對象中還存在對應的操作對象:
-
super_operation
?對象:內核針對超級塊所能調用的方法 -
inode_operation
?對象:內核針對索引結點所能調用的方法 -
dentry_operation
?對象:內核針對目錄項所能操作的方法 -
file_operation
?對象:內核針對進程中打開的文件所能操作的方法
大伙最熟悉的應該是文件,這是我們能夠在進程中實實在在能夠操作的,比如,在文件的?file_operation
?中,就有我們熟悉的讀、寫、拷貝、打開、寫入磁盤等方法。
不知道大伙兒有沒注意到,我特意標注了超級塊和索引節點存在于內存和磁盤,而目錄項和文件只存在于內存。
我的理解是對于磁盤,索引節點已經足夠記錄文件信息,并不需要目錄項再來記錄層級關系;而對于內存來說,為了節省內存,只會把需要用到的文件和目錄項所用到的索引節點加入內存,文件系統只有被掛載的時候超級塊才會被加入到內存中。
目錄項、索引節點、文件和超級塊結構圖:
上面的結構圖還有幾點要注意一下:
- 目錄項不等于目錄這個概念,對于 /home/pic/a.jpg 來說,根目錄 / 、home 目錄、pic 目錄 和 a.jpg 都屬于目錄項
- 每個目錄項都會持有索引節點的指針
- 索引節點包含內核在操作文件需要的全部信息,比如存在磁盤的位置等
- 進程中打開的文件持有目錄項
2. VFS中的緩存
結合本文中的第一張圖,我們會發現,VFS 有目錄項緩存、索引節點緩存和頁緩存,目錄項和索引節點我們都知道什么意思,那頁緩存呢?
頁緩存是由 RAM 中的物理頁組成的,對應著 ROM 上的物理地址。我們都知道,現在主流 Android 的 RAM 訪問速度高達是 8.5 GB/S,而 ROM 的訪問速度最高只有 6400 MB/S,所以訪問 RAM 的速度要遠遠快于 ROM,頁緩存的目的也在于此。
當發起一個讀操作的時候,內核會首先檢查需要的數據是否在頁緩存,如果在,直接從內存中讀取,我們稱之為緩存命中;如果不在,那么內核在讀取數據的時候,將讀到的數據放入頁緩存,需要注意的是,頁緩存可以存入全部文件內容,也可以僅僅存幾頁。
3. IO流程
經過系統調用,讀 IO 進入了 VFS。
就去找文件對象(VFS 中的),通過文件對象的?file_operation
?對象,調用?read
?方法,傳入讀取的數據量。不過?read
?方法也是找到文件對象對應的目錄項,目錄項又找到索引節點,畢竟,只有索引節點知道文件存在哪兒?
通過索引節點,內核就能唯一確定一個文件,然后在頁緩存中尋找是否有自己需要的數據,找到就直接返回。沒找到就去進行下一步的操作。
四、文件系統
VFS 定義了文件系統的統一接口,具體的實現了交給了文件系統,超級塊里面的數據如何組織、目錄和索引結構如何設計、怎么分配和清理數據,這都是設計一個文件系統必須考慮的!
說白了,文件系統就是用來管理磁盤里的持久化的數據的,對于 Android 來說,最常見的就是 ext4 和 f2fs。
1. 文件系統結構
因為文件系統是 VFS 的具體實現,所以同樣有目錄項、索引節點和超級塊,上面的圖片用來描述文件系統也同樣適合。
拿早起 ext2 的系統結構來講:
每一個 ext2 都由大量的塊組組成,每個塊組的結構就跟上面的目錄項和索引節點中的圖一樣??梢钥吹剑?inode 列表,存在著很多數據塊,塊是內存中最小的尋址單元,見于磁盤中的章節,一般可以設置帶大小為 2kb - 64kb 之間。
2. 文件系統的不同點
雖然大部分的文件系統也都有超級塊、索引節點和數據塊,但是各個文件系統的實現卻大不相同,這就導致了他們的側重點也不一樣。
拿 ext4 和 f2fs 來講:
- ext4連續讀取大文件更強,占用的空間更小
- f2fs隨機 IO 更快
說白了,也就是它們對于空閑空間分配和已有的數據管理方式不一致,不同的數據結構和算法導致了不同的結果。
3. IO流程
這里的 IO 流程其實跟 VFS 差不多,畢竟文件系統是 VFS 的具體實現。
五、塊IO層
Linux 下面有兩大基本設備類型:
- 塊設備:能夠隨機訪問固定大小數據片的硬件設備,硬盤和閃存(下面介紹)就是常見的塊設備
- 字符設備:字符設備只能按照字符流的方式被有序訪問,比如鍵盤和串口
這兩個設備的區別就是是否能夠隨機訪問。拿屬于字符設備的鍵盤來說,當我們輸入?Hello World
?的時候,系統肯定不可以先得到得到?eholl wrodl
,這樣的話,輸出就亂套了。而對于閃存來說,常常是看完這個這些數據庫組成的圖片,又要讀間隔很遠的數組塊的小說內容,所以讀取的塊在磁盤上肯定不是連續的。
因為內核管理塊設備實在太復雜了,所以就出現了管理塊設備的子系統,就是上面說的文件系統。
1. 塊設備結構
塊設備中常用的數據管理單位:
- 扇區:設備的最小尋址單元
- 塊:文件系統的最小尋址單元,數倍大于扇區
- 片段:由數百至數千的塊組成
因為 Linux 中常常用的硬盤,這里我有點疑問,這里的管理單位是否和下面閃存管理單位一致?
2. IO過程
如果當前有 IO 操作,內核會建立一個 bio 結構體的基本容器,它是由多個片段組成,每一個片段都是一小塊連續的內存緩沖區。
之后,內核會將這些 IO 請求保存在一個 request_queue 的請求隊列中。
如果按照 IO 請求產生的順序發向塊設備,性能肯定難以接受,所以內核會按照磁盤地址對進入隊列之前提交的 IO 請求做合并與排序的預操作。
六、磁盤
移動設備中常用的持久化存儲是 Nand 閃存,UFS 又是 Nand 閃存中的佼佼者,其特點是速度更快、體積小和更省電。
當今 Android 旗艦機基本上標配 UFS 3.1,它們只是一塊兒很小的芯片:
閃存是一種非易失性存儲器,即使掉電了,數據也不會丟。閃存的存儲單元從小到大有:
- Cell(單元):是閃存存儲的最小單位,根據存儲的數量可以分為SLC(1bit/Cell)、MLC(2bit/Cell)、TLC(3bit/Cell)和QLC(4bit/Cell)
- Page(頁):由大量的 Cell 構成,每個 Page 的大小通常是 16 kb,它是閃存能夠讀取的和寫入的最小單位
- Block(塊):每個塊由數百至數千的 Page 組成
- Plane(面):Plane 由數百至數千的 Black 組成
- Die(邏輯單元):每個 Die 由一個至多個 Plane,是閃存中可以執行命令或者回報狀態的最小單元
對于每個 Cell 來說,是由一種類 NMOS 的雙層浮柵 MOS 管組成,大概是這樣:
對于 SLC(存儲1bit)來說:
- 如果需要1,在 P 極施加一個電壓,將電子吸出儲存單元
- 如果需要0,需要在頂層的控制極施加一個電壓,讓電子吸回存儲單元
這就構成了數據存儲的最小單位,0和1!
總結
整個流程簡要的用一張圖來表示:
因為我對內核也不是特別熟,文中難免有不對的地方,歡迎在評論區指正,如果覺得本文不錯,「點贊」是最好的肯定!
原文鏈接:https://juejin.cn/post/7077724308574830605
相關推薦
- 2022-10-14 初識RPC中間件zeroC ICE工具之iceca
- 2022-11-06 Python解決多進程間訪問效率低的方法總結_python
- 2023-11-16 【Python】從列表/dataframe/pandas中刪除 nan
- 2022-07-28 XML基本概念XPath、XSLT與XQuery函數介紹_XML/RSS
- 2022-09-15 React手寫一個手風琴組件示例_React
- 2022-10-19 R語言安裝以及手動安裝devtools的詳細圖文教程_R語言
- 2022-11-04 關于docker部署服務時ip無法訪問服務正常的問題_docker
- 2022-06-24 jQuery一鍵移除使前端項目脫離對它的依賴_jquery
- 最近更新
-
- window11 系統安裝 yarn
- 超詳細win安裝深度學習環境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權
- redisson分布式鎖中waittime的設
- maven:解決release錯誤:Artif
- restTemplate使用總結
- Spring Security之安全異常處理
- MybatisPlus優雅實現加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務發現-Nac
- Spring Security之基于HttpR
- Redis 底層數據結構-簡單動態字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應用詳解
- 聊聊消息隊列,發送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支