天天看点

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。