网易首页 > 网易号 > 正文 申请入驻

Android存储系统解析之架构篇(上)

0
分享至

基于Android 6.0的源码,剖析存储架构的设计

  引言:笔者最近遇到不少存储相关bug导致手机发生ANR,经过分析后,瓶颈主要在于主线程被blocked在vold进程.大抵地一看MountService/vold/kernel的整个交互过程涉及多线程通信,IO相关的操作都是在子线程完成,不应该阻塞system_server主线程啊?既然在子线程的工作都阻塞主线程,那该如何修复这个bug?为了回答上面这些疑问,深入研究Android的整个存储系统的架构设计。

  一、概述

  本文讲述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个过程:

  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.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。

  当执行成功,则发送响应码为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。

  最后,本文主要详细阐述MountService与vold之间的通信过程,关注《IT架构师联盟》,继续了解下一篇文章继续说说vold与kernel是如何通信,底层是如何主动触发事件通知MountService.

  Loki日志系统详解

  如何成为一名优秀的架构师?

  从Elasticsearch来看分布式系统架构设计

  架构师的核心工作内容

  架构师图谱(上)

  免责声明:

  本公众号部分分享的资料来自网络收集和整理,所有文字和图片版权归属于原作者所有,且仅代表作者个人观点,与本公众号无关,文章仅供读者学习交流使用,并请自行核实相关内容,如文章内容涉及侵权,请联系后台管理员删除。

特别声明:以上内容(如有图片或视频亦包括在内)为自媒体平台“网易号”用户上传并发布,本平台仅提供信息存储服务。

Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.

相关推荐
热点推荐
去年加价30万不愁卖,今年降价10万没人要,豪华车究竟遭遇了啥?

去年加价30万不愁卖,今年降价10万没人要,豪华车究竟遭遇了啥?

贾文彬的史书
2024-06-19 16:05:32
刚刚!国家队抄底了!

刚刚!国家队抄底了!

中国基金报
2024-06-19 14:33:13
8亿英镑!罗马老板收购埃弗顿即将官宣 英超老牌球队自求多福吧

8亿英镑!罗马老板收购埃弗顿即将官宣 英超老牌球队自求多福吧

雪狼侃体育
2024-06-19 11:16:17
美媒称中国已做好开战准备,国外网友发声:我们不该天天挑衅中国

美媒称中国已做好开战准备,国外网友发声:我们不该天天挑衅中国

战域笔墨
2024-06-20 01:38:27
惊天丑闻!马斯克与公司48人发生关系!细节无法直视;几名澳洲前员工还牵扯上了...

惊天丑闻!马斯克与公司48人发生关系!细节无法直视;几名澳洲前员工还牵扯上了...

澳洲红领巾
2024-06-19 13:40:18
阿斯:皇马拥有先进面罩技术&担忧姆巴佩,但他们现在无权介入

阿斯:皇马拥有先进面罩技术&担忧姆巴佩,但他们现在无权介入

直播吧
2024-06-19 13:49:13
五大联赛锋霸现身鲁能合练!下一轮踢亚泰将首秀,曾破巴萨大门

五大联赛锋霸现身鲁能合练!下一轮踢亚泰将首秀,曾破巴萨大门

罗掌柜体育
2024-06-19 16:13:07
官宣了!上海这一机构正式挂牌

官宣了!上海这一机构正式挂牌

上观新闻
2024-06-19 19:10:15
中国将发生巨大转变!福布斯:贝莱德向美联储发出“前所未有”的警告……

中国将发生巨大转变!福布斯:贝莱德向美联储发出“前所未有”的警告……

FX168链界观察
2024-06-19 14:36:20
黄海波高调庆锡婚,祝酒扬眉吐气,娇妻眼神拉丝,爱意藏不住了

黄海波高调庆锡婚,祝酒扬眉吐气,娇妻眼神拉丝,爱意藏不住了

电影烂番茄
2024-06-19 11:58:27
1968年,张大千的四姨太徐雯波,正恭敬地跪在地上拜师

1968年,张大千的四姨太徐雯波,正恭敬地跪在地上拜师

视点历史
2024-06-15 17:59:58
美记:勇士球员小佩顿还有最后一天决定是否执行球员选项

美记:勇士球员小佩顿还有最后一天决定是否执行球员选项

懂球帝
2024-06-19 20:34:09
上海这一夜,由王中磊牵头的明星聚会,将华谊的落魄展现淋漓尽致

上海这一夜,由王中磊牵头的明星聚会,将华谊的落魄展现淋漓尽致

小韩的幸福生活
2024-06-19 20:24:03
广西男孩变成植物人,医院却查不出病因,母亲煮鸡汤时发现蹊跷

广西男孩变成植物人,医院却查不出病因,母亲煮鸡汤时发现蹊跷

安妮Emotiong
2024-06-17 20:50:40
电动自行车飙到104公里/时,男子还叫嚣“快来抓我”!浦东警方:行拘!

电动自行车飙到104公里/时,男子还叫嚣“快来抓我”!浦东警方:行拘!

新民晚报
2024-06-19 12:56:22
普京载金正恩在平壤街头兜风

普京载金正恩在平壤街头兜风

观察者网
2024-06-19 20:06:22
湖南一男子坐车时意外发现,6年前弟弟杀人案的死者依然活着

湖南一男子坐车时意外发现,6年前弟弟杀人案的死者依然活着

一个人讲故事
2024-04-23 20:17:39
普京下重手:俄唯一女大将,全球军衔最高的女性舍夫佐娃被解职!

普京下重手:俄唯一女大将,全球军衔最高的女性舍夫佐娃被解职!

咖啡店的老板娘
2024-06-19 20:04:46
电力“最大贪官”落马:家中豪车百辆,现金有近10亿,结局如何?

电力“最大贪官”落马:家中豪车百辆,现金有近10亿,结局如何?

天闻地知
2024-06-18 14:03:29
西方紧盯普京亚洲行

西方紧盯普京亚洲行

环球时报国际
2024-06-19 08:06:08
2024-06-20 06:20:49
IT架构师联盟
IT架构师联盟
IT架构实战分享
689文章数 7654关注度
往期回顾 全部

科技要闻

618观察:谁为高强度的低价竞争买单?

头条要闻

欧洲杯:苏格兰1-1瑞士 沙奇里无解世界波

头条要闻

欧洲杯:苏格兰1-1瑞士 沙奇里无解世界波

体育要闻

欧洲杯最大的混子,非他莫属

娱乐要闻

黄一鸣“杀疯了” 直播间卖大葱养孩子

财经要闻

深化科创板改革 证监会发布八条措施

汽车要闻

双肾格栅变化大/内饰焕新 新一代宝马X3官图发布

态度原创

亲子
游戏
健康
房产
公开课

亲子要闻

宝宝看到对面小朋友们在打篮球也跟着模仿动作有模有样。

一个国产二次元角色,竟让老外集体亢奋沉沦,并语无伦次了起来?

晚餐不吃or吃七分饱,哪种更减肥?

房产要闻

17.9亿!终于,有民企在三亚大手笔拿地了!周边房价10万+!

公开课

近视只是视力差?小心并发症

无障碍浏览 进入关怀版