一、概述

本文讲述Android存储系统的架构与设计,基于Android 6.0的源码,涉及到最为核心的便是MountService和Vold这两个模块以及之间的交互。为了缩减篇幅,只展示部分核心代码。

MountService:Android Binder服务端,运行在system_server进程,用于跟Vold进行消息通信,比如MountServiceVold发送挂载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.fgandroid.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.fgandroid.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。

Android存储系统的架构与设计的更多相关文章

  1. [转]Android App整体架构设计的思考

    1. 架构设计的目的 对程序进行架构设计的原因,归根到底是为了提高生产力.通过设计使程序模块化,做到模块内部的高聚合和模块之间的低耦合.这样做的好处是使得程序在开发的过程中,开发人员只需要专注于一点, ...

  2. Android App的架构设计:从VM、MVC、MVP到MVVM

    随着Android应用开发规模的扩大,客户端业务逻辑也越来越复杂,已然不是简单的数据展示了.如同后端开发遇到瓶颈时采用的组件拆分思想,客户端也需要进行架构设计,拆分视图和数据,解除模块之间的耦合,提高 ...

  3. Android基础-系统架构分析,环境搭建,下载Android Studio,AndroidDevTools,Git使用教程,Github入门,界面设计介绍

    系统架构分析 Android体系结构 安卓结构有四大层,五个部分,Android分四层为: 应用层(Applications),应用框架层(Application Framework),系统运行层(L ...

  4. 安卓架构 视频 Android 插件化架构设计

    韩梦飞沙  韩亚飞  313134555@qq.com  yue31313  han_meng_fei_sha Android 插件化架构设计-Dream老师 自定义SDK =====

  5. Android系统的架构

    android的系统架构和其操作系统一样,采用了分层的架构.从架构图看,android分为四个层,从高层到低层分别是应用程序层.应用程序框架层.系统运行库层和linux核心层. 1.应用程序 Andr ...

  6. fir.im Weekly - iOS/Android 应用程序架构解析

    假如问你一个iOS or Android app的架构,你会从哪些方面来说呢? 本期 fir.im Weekly 收集了关于  iOS/Android 开发资源,也加入了一些关于 Web 前端方面的分 ...

  7. 基于Flume的美团日志收集系统(一)架构和设计

    美团的日志收集系统负责美团的所有业务日志的收集,并分别给Hadoop平台提供离线数据和Storm平台提供实时数据流.美团的日志收集系统基于Flume设计和搭建而成. <基于Flume的美团日志收 ...

  8. 基于Flume的美团日志收集系统(一)架构和设计【转】

    美团的日志收集系统负责美团的所有业务日志的收集,并分别给Hadoop平台提供离线数据和Storm平台提供实时数据流.美团的日志收集系统基于Flume设计和搭建而成. <基于Flume的美团日志收 ...

  9. Android群英传笔记——第三章:Android控件架构与自定义控件讲解

    Android群英传笔记--第三章:Android控件架构与自定义控件讲解 真的很久没有更新博客了,三四天了吧,搬家干嘛的,心累,事件又很紧,抽时间把第三章大致的看完了,当然,我还是有一点View的基 ...

随机推荐

  1. 代理delegate

    1>代理的用处是什么? 监听那些不能通过addTarget监听的事件 主要用开负责在两个对象之间,发生某些事件时,传递或发送消息 当我们需要 监听某些事件时,但苹果没有提供相关监听方法(addt ...

  2. ACM Ignatius and the Princess II

    Problem Description Now our hero finds the door to the BEelzebub feng5166. He opens the door and fin ...

  3. docker volume创建、备份、nfs存储

    docker存储volume #环境 centos7.4 , Docker version 17.12.0-ce docker volume创建.备份.nfs存储 #docker volume 数据存 ...

  4. Swift基础之实现选择图片时,出现类似于ActionSheet的样式

    之前看到过有APP在选择图片时,调用手机相册时,将手机相册做成了左右滑动选择的效果,这次展示的就是这种样式,用OC语言已经有人实现过类似的代码,在这里写的仅仅是效果展示的代码调用,具体代码,可以自己研 ...

  5. AsyncTask还要知道的一些知识

    在之前的博客中,对AsyncTask做过详细分析,而且也以小案例的形式,介绍如何基本的使用它.今天再来探讨它,更多的认识,尤其在面试中以下面方式回答,可能印象分更多一些. 面试题:讲一讲您对Async ...

  6. SSL协议相关证书文件

    密钥: 我理解是公钥+私钥的统称. 密钥对: 公钥(证书)和私钥成对存在.通信双方各持有自己的私钥和对方的公钥.自己的私钥需密切保护,而公钥是公开给对方的.在windows下,单独存在的公钥一般是后缀 ...

  7. Android开发之Intent.Action 各种Action的常见作用

    1 Intent.ACTION_MAIN String: android.intent.action.MAIN 标识Activity为一个程序的开始.比较常用. Input:nothing Outpu ...

  8. Android开源框架ViewPagerIndicator的基本使用

    转载本博客请注明出处:点击打开链接    http://blog.csdn.net/qq_32059827/article/details/52495647 很多新闻资讯类的app都有一些共性,那就是 ...

  9. EBS销售(OE)模块常用表

     select * from ra_customers 客户 select * from ra_addresses_all 地址 select * from ra_site_uses_all 用户 ...

  10. 关于Lt分发系统的时序图分析

    我们已经知道,系统共分为两个模块,mather与son 同时系统允许的操作也有三种,向mather提交war包,我某个服务器更新代码,为所有服务器更新代码 我们一个一个来看 先说,向mather提交w ...