日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 运维知识 > Android >内容正文

Android

Android存储系统之架构篇

發布時間:2025/3/15 Android 40 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Android存储系统之架构篇 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

一、概述

本文講述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的事件。

涉及主要的問題:

  • Framework層主要看MountService.java和NativeDaemonConnector.java;
  • Native層主要看NetlinkManager.cpp,?NetlinkHandler.cpp,?VolumeManager.cpp,CommandListener.cpp

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操作的handlermObbActionHandler = 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登記到sLocalServiceObjectsLocalServices.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();//默認為falseif (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.Stubimplements 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則會記錄到logif (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.runCommandVolumeBase.mountEmulatedVolume.doMount(內置)PublicVolume.doMount(外置)vfat::Checkvfat::Mountfork (/sdcard)

    2.1.7 小節

    簡稱表:

    • MS: MountService.java
    • NDC: NativeDaemonConnector.java
    • SL: SocketListener.cpp
    • FL: FrameworkListener.cpp
    • CL: CommandListener.cpp
    • SC: SocketClient.cpp

    MountService向vold發送消息后,便阻塞在圖中的MountService線程的NDC.execute()方法,那么何時才會退出呢?

    那就是在MonutService接收到消息,且消息響應嗎不屬于區間[600,700)則添加事件到ResponseQueue,從而喚醒阻塞的MountService繼續執行。接下來再來說說MS接收消息的流程.

    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);} }
    • 當執行成功,則發送響應碼為200的成功應答消息;
    • 當執行失敗,則發送響應碼為400的失敗應答消息。

    不同的響應碼(VoldResponseCode),代表著系統不同的處理結果,例如當操作執行成功,VoldConnector線程能收到類似`RCV <- {200 3 Command succeeded}的響應事件。 再比如[600,700)響應碼是由Vold進程”不請自來”的事件,主要是針對disk,volume的一系列操作,比如設備創建,狀態、路徑改變,以及文件類型、uid、標簽改變等事件都是底層直接觸發。更多響應碼含義見本文最后的附錄.

    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服務端發送的eventfinal 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);//創建單例對象VolumeManagerif (!(vm = VolumeManager::Instance())) {exit(1);}//創建單例對象NetlinkManagerif (!(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()) { //啟動vmexit(1);}process_config(vm); //解析config參數if (nm->start()) { //啟動nmexit(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。

    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(); //記錄當前進程的pidnladdr.nl_groups = 0xffffffff;//PF_NETLINK代表創建的是Netlink通信的socketif ((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 socketif (bind(mSock, (struct sockaddr *) &nladdr, sizeof(nladdr)) < 0) {goto out;}//創建NetlinkHandlermHandler = new NetlinkHandler(mSock);//啟動NetlinkHandlerif (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();//解析消息并封裝成NetlinkEventif (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服務端發送的eventfinal 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。

    四. 響應碼附錄

    關于響應嗎, Java層定義在MountService.java中的內部類VoldResponseCode,與Native層定義在文件/system/vold/ResponseCode.h中的響應碼是相互對應.

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

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

    100系列

    請求操作已初始化, 收到部分響應. 在處理下一個新命令前,期待收到另一個響應消息

    命令 響應碼
    VolumeListResult 110
    AsecListResult 111
    StorageUsersListResult 112
    CryptfsGetfieldResult 113

    200系列

    請求操作成功完成

    命令 響應碼
    ShareStatusResult 210
    AsecPathResult 211
    ShareEnabledResult 212
    PasswordTypeResult 213

    400系列

    命令已接收,但請求操作并沒有執行

    命令 響應碼
    OpFailedNoMedia 401
    OpFailedMediaBlank 402
    OpFailedMediaCorrupt 403
    OpFailedVolNotMounted 404
    OpFailedStorageBusy 405
    OpFailedStorageNotFound 406

    500系列

    命令沒有接收,請求操作也沒有執行

    命令 響應碼
    CommandSyntaxError 500
    CommandParameterError 501
    CommandNoPermission 502

    600系列

    不請自來的廣播, 是指由底層觸發的事件, 主要是針對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

    原文地址: http://gityuan.com/2016/07/23/android-io-arch/

    總結

    以上是生活随笔為你收集整理的Android存储系统之架构篇的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。