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。
2.3.1 Uevent && Netlink
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 發送過來的響應消息:
-
情況一:響應碼不屬於區間 [600, 700),則直接將響應消息添加到響應隊列 ResponseQueue,當響應隊列有數據到來,便會喚醒另個線程 MountService 阻塞操作 poll 輪詢操作。
-
情況二:響應碼屬於區間 [600, 700),則便是 Unsolicited broadcasts,即不請自來的廣播,當收到這類事件,則處理流程較第一種情況更復雜。
接下來說說第二種情況,對於不清自來的廣播,這裏的廣播並非四大組件的廣播,而是 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),代表着系統不同的處理結果,主要分爲下面幾大類:
100 系列
請求操作已初始化, 收到部分響應. 在處理下一個新命令前, 期待收到另一個響應消息
200 系列
請求操作成功完成
400 系列
命令已接收, 但請求操作並沒有執行
500 系列
命令沒有接收, 請求操作也沒有執行
600 系列
不請自來的廣播, 是指由底層觸發的事件, 主要是針對 disk,volume 的一系列操作,比如設備創建,狀態、路徑改變,以及文件類型、uid、標籤改變等事件都是底層直接觸發。
作者:袁輝輝
來源:http://gityuan.com/2016/07/23/android-io-arch/
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/dJQD3mqs9GRPuHM8DIcoOQ