天天看點

Android存儲系統的架構與設計

本文講述android存儲系統的架構與設計,基于android 6.0的源碼,涉及到最為核心的便是mountservice和vold這兩個子產品以及之間的互動。為了縮減篇幅,隻展示部分核心代碼。

mountservice:android binder服務端,運作在system_server程序,用于跟vold進行消息通信,比如<code>mountservice</code>向<code>vold</code>發送挂載sd卡的指令,或者接收到來自<code>vold</code>的外設熱插拔事件。mountservice作為binder服務端,那麼相應的binder用戶端便是storagemanager,通過binder

ipc與mountservice互動。

vold:全稱為volume daemon,用于管理外部儲存設備的native daemon程序,這是一個非常重要的守護程序,主要由netlinkmanager,volumemanager,commandlistener這3部分組成。

從子產品地角度劃分android整個存儲架構:

Android存儲系統的架構與設計

圖解:

linux kernel:通過<code>uevent</code>向vold的netlinkmanager發送uevent事件;

netlinkmanager:接收來自kernel的<code>uevent</code>事件,再轉發給volumemanager;

volumemanager:接收來自netlinkmanager的事件,再轉發給commandlistener進行處理;

commandlistener:接收來自volumemanager的事件,通過<code>socket</code>通信方式發送給mountservice;

mountservice:接收來自commandlistener的事件。

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

Android存儲系統的架構與設計

mountservice運作在system_server程序,這裡查詢的便是system_server程序的所有子線程,system_server程序承載整個framework所有核心服務,子線程數有很多,這裡隻列舉與mountservice子產品相關的子線程。

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

Android存儲系統的架構與設計

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

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

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

Android存儲系統的架構與設計

java層:采用 <code>1個主線程</code>(system_server) + <code>3個子線程</code>(voldconnector,

mountservice, cryptdconnector);

native層:采用 <code>1個主線程</code>(/system/bin/vold) + <code>3個子線程</code>(vold)

+ <code>1子程序</code>(/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的兩個子線程:<code>android.fg</code>和<code>android.io</code>。

tips: 同一個子產品可以運作在各個不同的程序/線程, 同一個程序可以運作不同子產品的代碼,是以從程序角度和子產品角度劃分看到的有所不同的.

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

mountservice發送消息:mountservice是如何從向vold守護程序通信;

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

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

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

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

Android存儲系統的架構與設計
Android存儲系統的架構與設計

上圖中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個線程:<code>mountservice</code>,<code>voldconnector</code>,<code>cryptdconnector</code>,另外還會使用到系統程序中的兩個線程<code>android.fg</code>和<code>android.io</code>.

這便是在文章開頭程序架構圖中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()

Android存儲系統的架構與設計

首先,将帶執行的指令msequencenumber執行加1操作;

再将cmd(例如<code>3 volume reset</code>)寫入到socket的輸出流;

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

有兩個情況會跳出循環: 

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

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

對于執行時間超過500ms的時間,則額外輸出以<code>ndc command</code>開頭的log資訊,提示可能存在優化之處。

mountservice線程通過socket發送cmd事件給vold,對于vold守護程序在啟動的過程,初始化commandlistener時通過<code>pthread_create</code>建立子線程vold來專門監聽mountservice發送過來的消息,當該線程接收到socket消息時,便會調用ondataavailable()方法

Android存儲系統的架構與設計

2.1.4 fl.dispatchcommand

Android存儲系統的架構與設計

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

當無法找到比對的類,則會直接向mountservice傳回響應碼500,内容"command not recognized"的應答消息;

指令參數過長導緻socket管道溢出,則會發送響應碼500,内容"command too long"的應答消息。

例如前面發送過來的是<code>volume mount</code>,則會調用到commandlistener的内部類volumecmd的runcommand來處理該消息,并進入mount分支。

Android存儲系統的架構與設計

2.1.6 小節

Android存儲系統的架構與設計

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

當vold在處理完完mountservice發送過來的消息後,會通過sendgenericokfail發送應答消息給上層的mountservice。

Android存儲系統的架構與設計

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

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

響應碼

事件類别

對應方法

[100, 200)

部分響應,随後繼續産生事件

isclasscontinue

[200, 300)

成功響應

isclassok

[400, 500)

遠端服務端錯誤

isclassservererror

[500, 600)

本地用戶端錯誤

isclassclienterror

[600, 700)

遠端vold程序自觸發的事件

isclassunsolicited

例如當操作執行成功,voldconnector線程能收到類似`rcv &lt;- {200 3 command succeeded}的響應事件。其中對于[600,700)響應碼是由vold程序"不請自來"的事件,主要是針對disk,volume的一系列操作,比如裝置建立,狀态、路徑改變,以及檔案類型、uid、标簽改變等事件都是底層直接觸發,後面再會詳細講。介紹完響應碼,接着繼續來說說發送應答消息的過程:

Android存儲系統的架構與設計
Android存儲系統的架構與設計

應答消息寫入socket管道後,在mountservice的另個線程"voldconnector"中建立了名為<code>vold</code>的socket的用戶端,通過循環方式不斷監聽vold服務端發送過來的消息。

Android存儲系統的架構與設計

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

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

當響應碼區間為[600,700):則發送消息交由mcallbackhandler處理,向線程<code>android.fg</code>發送handler消息,該線程收到後回調nativedaemonconnector的<code>handlemessage</code>來處理。

Android存儲系統的架構與設計

本文首先從子產品化和程序的視角來整體上描述了android存儲系統的架構,并分别展開對mountservice, vold, kernel這三者之間的通信流程的剖析。

{1}java framework層:采用 <code>1個主線程</code>(system_server)

+ <code>3個子線程</code>(voldconnector, mountservice, cryptdconnector);mountservice線程不斷向vold下發存儲相關的指令,比如mount,

mkdirs等操作;而線程voldconnector一直處于等待接收vold發送過來的應答事件;cryptdconnector通信原理和voldconnector大抵相同,有興趣地讀者可自行閱讀。

(2)native層:采用 <code>1個主線程</code>(/system/bin/vold)

+ <code>3個子線程</code>(vold) + <code>1子程序</code>(/system/bin/sdcard);vold程序中會通過<code>pthread_create</code>方式來生成3個vold子線程,其中兩個vold線程分别跟上層system_server程序中的線程voldconnector和cryptdconnector通信,第3個vold線程用于與kernel進行netlink方式通信。

本文更多的是以系統的角度來分析存儲系統,那麼對于app來說,那麼地方會直接用到的呢?其實用到的地方很多,例如儲存設備挂載成功會發送廣播讓app知曉目前存儲挂載情況;其次當app需要建立目錄時,比如<code>getexternalfilesdirs</code>,<code>getexternalcachedirs</code>等當目錄不存在時都需向存儲系統發出mkdirs的指令。另外,mountservice作為binder服務端,那自然而然會有binder用戶端,那就是<code>storagemanager</code>,這個比較簡單就不再細說了,歡迎大家與gityuan。

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

目前外置儲存設備比如sdcard或者otg的硬體品質參差不齊,且随使用時間碎片化程度也越來越嚴重,對于儲存設備挂載的過程中往往會有磁盤檢測fsck_msdos或者整理fstrim的動作,那麼勢必會阻塞多線程并發通路,影響系統穩定性,進而造成系統anr。

例如系統剛啟動過程中reset操作需要重新挂載外置儲存設備,而緊接着system_server主線程需要執行的volume user_started操作便會被阻塞,阻塞超過20s則系統會抛出service timeout的anr。