Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android存儲系統的架構與設計

Android存儲系統的架構與設計

編輯:關於Android編程

一、概述

本文講述Android存儲系統的架構與設計,基於Android 6.0的源碼,涉及到最為核心的便是MountService和Vold這兩個模塊以及之間的交互。為了縮減篇幅,只展示部分核心代碼。

MountService:Android Binder服務端,運行在system_server進程,用於跟Vold進行消息通信,比如MountService向Vold發送掛載SD卡的命令,或者接收到來自Vold的外設熱插拔事件。MountService作為Binder服務端,那麼相應的Binder客戶端便是StorageManager,通過binder IPC與MountService交互。

Vold:全稱為Volume Daemon,用於管理外部存儲設備的Native daemon進程,這是一個非常重要的守護進程,主要由NetlinkManager,VolumeManager,CommandListener這3部分組成。

1.1 模塊架構

從模塊地角度劃分Android整個存儲架構:

 

圖解:

  • Linux Kernel:通過uevent向Vold的NetlinkManager發送Uevent事件;

  • NetlinkManager:接收來自Kernel的Uevent事件,再轉發給VolumeManager;

  • VolumeManager:接收來自NetlinkManager的事件,再轉發給CommandListener進行處理;

  • CommandListener:接收來自VolumeManager的事件,通過socket通信方式發送給MountService;

  • MountService:接收來自CommandListener的事件。

1.2 進程架構

(1)先看看Java framework層的線程:

MountService運行在system_server進程,這裡查詢的便是system_server進程的所有子線程,system_server進程承載整個framework所有核心服務,子線程數有很多,這裡只列舉與MountService模塊相關的子線程。

(2)再看看Native層的線程:

Vold作為native守護進程,進程名為"/system/bin/vold",pid=387,通過ps -t可查詢到該進程下所有的子進程/線程。

小技巧:有讀者可能會好奇,為什麼/system/bin/sdcard是子進程,而非子線程呢?要回答這個問題,有兩個方法,其一就是直接看撸源碼,會發現這是通過fork方式創建的,而其他子線程都是通過pthread_create方式創建的。當然其實還有個更快捷的小技巧,就是直接看上圖中的第4列,這一列的含義是VSIZE,代表的是進程虛擬地址空間大小,是否共享地址空間,這是進程與線程最大的區別,再來看看/sdcard的VSIZE大小跟父進程不一樣,基本可以確實/sdcard是子進程。

(3) 從進程/線程視角來看Android存儲架構:

  • Java層:采用1個主線程(system_server) +3個子線程(VoldConnector, MountService, CryptdConnector);

  • Native層:采用1個主線程(/system/bin/vold) +3個子線程(vold) +1子進程(/system/bin/sdcard);

注:圖中紅色字代表的進程/線程名,vold進程通過pthread_create的方式創建的3個子線程名都為vold,圖中只是為了便於區別才標注為vold1, vold2, vold3,其實名稱都為vold。

Android還可劃分為內核空間(Kernel Space)和用戶空間(User space),從上圖可看出,Android存儲系統在User space總共采用9個進程/線程的架構模型。當然,除了這9個進/線程,另外還會在handler消息處理過程中使用到system_server的兩個子線程:android.fg和android.io。

Tips: 同一個模塊可以運行在各個不同的進程/線程, 同一個進程可以運行不同模塊的代碼,所以從進程角度和模塊角度劃分看到的有所不同的.

為了闡述清楚存儲系統的通信架構,主要分為以下4個過程:

  1. MountService發送消息:MountService是如何從向vold守護進程通信;

  2. MountService接收消息:MountService接收到vold發送過來的消息又是如何處理;

  3. Kernel上報事件:當存儲設備發生熱插拔等事件,kernel是如何通知用戶空間的vold;

  4. 不請自來的廣播:對於事件往往都是MountService下發,然後再收到底層的回應,但對於有些廣播卻非如此,而是由底層直接觸發,對於MountService來說卻是“不請自來”的消息。

限於篇幅過長,本文先講述前兩個過程,下一篇文章再來說說後兩個過程。

1.3 類關系圖

上圖中4個藍色塊便是前面談到的核心模塊。

二、 通信架構

Android存儲系統中涉及各個進程間通信,這個架構采用的socket,並沒有采用Android binder IPC機制。這樣的架構代碼大量更少,整體架構邏輯也相對簡單,在介紹通信過程前,先來看看MountService對象的實例化過程,那麼也就基本明白進程架構中system_sever進程為了MountService服務而單獨創建與共享使用到線程情況。

首先,MountService對象實例化的過程中完成是:

  1. 創建ICallbacks回調方法,FgThread線程名為"android.fg",此處用到的Looper便是線程"android.fg"中的Looper;

  2. 創建並啟動線程名為"MountService"的handlerThread;

  3. 創建OBB操作的handler,IoThread線程名為"android.io",此處用到的的Looper便是線程"android.io"中的Looper;

  4. 創建NativeDaemonConnector對象

  5. 創建並啟動線程名為"VoldConnector"的線程;

  6. 創建並啟動線程名為"CryptdConnector"的線程;

  7. 注冊監聽用戶添加、刪除的廣播;

