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部分组成。
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.fg
和android.io
。
Tips: 同一个模块可以运行在各个不同的进程/线程, 同一个进程可以运行不同模块的代码,所以从进程角度和模块角度划分看到的有所不同的.
为了阐述清楚存储系统的通信架构,主要分为以下4个过程:
MountService发送消息:MountService是如何从向vold守护进程通信;
MountService接收消息:MountService接收到vold发送过来的消息又是如何处理;
Kernel上报事件:当存储设备发生热插拔等事件,kernel是如何通知用户空间的vold;
不请自来的广播:对于事件往往都是MountService下发,然后再收到底层的回应,但对于有些广播却非如此,而是由底层直接触发,对于MountService来说却是“不请自来”的消息。
限于篇幅过长,本文先讲述前两个过程,下一篇文章再来说说后两个过程。
1.3 类关系图
上图中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层进程的创建情况.
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存储系统的架构与设计的更多相关文章
- [转]Android App整体架构设计的思考
1. 架构设计的目的 对程序进行架构设计的原因,归根到底是为了提高生产力.通过设计使程序模块化,做到模块内部的高聚合和模块之间的低耦合.这样做的好处是使得程序在开发的过程中,开发人员只需要专注于一点, ...
- Android App的架构设计:从VM、MVC、MVP到MVVM
随着Android应用开发规模的扩大,客户端业务逻辑也越来越复杂,已然不是简单的数据展示了.如同后端开发遇到瓶颈时采用的组件拆分思想,客户端也需要进行架构设计,拆分视图和数据,解除模块之间的耦合,提高 ...
- Android基础-系统架构分析,环境搭建,下载Android Studio,AndroidDevTools,Git使用教程,Github入门,界面设计介绍
系统架构分析 Android体系结构 安卓结构有四大层,五个部分,Android分四层为: 应用层(Applications),应用框架层(Application Framework),系统运行层(L ...
- 安卓架构 视频 Android 插件化架构设计
韩梦飞沙 韩亚飞 313134555@qq.com yue31313 han_meng_fei_sha Android 插件化架构设计-Dream老师 自定义SDK =====
- Android系统的架构
android的系统架构和其操作系统一样,采用了分层的架构.从架构图看,android分为四个层,从高层到低层分别是应用程序层.应用程序框架层.系统运行库层和linux核心层. 1.应用程序 Andr ...
- fir.im Weekly - iOS/Android 应用程序架构解析
假如问你一个iOS or Android app的架构,你会从哪些方面来说呢? 本期 fir.im Weekly 收集了关于 iOS/Android 开发资源,也加入了一些关于 Web 前端方面的分 ...
- 基于Flume的美团日志收集系统(一)架构和设计
美团的日志收集系统负责美团的所有业务日志的收集,并分别给Hadoop平台提供离线数据和Storm平台提供实时数据流.美团的日志收集系统基于Flume设计和搭建而成. <基于Flume的美团日志收 ...
- 基于Flume的美团日志收集系统(一)架构和设计【转】
美团的日志收集系统负责美团的所有业务日志的收集,并分别给Hadoop平台提供离线数据和Storm平台提供实时数据流.美团的日志收集系统基于Flume设计和搭建而成. <基于Flume的美团日志收 ...
- Android群英传笔记——第三章:Android控件架构与自定义控件讲解
Android群英传笔记--第三章:Android控件架构与自定义控件讲解 真的很久没有更新博客了,三四天了吧,搬家干嘛的,心累,事件又很紧,抽时间把第三章大致的看完了,当然,我还是有一点View的基 ...
随机推荐
- Node.js ZLIB
Zlib 稳定性: 3 - 文档 可以通过以下方式访问这个模块: var zlib = require('zlib'); 这个模块提供了对 Gzip/Gunzip, Deflate/Inflate, ...
- Git提交代码到远程服务器
1.下载Git 不用说了,这个是必须的,也是最简单的步骤,地址如下: http://git-scm.com/download 这里会提供三个版本的下载地址,读者可以自行查找. 2.创建代码库 远程的代 ...
- OpenCV+VS2013 属性表配置
简介 计算机视觉任务越来越多的依赖著名的开源计算机视觉库OpenCV.OpenCV 2.0 包含了一系列精心设计数据结构和经过优化的视觉算法,大家可以短时间内开发一个不错的视觉应用.OpenCV支持多 ...
- postgresql 登录查看表定义
su - postgres psql \connect database_name; \d table_name
- ToolBar控件详解
ToolBar控件详解 在Activity中添加ToolBar 1.添加库 dependencies { ... compile "com.android.support:appcompat ...
- Android设置竖屏
Android设置竖屏(禁止旋转屏) 清单文件,Activity下添加属性 android:screenOrientation="portrait" 如下 <?xml ver ...
- Docker简介/安装/使用
什么是Docker?docker是一个开源的应用容器引擎,系统级的轻量虚拟化技术.应用程序的自动化部署解决方案,能够迅速创建一个容器,并在容器上部署和运行应用程序,并通过配置文件可以轻松实现应用程序的 ...
- 浅谈机器人控制与仿真设计----RDS和ROS
机器人控制.仿真或实验,主要由三个部分组成,机器人.环境和算法. 当然各部分又包含很多子部分和功能,这里主要以仿真为主,为了使得仿真结果能够直接应用到实际机器人上,这里分别以RDS和ROS对比介绍.h ...
- 一个貌似比较吊的递归转换为loop--总算成功了.
class Stack(object): """ A class to hold arguements and state data. """ ...
- Leetcode解题-树(5.0.0)基础类
与第二章类似,LeetCode指定了TreeNode实现.为了方便后续习题的编写和测试,创建一个基础父类,包含TreeNode实现,以及create()和print()创建和打印树的方法.其中crea ...