Android 存儲系統解析之架構篇(下)

基於 Android 6.0 的源碼,剖析存儲架構的設計

上篇講到 MountService 接收消息(第 2.2 節),當 Vold 在處理完完 MountService 發送過來的消息後,會通過 sendGenericOkFail 發送應答消息給上層的 MountService。

本期繼續銜接內容:

2.3 Kernel 上報事件

介紹完 MonutService 與 vold 之間的交互通信,那麼再來看看 Kernel 是如何上報事件到 vold 的流程。再介紹這個之前,先簡單看看 vold 啓動時都創建了哪些對象。通過 system/vold/Main.cpp 的 main() 方法創建並啓動:VolumeManager,NetlinkManager ,NetlinkHandler,CommandListener,CryptCommandListener。

Kernel 上報事件給用戶空間採用了 Netlink 方式,Netlink 是一種特殊的 socket,它是 Linux 所特有的。傳送的消息是暫存在 socket 接收緩存中,並不被接收者立即處理,所以 netlink 是一種異步通信機制。而對於 syscall 和 ioctl 則都是同步通信機制。

Linux 系統中大量採用 Netlink 機制來進行用戶空間程序與 kernel 的通信。例如設備熱插件,這會產生 Uevent(User Space event,用戶空間事件) 是 Linux 系統中用戶空間與內核空間之間通信的消息內容,主要用於設備驅動的事件通知。Uevent 是 Kobject 的一部分,當 Kobject 狀態改變時通知用戶空間程序。對於 kobject_action 包括 KOBJ_ADD,KOBJ_REMOVE,KOBJ_CHANGE,KOBJ_MOVE,KOBJ_ONLINE,KOBJ_OFFLINE,當發送任何一種 action 都會引發 Kernel 發送 Uevent 消息。

vold 早已準備就緒等待着 Kernel 上報 Uevent 事件,接下來看看 vold 是如何接收 Uevent 事件,這就從 NetlinkManager 啓動開始說起。

2.3.2 NM.start

NetlinkManager 啓動的過程中,會創建並啓動 NetlinkHandler,在該過程會通過 pthrea_create 創建子線程專門用於接收 Kernel 發送過程的 Uevent 事件,當收到數據時會調用 NetlinkListener 的 onDataAvailable 方法。

2.3.3 NL.onDataAvailable

2.3.4 NH.onEvent

驅動設備分爲字符設備、塊設備、網絡設備。對於字符設備按照字符流的方式被有序訪問,字符設備也稱爲裸設備,可以直接讀取物理磁盤,不經過系統緩存,例如鍵盤直接產生中斷。而塊設備是指系統中能夠隨機(不需要按順序)訪問固定大小數據片(chunks)的設備,例如硬盤;塊設備則是通過系統緩存進行讀取。

2.3.5 VM.handleBlockEvent

2.3.6 小節

此處,我們以設備插入爲例,來描繪一下整個流程圖:

2.4 不請自來的廣播

線程 VoldConnector 通過 socket 不斷監聽來自 vold 發送過來的響應消息:

接下來說說第二種情況,對於不清自來的廣播,這裏的廣播並非四大組件的廣播,而是 vold 通過 socket 發送過來的消息。還記得還文章的開頭講到進程架構時,提到會涉及 system_server 的線程 android.fg,那麼這個過程就會講到該線程的作用。回到 NDC 的監聽 socket 過程。

2.4.1 NDC.listenToSocket


通過 handler 消息機制,由 mCallbackHandler 處理,先來看看其初始化過程:

mCallbackHandler = new Handler(mLooper, this);
Looper = FgThread.get().getLooper();

可以看出 Looper 採用的是線程 android.fg 的 Looper,消息回調處理方法爲 NativeDaemonConnector 的 handleMessage 來處理。那麼這個過程就等價於向線程 android.fg 發送 Handler 消息,該線程收到消息後回調 NativeDaemonConnector 的 handleMessage 來處理。

2.4.2 NDC.handleMessage

此處的 mCallbacks,是由實例化 NativeDaemonConnector 對象時傳遞進來的,在這裏是指 MountService。轉了一圈,又回到 MountService。

2.4.3 MS.onEvent

onEventLocked 增加同步鎖,用於多線程併發訪問的控制。根據 vold 發送過來的不同響應碼將採取不同的處理流程。

2.4.4 MS.onEventLocked

