Android存儲系統之架構篇
基于Android 6.0的源碼,剖析存儲架構的設計
一、概述
本文講述Android存儲系統的架構與設計,涉及到最為核心的便是MountService和Vold這兩個模塊以及之間的交互。上一篇文章 Android存儲系統之源碼篇 從源碼角度介紹相關模塊的創建與啟動過程,那么本文主要從全局角度把握和剖析Android的存儲系統。
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層的線程:
root@gityuan:/ # ps -t | grep 1212
system 1212 557 2334024 160340 SyS_epoll_ 7faedddbe4 S system_server
system 2662 1212 2334024 160340 SyS_epoll_ 7faedddbe4 S MountService
system 2663 1212 2334024 160340 unix_strea 7faedde73c S VoldConnector
system 2664 1212 2334024 160340 unix_strea 7faedde73c S CryptdConnector
...
MountService運行在system_server進程,這里查詢的便是system_server進程的所有子線程,system_server進程承載整個framework所有核心服務,子線程數有很多,這里只列舉與MountService模塊相關的子線程。
(2)再看看Native層的線程:
root@gityuan:/ # ps -t | grep " 387 "
USER PID PPID VSIZE RSS WCHAN PC NAME
root 387 1 13572 2912 hrtimer_na 7fa34755d4 S /system/bin/vold
root 397 387 13572 2912 poll_sched 7fa3474d1c S vold
root 399 387 13572 2912 poll_sched 7fa3474d1c S vold
root 400 387 13572 2912 poll_sched 7fa3474d1c S vold
media_rw 2702 387 7140 2036 inotify_re 7f84b1d6ac S /system/bin/sdcard
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: 同一個模塊可以運行在各個不同的進程/線程, 同一個進程可以運行不同模塊的代碼,所以從進程角度和模塊角度劃分看到的有所不同的.
1.3 類關系圖
上圖中4個藍色塊便是前面談到的核心模塊。
二、 通信架構
Android存儲系統中涉及各個進程間通信,這個架構采用的socket,并沒有采用Android binder IPC機制。這樣的架構代碼大量更少,整體架構邏輯也相對簡單,在介紹通信過程前,先來看看MountService對象的實例化過程,那么也就基本明白進程架構中system_sever進程為了MountService服務而單獨創建與共享使用到線程情況。
public MountService(Context context) {
sSelf = this;
mContext = context;
//FgThread線程名為“"android.fg",創建IMountServiceListener回調方法
mCallbacks = new Callbacks(FgThread.get().getLooper());
//獲取PKMS的Client端對象
mPms = (PackageManagerService) ServiceManager.getService("package");
//創建“MountService”線程
HandlerThread hthread = new HandlerThread(TAG);
hthread.start();
mHandler = new MountServiceHandler(hthread.getLooper());
//IoThread線程名為"android.io",創建OBB操作的handler
mObbActionHandler = new ObbActionHandler(IoThread.get().getLooper());
File dataDir = Environment.getDataDirectory();
File systemDir = new File(dataDir, "system");
mLastMaintenanceFile = new File(systemDir, LAST_FSTRIM_FILE);
//判斷/data/system/last-fstrim文件,不存在則創建,存在則更新最后修改時間
if (!mLastMaintenanceFile.exists()) {
(new FileOutputStream(mLastMaintenanceFile)).close();
...
} else {
mLastMaintenance = mLastMaintenanceFile.lastModified();
}
...
//將MountServiceInternalImpl登記到sLocalServiceObjects
LocalServices.addService(MountServiceInternal.class, mMountServiceInternal);
//創建用于VoldConnector的NDC對象
mConnector = new NativeDaemonConnector(this, "vold", MAX_CONTAINERS * 2, VOLD_TAG, 25,
null);
mConnector.setDebug(true);
//創建線程名為"VoldConnector"的線程,用于跟vold通信
Thread thread = new Thread(mConnector, VOLD_TAG);
thread.start();
//創建用于CryptdConnector工作的NDC對象
mCryptConnector = new NativeDaemonConnector(this, "cryptd",
MAX_CONTAINERS * 2, CRYPTD_TAG, 25, null);
mCryptConnector.setDebug(true);
//創建線程名為"CryptdConnector"的線程,用于加密
Thread crypt_thread = new Thread(mCryptConnector, CRYPTD_TAG);
crypt_thread.start();
//注冊監聽用戶添加、刪除的廣播
final IntentFilter userFilter = new IntentFilter();
userFilter.addAction(Intent.ACTION_USER_ADDED);
userFilter.addAction(Intent.ACTION_USER_REMOVED);
mContext.registerReceiver(mUserReceiver, userFilter, null, mHandler);
//內部私有volume的路徑為/data,該volume通過dumpsys mount是不會顯示的
addInternalVolume();
//默認為false
if (WATCHDOG_ENABLE) {
Watchdog.getInstance().addMonitor(this);
}
}
其主要功能依次是:
- 創建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層進程的創建情況.
接下來,我們分別從MountService向vold發送消息和接收消息兩個方面,以及Kernel向vold上報事件3個方面展開。
2.1 MountService發送消息
system_server進程與vold守護進程間采用socket進行通信,這個通信過程是由MountService線程向vold線程發送消息。這里以執行mount調用為例:
2.1.1 MS.mount
class MountService extends IMountService.Stub
implements INativeDaemonConnectorCallbacks, Watchdog.Monitor {
public void mount(String volId) {
...
try {
//【見小節2.1.2】
mConnector.execute("volume", "mount", vol.id, vol.mountFlags, vol.mountUserId);
} catch (NativeDaemonConnectorException e) {
throw e.rethrowAsParcelableException();
}
}
}
2.1.2 NDC.execute
[-> NativeDaemonConnector.java]
public NativeDaemonEvent execute(String cmd, Object… args) throws NativeDaemonConnectorException { return execute(DEFAULT_TIMEOUT, cmd, args); }
其中 DEFAULT_TIMEOUT=1min ,即命令執行超時時長為1分鐘。經過層層調用到executeForList()
public NativeDaemonEvent[] executeForList(long timeoutMs, String cmd, Object... args)
throws NativeDaemonConnectorException {
final long startTime = SystemClock.elapsedRealtime();
final ArrayList<NativeDaemonEvent> events = Lists.newArrayList();
final StringBuilder rawBuilder = new StringBuilder();
final StringBuilder logBuilder = new StringBuilder();
//mSequenceNumber初始化值為0,每執行一次該方法則進行加1操作
final int sequenceNumber = mSequenceNumber.incrementAndGet();
makeCommand(rawBuilder, logBuilder, sequenceNumber, cmd, args);
//例如:“3 volume reset”
final String rawCmd = rawBuilder.toString();
final String logCmd = logBuilder.toString();
log("SND -> {" + logCmd + "}");
synchronized (mDaemonLock) {
//將cmd寫入到socket的輸出流
mOutputStream.write(rawCmd.getBytes(StandardCharsets.UTF_8));
...
}
NativeDaemonEvent event = null;
do {
//阻塞等待,直到收到相應指令的響應碼
event = mResponseQueue.remove(sequenceNumber, timeoutMs, logCmd);
events.add(event);
//當收到的事件響應碼屬于[100,200)區間,則繼續等待后續事件上報
} while (event.isClassContinue());
final long endTime = SystemClock.elapsedRealtime();
//對于執行時間超過500ms則會記錄到log
if (endTime - startTime > WARN_EXECUTE_DELAY_MS) {
loge("NDC Command {" + logCmd + "} took too long (" + (endTime - startTime) + "ms)");
}
...
return events.toArray(new NativeDaemonEvent[events.size()]);
}
- 首先,將帶執行的命令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()方法
[-> FrameworkListener.cpp]
bool FrameworkListener::onDataAvailable(SocketClient *c) {
char buffer[CMD_BUF_SIZE];
int len;
// 多次嘗試從socket管道讀取數據
len = TEMP_FAILURE_RETRY(read(c->getSocket(), buffer, sizeof(buffer)));
...
for (i = 0; i < len; i++) {
if (buffer[i] == '\0') {
//分發該命令【見小節2.1.4】
dispatchCommand(c, buffer + offset);
...
}
}
return true;
}
2.1.4 FL.dispatchCommand
[-> FrameworkListener.cpp]
void FrameworkListener::dispatchCommand(SocketClient *cli, char *data) {
...
for (i = mCommands->begin(); i != mCommands->end(); ++i) {
FrameworkCommand *c = *i;
if (!strcmp(argv[0], c->getCommand())) {
//找到相應的類處理該命令
if (c->runCommand(cli, argc, argv)) {
SLOGW("Handler '%s' error (%s)", c->getCommand(), strerror(errno));
}
goto out;
}
}
...
}
這是用于分發從MountService發送過來的命令,針對不同的命令調用不同的類,總共有以下6類:
- DumpCmd
- VolumeCmd
- AsecCmd
- ObbCmd
- StorageCmd
- FstrimCmd
另外,在處理過程中遇到下面情況,則會直接發送響應嗎500的應答消息給MountService
- 當無法找到匹配的類,則會直接向MountService返回響應碼500,內容”Command not recognized”的應答消息;
- 命令參數過長導致socket管道溢出,則會發送響應碼500,內容”Command too long”的應答消息。
2.1.5 CL.runCommand
例如前面發送過來的是 volume mount ,則會調用到CommandListener的內部類VolumeCmd的runCommand來處理該消息,并進入mount分支。
int CommandListener::VolumeCmd::runCommand(SocketClient *cli,
int argc, char **argv) {
VolumeManager *vm = VolumeManager::Instance();
std::lock_guard<std::mutex> lock(vm->getLock());
...
std::string cmd(argv[1]);
if (cmd == "reset") {
return sendGenericOkFail(cli, vm->reset());
}else if (cmd == "mount" && argc > 2) {
// mount [volId] [flags] [user]
std::string id(argv[2]);
auto vol = vm->findVolume(id);
if (vol == nullptr) {
return cli->sendMsg(ResponseCode::CommandSyntaxError, "Unknown volume", false);
}
int mountFlags = (argc > 3) ? atoi(argv[3]) : 0;
userid_t mountUserId = (argc > 4) ? atoi(argv[4]) : -1;
vol->setMountFlags(mountFlags);
vol->setMountUserId(mountUserId);
//真正的掛載操作【見2.1.6】
int res = vol->mount();
if (mountFlags & android::vold::VolumeBase::MountFlags::kPrimary) {
vm->setPrimary(vol);
}
//發送應答消息給MountService【見2.2.1】
return sendGenericOkFail(cli, res);
}
// 省略其他的else if
...
}
2.1.6 mount
這里便進入了VolumeManager模塊,執行volume設備真正的掛載操作。對于掛載內置存儲和外置存儲流程是有所不同的,這里就不再細說,簡單的調用流程:
VolumeCmd.runCommand
VolumeBase.mount
EmulatedVolume.doMount(內置)
PublicVolume.doMount(外置)
vfat::Check
vfat::Mount
fork (/sdcard)
2.1.7 小節
MountService向vold發送消息后,便阻塞在圖中的MountService線程的NDC.execute()方法,那么何時才會退出呢?圖的后半段MonutService接收消息的過程會有答案,那便是在收到消息,并且消息的響應嗎不屬于區間[600,700)則添加事件到ResponseQueue,從而喚醒阻塞的MountService繼續執行。關于上圖的后半段介紹的便是MountService接收消息的流程。
2.2 MountService接收消息
當Vold在處理完完MountService發送過來的消息后,會通過sendGenericOkFail發送應答消息給上層的MountService。
2.2.1 響應碼
[-> CommandListener.cpp]
int CommandListener::sendGenericOkFail(SocketClient *cli, int cond) {
if (!cond) {
//【見小節2.2.2】
return cli->sendMsg(ResponseCode::CommandOkay, "Command succeeded", false);
} else {
return cli->sendMsg(ResponseCode::OperationFailed, "Command failed", false);
}
}
- 當執行成功,則發送響應碼為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、標簽改變等事件都是底層直接觸發。
命令 | 響應嗎 |
---|---|
DISK_CREATED | 640 |
DISK_SIZE_CHANGED | 641 |
DISK_LABEL_CHANGED | 642 |
DISK_SCANNED | 643 |
DISK_SYS_PATH_CHANGED | 644 |
DISK_DESTROYED | 649 |
VOLUME_CREATED | 650 |
VOLUME_STATE_CHANGED | 651 |
VOLUME_FS_TYPE_CHANGED | 652 |
VOLUME_FS_UUID_CHANGED | 653 |
VOLUME_FS_LABEL_CHANGED | 654 |
VOLUME_PATH_CHANGED | 655 |
VOLUME_INTERNAL_PATH_CHANGED | 656 |
VOLUME_DESTROYED | 659 |
MOVE_STATUS | 660 |
BENCHMARK_RESULT | 661 |
TRIM_RESULT | 662 |
介紹完響應碼,接著繼續來說說發送應答消息的過程:
2.2.2 SC.sendMsg
[-> SocketClient.cpp]
int SocketClient::sendMsg(int code, const char *msg, bool addErrno) {
return sendMsg(code, msg, addErrno, mUseCmdNum);
}
sendMsg經過層層調用,進入sendDataLockedv方法
int SocketClient::sendDataLockedv(struct iovec *iov, int iovcnt) {
...
struct sigaction new_action, old_action;
memset(&new_action, 0, sizeof(new_action));
new_action.sa_handler = SIG_IGN;
sigaction(SIGPIPE, &new_action, &old_action);
//將應答消息寫入socket管道
for (;;) {
ssize_t rc = TEMP_FAILURE_RETRY(
writev(mSocket, iov + current, iovcnt - current));
if (rc > 0) {
size_t written = rc;
while ((current < iovcnt) && (written >= iov[current].iov_len)) {
written -= iov[current].iov_len;
current++;
}
if (current == iovcnt) {
break;
}
iov[current].iov_base = (char *)iov[current].iov_base + written;
iov[current].iov_len -= written;
continue;
}
...
break;
}
sigaction(SIGPIPE, &old_action, &new_action);
...
return ret;
}
2.2.3 NDC.listenToSocket
應答消息寫入socket管道后,在MountService的另個線程”VoldConnector”中建立了名為 vold 的socket的客戶端,通過循環方式不斷監聽Vold服務端發送過來的消息。
[-> NativeDaemonConnector.java]
private void listenToSocket() throws IOException {
LocalSocket socket = null;
try {
socket = new LocalSocket();
LocalSocketAddress address = determineSocketAddress();
//建立與"/dev/socket/vold"的socket連接
socket.connect(address);
InputStream inputStream = socket.getInputStream();
synchronized (mDaemonLock) {
mOutputStream = socket.getOutputStream();
}
...
while (true) {
int count = inputStream.read(buffer, start, BUFFER_SIZE - start);
...
for (int i = 0; i < count; i++) {
if (buffer[i] == 0) {
final String rawEvent = new String(
buffer, start, i - start, StandardCharsets.UTF_8);
//解析socket服務端發送的event
final NativeDaemonEvent event = NativeDaemonEvent.parseRawEvent(
rawEvent);
log("RCV <- {" + event + "}");
if (event.isClassUnsolicited()) {
...
//當響應碼區間為[600,700),則發送消息交由mCallbackHandler處理
if (mCallbackHandler.sendMessage(mCallbackHandler.obtainMessage(
event.getCode(), event.getRawEvent()))) {
releaseWl = false;
}
} else {
//對于其他響應碼則添加到mResponseQueue隊列
mResponseQueue.add(event.getCmdNumber(), event);
}
}
}
}
} finally {
//收尾清理類工作
...
}
}
監聽也是阻塞的過程,當收到不同的消息相應碼,采用不同的行為:
- 當響應嗎不屬于區間[600,700):則將該事件添加到mResponseQueue,并且觸發響應事件所對應的請求事件不再阻塞到ResponseQueue.poll,那么線程繼續往下執行,即前面小節[2.1.2] NDC.execute的過程。
- 當響應碼區間為[600,700):則發送消息交由mCallbackHandler處理,向線程 android.fg 發送Handler消息,該線程收到后回調NativeDaemonConnector的 handleMessage 來處理。
2.2.4 小節
2.3 Kernel上報事件
介紹完MonutService與vold之間的交互通信,那么再來看看Kernel是如何上報事件到vold的流程。再介紹這個之前,先簡單看看vold啟動時都創建了哪些對象。
[-> system/vold/Main.cpp]
int main(int argc, char** argv) {
setenv("ANDROID_LOG_TAGS", "*:v", 1);
android::base::InitLogging(argv, android::base::LogdLogger(android::base::SYSTEM));
VolumeManager *vm;
CommandListener *cl;
CryptCommandListener *ccl;
NetlinkManager *nm;
mkdir("/dev/block/vold", 0755);
//用于cryptfs檢查,并mount加密的文件系統
klog_set_level(6);
//創建單例對象VolumeManager
if (!(vm = VolumeManager::Instance())) {
exit(1);
}
//創建單例對象NetlinkManager
if (!(nm = NetlinkManager::Instance())) {
exit(1);
}
if (property_get_bool("vold.debug", false)) {
vm->setDebug(true);
}
// 創建CommandListener對象
cl = new CommandListener();
// 創建CryptCommandListener對象
ccl = new CryptCommandListener();
//給vm設置socket監聽對象
vm->setBroadcaster((SocketListener *) cl);
//給nm設置socket監聽對象
nm->setBroadcaster((SocketListener *) cl);
if (vm->start()) { //啟動vm
exit(1);
}
process_config(vm); //解析config參數
if (nm->start()) { //啟動nm
exit(1);
}
coldboot("/sys/block");
//啟動響應命令的監聽器
if (cl->startListener()) {
exit(1);
}
if (ccl->startListener()) {
exit(1);
}
//Vold成為監聽線程
while(1) {
sleep(1000);
}
exit(0);
}
該方法的主要功能是創建并啟動: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.java]
int NetlinkManager::start() {
struct sockaddr_nl nladdr;
int sz = 64 * 1024;
int on = 1;
memset(&nladdr, 0, sizeof(nladdr));
nladdr.nl_family = AF_NETLINK;
nladdr.nl_pid = getpid(); //記錄當前進程的pid
nladdr.nl_groups = 0xffffffff;
//PF_NETLINK代表創建的是Netlink通信的socket
if ((mSock = socket(PF_NETLINK, SOCK_DGRAM | SOCK_CLOEXEC,
NETLINK_KOBJECT_UEVENT)) < 0) {
return -1;
}
//設置uevent的SO_RCVBUFFORCE選項
if (setsockopt(mSock, SOL_SOCKET, SO_RCVBUFFORCE, &sz, sizeof(sz)) < 0) {
goto out;
}
//設置uevent的SO_PASSCRED選項
if (setsockopt(mSock, SOL_SOCKET, SO_PASSCRED, &on, sizeof(on)) < 0) {
goto out;
}
//綁定uevent socket
if (bind(mSock, (struct sockaddr *) &nladdr, sizeof(nladdr)) < 0) {
goto out;
}
//創建NetlinkHandler
mHandler = new NetlinkHandler(mSock);
//啟動NetlinkHandler
if (mHandler->start()) {
goto out;
}
return 0;
out:
close(mSock);
return -1;
}
NetlinkManager啟動的過程中,會創建并啟動NetlinkHandler,在該過程會通過 pthrea_create 創建子線程專門用于接收Kernel發送過程的Uevent事件,當收到數據時會調用NetlinkListener的onDataAvailable方法。
2.3.3 NL.onDataAvailable
[-> NetlinkListener.cpp]
bool NetlinkListener::onDataAvailable(SocketClient *cli)
{
int socket = cli->getSocket();
...
//多次嘗試獲取socket數據
count = TEMP_FAILURE_RETRY(uevent_kernel_recv(socket,
mBuffer, sizeof(mBuffer), require_group, &uid));
...
NetlinkEvent *evt = new NetlinkEvent();
//解析消息并封裝成NetlinkEvent
if (evt->decode(mBuffer, count, mFormat)) {
//事件處理【見小節2.3.4】
onEvent(evt);
} else if (mFormat != NETLINK_FORMAT_BINARY) {
...
}
delete evt;
return true;
}
2.3.4 NH.onEvent
[-> NetlinkHandler.cpp]
void NetlinkHandler::onEvent(NetlinkEvent *evt) {
VolumeManager *vm = VolumeManager::Instance();
const char *subsys = evt->getSubsystem();
if (!strcmp(subsys, "block")) {
//對于塊設備的處理過程
vm->handleBlockEvent(evt);
}
}
驅動設備分為字符設備、塊設備、網絡設備。對于字符設備按照字符流的方式被有序訪問,字符設備也稱為裸設備,可以直接讀取物理磁盤,不經過系統緩存,例如鍵盤直接產生中斷。而塊設備是指系統中能夠隨機(不需要按順序)訪問固定大小數據片(chunks)的設備,例如硬盤;塊設備則是通過系統緩存進行讀取。
2.3.5 VM.handleBlockEvent
[-> VolumeManager.cpp]
void VolumeManager::handleBlockEvent(NetlinkEvent *evt) {
std::lock_guard<std::mutex> lock(mLock);
std::string eventPath(evt->findParam("DEVPATH")?evt->findParam("DEVPATH"):"");
std::string devType(evt->findParam("DEVTYPE")?evt->findParam("DEVTYPE"):"");
if (devType != "disk") return;
int major = atoi(evt->findParam("MAJOR"));
int minor = atoi(evt->findParam("MINOR"));
dev_t device = makedev(major, minor);
switch (evt->getAction()) {
case NetlinkEvent::Action::kAdd: {
for (auto source : mDiskSources) {
if (source->matches(eventPath)) {
int flags = source->getFlags();
if (major == kMajorBlockMmc) {
flags |= android::vold::Disk::Flags::kSd;
} else {
flags |= android::vold::Disk::Flags::kUsb;
}
auto disk = new android::vold::Disk(eventPath, device,
source->getNickname(), flags);
//創建
disk->create();
mDisks.push_back(std::shared_ptr<android::vold::Disk>(disk));
break;
}
}
break;
}
case NetlinkEvent::Action::kChange: {
...
break;
}
case NetlinkEvent::Action::kRemove: {
...
break;
}
...
}
}
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
[-> NativeDaemonConnector.java]
private void listenToSocket() throws IOException {
LocalSocket socket = null;
try {
socket = new LocalSocket();
LocalSocketAddress address = determineSocketAddress();
//建立與"/dev/socket/vold"的socket連接
socket.connect(address);
InputStream inputStream = socket.getInputStream();
synchronized (mDaemonLock) {
mOutputStream = socket.getOutputStream();
}
...
while (true) {
int count = inputStream.read(buffer, start, BUFFER_SIZE - start);
...
for (int i = 0; i < count; i++) {
if (buffer[i] == 0) {
final String rawEvent = new String(
buffer, start, i - start, StandardCharsets.UTF_8);
//解析socket服務端發送的event
final NativeDaemonEvent event = NativeDaemonEvent.parseRawEvent(
rawEvent);
log("RCV <- {" + event + "}");
if (event.isClassUnsolicited()) {
...
//當響應碼區間為[600,700),則發送消息交由mCallbackHandler處理【2.4.2】
if (mCallbackHandler.sendMessage(mCallbackHandler.obtainMessage(
event.getCode(), event.getRawEvent()))) {
releaseWl = false;
}
} else {
//對于其他響應碼則添加到mResponseQueue隊列
mResponseQueue.add(event.getCmdNumber(), event);
}
}
}
}
} finally {
//收尾清理類工作
...
}
}
通過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
[-> NativeDaemonConnector.java]
public boolean handleMessage(Message msg) {
String event = (String) msg.obj;
...
mCallbacks.onEvent(msg.what, event, NativeDaemonEvent.unescapeArgs(event))
log(String.format("Unhandled event '%s'", event));
...
return true;
}
此處的mCallbacks,是由實例化NativeDaemonConnector對象時傳遞進來的,在這里是指MountService。轉了一圈,又回到MountService。
2.4.3 MS.onEvent
[-> MountService.java]
public boolean onEvent(int code, String raw, String[] cooked) {
synchronized (mLock) {
return onEventLocked(code, raw, cooked);
}
}
onEventLocked增加同步鎖,用于多線程并發訪問的控制。根據vold發送過來的不同響應碼將采取不同的處理流程。
2.4.4 MS.onEventLocked
這里以收到vold發送過來的 RCV <- {650 public ...} 為例,即掛載外置sdcard/otg外置存儲的流程:
[-> MountService.java]
private boolean onEventLocked(int code, String raw, String[] cooked) {
switch (code) {
case VoldResponseCode.VOLUME_CREATED: {
final String id = cooked[1];
final int type = Integer.parseInt(cooked[2]);
final String diskId = TextUtils.nullIfEmpty(cooked[3]);
final String partGuid = TextUtils.nullIfEmpty(cooked[4]);
final DiskInfo disk = mDisks.get(diskId);
final VolumeInfo vol = new VolumeInfo(id, type, disk, partGuid);
mVolumes.put(id, vol);
//【見小節2.4.5】
onVolumeCreatedLocked(vol);
break;
}
...
}
return true;
}
2.4.5 MS.onVolumeCreatedLocked
[-> MountService.java]
private void onVolumeCreatedLocked(VolumeInfo vol) {
if (vol.type == VolumeInfo.TYPE_EMULATED) {
...
} else if (vol.type == VolumeInfo.TYPE_PUBLIC) {
if (Objects.equals(StorageManager.UUID_PRIMARY_PHYSICAL, mPrimaryStorageUuid)
&& vol.disk.isDefaultPrimary()) {
vol.mountFlags |= VolumeInfo.MOUNT_FLAG_PRIMARY;
vol.mountFlags |= VolumeInfo.MOUNT_FLAG_VISIBLE;
}
if (vol.disk.isAdoptable()) {
vol.mountFlags |= VolumeInfo.MOUNT_FLAG_VISIBLE;
}
vol.mountUserId = UserHandle.USER_OWNER;
//【見小節2.4.6】
mHandler.obtainMessage(H_VOLUME_MOUNT, vol).sendToTarget();
}
}
這里又遇到一個Handler類型的對象 mHandler ,再來看看其定義:
private static final String TAG = "MountService";
HandlerThread hthread = new HandlerThread(TAG);
hthread.start();
mHandler = new MountServiceHandler(hthread.getLooper());
該Handler用到Looper便是線程 MountService 中的Looper,回調方法handleMessage位于MountServiceHandler類:
2.4.6 MSH.handleMessage
[-> MountService]
class MountServiceHandler extends Handler {
public void handleMessage(Message msg) {
switch (msg.what) {
case H_VOLUME_MOUNT: {
final VolumeInfo vol = (VolumeInfo) msg.obj;
try {
//發送mount操作
mConnector.execute("volume", "mount", vol.id, vol.mountFlags,
vol.mountUserId);
} catch (NativeDaemonConnectorException ignored) {
}
break;
}
...
}
}
}
當收到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 ,這個比較簡單就不再細說了。
3.2 架構的思考
以Google原生的Android存儲系統的架構設計主要采用Socket阻塞式通信方式,雖然vold的native層面有多個子線程干活,但各司其職,真正處理上層發送過來的命令,仍然是單通道的模式。
目前外置存儲設備比如sdcard或者otg的硬件質量參差不齊,且隨使用時間碎片化程度也越來越嚴重,對于存儲設備掛載的過程中往往會有磁盤檢測fsck_msdos或者整理fstrim的動作,那么勢必會阻塞多線程并發訪問,影響系統穩定性,從而造成系統ANR。
例如系統剛啟動過程中reset操作需要重新掛載外置存儲設備,而緊接著system_server主線程需要執行的volume user_started操作便會被阻塞,阻塞超過20s則系統會拋出Service Timeout的ANR。
系列文章
Android存儲系統之源碼篇
Android存儲系統之架構篇
來自:http://gityuan.com/2016/07/23/android-io-arch/