編輯:關於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部分組成。
從模塊地角度劃分Android整個存儲架構:
圖解:
Linux Kernel:通過uevent向Vold的NetlinkManager發送Uevent事件;
NetlinkManager:接收來自Kernel的Uevent事件,再轉發給VolumeManager;
VolumeManager:接收來自NetlinkManager的事件,再轉發給CommandListener進行處理;
CommandListener:接收來自VolumeManager的事件,通過socket通信方式發送給MountService;
(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);
注:圖中紅色字代表的進程/線程名,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個過程:
MountService發送消息:MountService是如何從向vold守護進程通信;
MountService接收消息:MountService接收到vold發送過來的消息又是如何處理;
Kernel上報事件:當存儲設備發生熱插拔等事件,kernel是如何通知用戶空間的vold;
限於篇幅過長,本文先講述前兩個過程,下一篇文章再來說說後兩個過程。
上圖中4個藍色塊便是前面談到的核心模塊。
Android存儲系統中涉及各個進程間通信,這個架構采用的socket,並沒有采用Android binder IPC機制。這樣的架構代碼大量更少,整體架構邏輯也相對簡單,在介紹通信過程前,先來看看MountService對象的實例化過程,那麼也就基本明白進程架構中system_sever進程為了MountService服務而單獨創建與共享使用到線程情況。
首先,MountService對象實例化的過程中完成是:
創建ICallbacks回調方法,FgThread線程名為"android.fg",此處用到的Looper便是線程"android.fg"中的Looper;
創建並啟動線程名為"MountService"的handlerThread;
創建OBB操作的handler,IoThread線程名為"android.io",此處用到的的Looper便是線程"android.io"中的Looper;
創建NativeDaemonConnector對象
創建並啟動線程名為"VoldConnector"的線程;
創建並啟動線程名為"CryptdConnector"的線程;
從這裡便可知道共創建了3個線程:MountService,VoldConnector,CryptdConnector,另外還會使用到系統進程中的兩個線程android.fg和android.io. 這便是在文章開頭進程架構圖中Java framework層進程的創建情況.
system_server進程與vold守護進程間采用socket進行通信,這個通信過程是由MountService線程向vold線程發送消息。這裡以執行mount調用為例:
public void mount(String volId) {
//【見小節2.1.2】
mConnector.execute("volume", "mount", vol.id, vol.mountFlags, vol.mountUserId);
}
execute()經過層層調用到executeForList()
再將cmd(例如3 volume reset)寫入到socket的輸出流;
通過循環與poll機制阻塞等待底層響應該操作完成的結果;
有兩個情況會跳出循環:
當超過1分鐘未收到vold相應事件的響應碼,則跳出阻塞等待;
當收到底層的響應碼,且響應碼不屬於[100,200)區間,則跳出循環。
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"的應答消息;
例如前面發送過來的是volume mount,則會調用到CommandListener的內部類VolumeCmd的runCommand來處理該消息,並進入mount分支。
2.1.6 小節
MountService向vold發送消息後,便阻塞在圖中的MountService線程的NDC.execute()方法,那麼何時才會退出呢?圖的後半段MonutService接收消息的過程會有答案,那便是在收到消息,並且消息的響應嗎不屬於區間[600,700)則添加事件到ResponseQueue,從而喚醒阻塞的MountService繼續執行。關於上圖的後半段介紹的便是MountService接收消息的流程。
當Vold在處理完完MountService發送過來的消息後,會通過sendGenericOkFail發送應答消息給上層的MountService。
不同的響應碼(VoldResponseCode),代表著系統不同的處理結果,主要分為下面幾大類:
例如當操作執行成功,VoldConnector線程能收到類似`RCV <- {200 3 Command succeeded}的響應事件。其中對於[600,700)響應碼是由Vold進程"不請自來"的事件,主要是針對disk,volume的一系列操作,比如設備創建,狀態、路徑改變,以及文件類型、uid、標簽改變等事件都是底層直接觸發,後面再會詳細講。介紹完響應碼,接著繼續來說說發送應答消息的過程:
應答消息寫入socket管道後,在MountService的另個線程"VoldConnector"中建立了名為vold的socket的客戶端,通過循環方式不斷監聽Vold服務端發送過來的消息。
監聽也是阻塞的過程,當收到不同的消息相應碼,采用不同的行為:
當響應嗎不屬於區間[600,700):則將該事件添加到mResponseQueue,並且觸發響應事件所對應的請求事件不再阻塞到ResponseQueue.poll,那麼線程繼續往下執行,即前面小節[2.1.2] NDC.execute的過程。
本文首先從模塊化和進程的視角來整體上描述了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。
以Google原生的Android存儲系統的架構設計主要采用Socket阻塞式通信方式,雖然vold的native層面有多個子線程干活,但各司其職,真正處理上層發送過來的命令,仍然是單通道的模式。
目前外置存儲設備比如sdcard或者otg的硬件質量參差不齊,且隨使用時間碎片化程度也越來越嚴重,對於存儲設備掛載的過程中往往會有磁盤檢測fsck_msdos或者整理fstrim的動作,那麼勢必會阻塞多線程並發訪問,影響系統穩定性,從而造成系統ANR。
例如系統剛啟動過程中reset操作需要重新掛載外置存儲設備,而緊接著system_server主線程需要執行的volume user_started操作便會被阻塞,阻塞超過20s則系統會拋出Service Timeout的ANR。
簡介Container for a tabbed window view. This object holds two children: a set of tab la
用UDP協議與Socket調試工具進行測試。 SocketActivity.java: package com.example.socket; impo
本文實例為大家分享了Android Chronometer計時器基本使用方法,供大家參考,具體內容如下在默認情況下,Chronometer組件只輸出MM:SS或H:MM:
在Andoird使用Android自帶的那些組件,像SlidingDrawer和DrawerLayout都是抽屜效果的菜單,但是在項目很多要實現的功能都收到Android