這裏以收到 vold 發送過來的 RCV <- {650 public ...} 爲例,即掛載外置 sdcard/otg 外置存儲的流程:

2.4.5 MS.onVolumeCreatedLocked

這裏又遇到一個 Handler 類型的對象 mHandler, 再來看看其定義:

該 Handler 用到 Looper 便是線程 MountService 中的 Looper,回調方法 handleMessage 位於 MountServiceHandler 類:

2.4.6 MSH.handleMessage

當收到 H_VOLUME_MOUNT 消息後,線程 MountService 便開始向 vold 發送 mount 操作事件,再接下來的流程在前面小節【2.1】已經介紹過。

2.4.7 小結

三、總結

3.1 概括

本文首先從模塊化和進程的視角來整體上描述了 Android 存儲系統的架構,並分別展開對 MountService, vold, kernel 這三者之間的通信流程的剖析。

{1}Java framework 層:採用 1 個主線程 (system_server) + 3 個子線程 (VoldConnector, MountService, CryptdConnector);MountService 線程不斷向 vold 下發存儲相關的命令,比如 mount, mkdirs 等操作;而線程 VoldConnector 一直處於等待接收 vold 發送過來的應答事件;CryptdConnector 通信原理和 VoldConnector 大抵相同,有興趣地讀者可自行閱讀。

(2)Native 層:採用 1 個主線程 (/system/bin/vold) + 3 個子線程 (vold) + 1 子進程 (/system/bin/sdcard);vold 進程中會通過 pthread_create 方式來生成 3 個 vold 子線程,其中兩個 vold 線程分別跟上層 system_server 進程中的線程 VoldConnector 和 CryptdConnector 通信,第 3 個 vold 線程用於與 kernel 進行 netlink 方式通信。

本文更多的是以系統的角度來分析存儲系統,那麼對於 app 來說,那麼地方會直接用到的呢?其實用到的地方很多,例如存儲設備掛載成功會發送廣播讓 app 知曉當前存儲掛載情況;其次當 app 需要創建目錄時,比如 getExternalFilesDirs,getExternalCacheDirs 等當目錄不存在時都需向存儲系統發出 mkdirs 的命令。另外,MountService 作爲 Binder 服務端,那自然而然會有 Binder 客戶端,那就是 StorageManager,這個比較簡單就不再細說了,歡迎大家與 Gityuan。

3.2 架構的思考

以 Google 原生的 Android 存儲系統的架構設計主要採用 Socket 阻塞式通信方式,雖然 vold 的 native 層面有多個子線程幹活,但各司其職,真正處理上層發送過來的命令,仍然是單通道的模式。

目前外置存儲設備比如 sdcard 或者 otg 的硬件質量參差不齊,且隨使用時間碎片化程度也越來越嚴重,對於存儲設備掛載的過程中往往會有磁盤檢測 fsck_msdos 或者整理 fstrim 的動作,那麼勢必會阻塞多線程併發訪問,影響系統穩定性,從而造成系統 ANR。

例如系統剛啓動過程中 reset 操作需要重新掛載外置存儲設備,而緊接着 system_server 主線程需要執行的 volume user_started 操作便會被阻塞,阻塞超過 20s 則系統會拋出 Service Timeout 的 ANR。

四. 響應碼附錄

關於響應嗎, Java 層定義在MountService.java中的內部類VoldResponseCode, 與 Native 層定義在文件/system/vold/ResponseCode.h中的響應碼是相互對應.

不同的響應碼 (VoldResponseCode),代表着系統不同的處理結果,主要分爲下面幾大類:

DJ6ZgN

100 系列

請求操作已初始化, 收到部分響應. 在處理下一個新命令前, 期待收到另一個響應消息

VCV3N1

200 系列

請求操作成功完成

wmj6Gv

400 系列

命令已接收, 但請求操作並沒有執行

QvPxAC

500 系列

命令沒有接收, 請求操作也沒有執行

C0Qu2f

600 系列

不請自來的廣播, 是指由底層觸發的事件, 主要是針對 disk,volume 的一系列操作,比如設備創建,狀態、路徑改變,以及文件類型、uid、標籤改變等事件都是底層直接觸發。

gYsJnR

px1wEv

kt4I11

作者:袁輝輝

來源:http://gityuan.com/2016/07/23/android-io-arch/

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/dJQD3mqs9GRPuHM8DIcoOQ