從這裡便可知道共創建了3個線程:MountService,VoldConnector,CryptdConnector,另外還會使用到系統進程中的兩個線程android.fg和android.io. 這便是在文章開頭進程架構圖中Java framework層進程的創建情況.

2.1 MountService發送消息

system_server進程與vold守護進程間采用socket進行通信,這個通信過程是由MountService線程向vold線程發送消息。這裡以執行mount調用為例:

2.1.1 MountService.mount

public void mount(String volId) {
//【見小節2.1.2】
mConnector.execute("volume", "mount", vol.id, vol.mountFlags, vol.mountUserId);
}

2.1.2 NDC.execute

execute()經過層層調用到executeForList()

  • 首先,將帶執行的命令mSequenceNumber執行加1操作;
  • 再將cmd(例如3 volume reset)寫入到socket的輸出流;

  • 通過循環與poll機制阻塞等待底層響應該操作完成的結果;

有兩個情況會跳出循環:

  • 當超過1分鐘未收到vold相應事件的響應碼,則跳出阻塞等待;

  • 當收到底層的響應碼,且響應碼不屬於[100,200)區間,則跳出循環。

  • 對於執行時間超過500ms的時間,則額外輸出以NDC Command開頭的log信息,提示可能存在優化之處。

2.1.3 FL.onDataAvailable

MountService線程通過socket發送cmd事件給vold,對於vold守護進程在啟動的過程,初始化CommandListener時通過pthread_create創建子線程vold來專門監聽MountService發送過來的消息,當該線程接收到socket消息時,便會調用onDataAvailable()方法

2.1.4 FL.dispatchCommand

這是用於分發從MountService發送過來的命令,針對不同的命令調用不同的類。在處理過程中遇到下面情況,則會直接發送響應嗎500的應答消息給MountService

  • 當無法找到匹配的類,則會直接向MountService返回響應碼500,內容"Command not recognized"的應答消息;

  • 命令參數過長導致socket管道溢出,則會發送響應碼500,內容"Command too long"的應答消息。

2.1.5 CL.runCommand

例如前面發送過來的是volume mount,則會調用到CommandListener的內部類VolumeCmd的runCommand來處理該消息,並進入mount分支。

2.1.6 小節

MountService向vold發送消息後,便阻塞在圖中的MountService線程的NDC.execute()方法,那麼何時才會退出呢?圖的後半段MonutService接收消息的過程會有答案,那便是在收到消息,並且消息的響應嗎不屬於區間[600,700)則添加事件到ResponseQueue,從而喚醒阻塞的MountService繼續執行。關於上圖的後半段介紹的便是MountService接收消息的流程。

2.2 MountService接收消息

當Vold在處理完完MountService發送過來的消息後,會通過sendGenericOkFail發送應答消息給上層的MountService。

2.2.1 響應碼

  • 當執行成功,則發送響應碼為500的成功應答消息;

  • 當執行失敗,則發送響應碼為400的失敗應答消息。

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

響應碼 事件類別 對應方法 [100, 200) 部分響應,隨後繼續產生事件 isClassContinue [200, 300) 成功響應 isClassOk [400, 500) 遠程服務端錯誤 isClassServerError [500, 600) 本地客戶端錯誤 isClassClientError [600, 700) 遠程Vold進程自觸發的事件 isClassUnsolicited

例如當操作執行成功,VoldConnector線程能收到類似`RCV <- {200 3 Command succeeded}的響應事件。其中對於[600,700)響應碼是由Vold進程"不請自來"的事件,主要是針對disk,volume的一系列操作,比如設備創建,狀態、路徑改變,以及文件類型、uid、標簽改變等事件都是底層直接觸發,後面再會詳細講。介紹完響應碼,接著繼續來說說發送應答消息的過程:

2.2.2 SC.sendMsg

sendMsg經過層層調用,進入sendDataLockedv方法

2.2.3 NDC.listenToSocket

應答消息寫入socket管道後,在MountService的另個線程"VoldConnector"中建立了名為vold的socket的客戶端,通過循環方式不斷監聽Vold服務端發送過來的消息。

監聽也是阻塞的過程,當收到不同的消息相應碼,采用不同的行為:

  • 當響應嗎不屬於區間[600,700):則將該事件添加到mResponseQueue,並且觸發響應事件所對應的請求事件不再阻塞到ResponseQueue.poll,那麼線程繼續往下執行,即前面小節[2.1.2] NDC.execute的過程。

  • 當響應碼區間為[600,700):則發送消息交由mCallbackHandler處理,向線程android.fg發送Handler消息,該線程收到後回調NativeDaemonConnector的handleMessage來處理。

2.2.4 小節

三、總結

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。

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved