这篇文章主要是分析Android存储向关联的一些模块,这个分析主要从大的工作流程和代码模块分析,没有对于没有分析到地方后续遇到后在详细分析。主要从以下几个模块分析
系统分区的挂载、外部分区挂载、Vold守候进程分、MountService的业务分析、Sdcard的详细分析、MTP模式分析和设备存储空间的监控机制。
-
系统分区挂载
Android是基于linux内核的系统,遵从linux的文件系统的挂载方式。在Android中在init进程负责挂载常用的system,data,cache等分区。Init进程通过读取init.rc中的指令完成系统系统挂载:
mount_all ./fstab.xxxxx
看一下啊fstab文件是怎样的格式:
<src> :挂载分区的地址或者是sys文件系统的设备的路径
<mnt_point> :在根文件系统中的挂载点
<type> :文件类型
<mnt_flag>:挂载的参数
<fs_mgr_flags>:文件系统管理的标识,encryptable表示该分区可以加密,voldmanaged表示可以被vold进程管理。
Init进程是如何通过这条指令是如何完成system分区挂载呢?
Init具体的解析方式,这儿不做详细的解释。Init进程会把mount_all 关键字解析成
转会为do_mount_all函数,在该函数中调用fs_mgr_mount_all() 实现对
Fstab.xxxx 文件的system、data、cache分区的挂载。
在do_mount_all函数中会fork一个子进程,在子进程中读取/fstab.xxx文件并保存在fstab结构体中,供在fs_mgr_mount_all函数中使用。
在mount分区时会判断是含有voldmanaged 标示位,如果含有voldmanged标示则跳过mount。如果分区含有MF_CRYPT标示则mount则tmpfs文件并返回encrypted的值为1,在父进程中设置 property_set("ro.crypto.state", "encrypted")供后面系统解密分区会用到。
到此系统分区的挂载已经ok。
-
外置分区挂载
上面了解了系统分区的挂载,下面分析一下外置sdcard或者usb等外部存储设备的挂载过程。整个挂载过程在Vold和MountService(SystemServer)进程中完成。sdcard插拔会被kernel监听到并发送uevent到用户空间。此时vold守候进程会收到从kernel上报uvent事件,检查和处理后并上报到MountService中,MountSerivce和上层其他模块交互确认然后通过socket发送指令给vold完成具体的磁盘Mount和Unmount操作整体框架如下图:
当然sdcard的事件也不是总是由底层上报,App应用上层可以通过format和unmount sdcard操作通知MountService进而通知vold完成对应的操作。
-
Vold守候进程分析
-
Uevent测试程序
外置sdcard或usb插拔kerenl都会发出uevent通知到用户空间,关于kernel中如何上报检查插拔事件和发送uevent的这儿不做介绍。Vold就是接收uevent后来判断设备的插拔动作,如果能够看到kernel的上报uevent的值对我们后面分析vold代码很有帮忙,因此Android提供另一个Uevents的测试程序,专门接收由kernel发出的uevent并打印出uevent的内容。
代码目录:system/extras/tests/uevents
可以很清楚的看到kernel上报的event内。这位后面分析vold挂载sdcard过程挺有帮助。
Vold - Volume Daemon存储类的守护进程,作为Android的一个本地服务,负责处理诸如SD、USB等存储类设备的插拔等事件。Vold服务由volumeManager统一管控,它将具体任务分别分派给netlinkManager, commandListener, directVolume, Volume去完成。
-
Vold的启动分析
vold是native的守候进程是在init进程通过解析init.rc 启动的。
service vold /system/bin/vold
class core
socket vold stream 0660 root mount
启动vold后并同时创建了socket的用于和其他进程通信。
具体的启动过程可以参看system/vold/main.cpp文件详细的了解启动过程。
启动主要做了以下几点工作:
创建VolumeManager和NetLinkeManager实例。
创建CommandListenser并注册到vm和nm中并调用nm.start()启动netlink的监听模式。
解析fstab.xxx文件并根据voldmanaged标识创建DirectVolume并添加到vm中
开启startListener()开始监听来自Framework的cmd命令。
进入while死循环变成守候进程,一直存在系统中。
-
vold的工作流程
vold启动后就进入的监听模式,同时开了2个线程监听来自kernel的uevent和来自frmaework的指令操作。下面这幅图展示,vold监听uevent和接收来自MountService中的各种指令过程。
在讲上面的图之前先介绍几个关键类以及作用,这样在看上面的图就很容易理解。
NetLinkManager:启动和关闭Netlink的监听模式
NetLinkHandler:uevent的处理类
VolumeManager: 管理volume的mount/unmount/shared/mountasec/list 等操作
Volume: sdcard等外部磁盘卷的抽象,抽象类
DirectVolume: volume的子类代表外置的sdcard
CommandListener: 接收来自framework的指令的监听
SocketListener: socket通信的监听的基础类
NetLinkListener: 接收uevent的监听
NativeDaemonConnector: 和vold的socket通信的工具类
MountService: volume 在framework层的管理以及volume的控制中心
这几个的类之间的关系如下图
看了这几个类的关系后先分析一下socketListener的监听框架,是如何监听事件分发这些事件的。
1.uevent的事件分发:在NetLinkManager通过创建NetlinkHandler对象并通过start方法调用sockeLisetener的startListener方法创建一个uevent线程并执行runListener方法,在runLisenter方法中接收到client发送过来的socket并调用抽象onDateAvailable方法,具体实现在NetLinkLisenter的同名方法中,在该方法中调用NetlInkEvent类的decode方法解析uevent最后调NetLinkHandler的onEvent处理uevent的插拔事件。
2.Framework的命令分发:在Vold的启动代码就执行cl->startLisenter方法进入监听状态。当framework有socket来时,也是先调用FrameworkLisenter.onDataAvaliable方法,在次方法中通过dispatchCommand方法分发cmd命令,在注册的volumeCmd中找到匹配的XXXCmd类调用其runCommand执行对应的操作。
这儿简单的介绍一下几个常用Cmd
VolumeCmd:用于磁盘卷的管理,支持list,mount,unmount,format,shared,unshared 操作。
StorageCmd:用于查询那些进程正在使用指定的存储设备。
AsecCmd:用于apk应用安装在sdcard上,实际通过loop设备实现。
ObbCmd:不透明二进制文件的挂载命令,应用大数据块的挂载
CryptoCmd:磁盘加密的相关命令
到此vold的框架已经讲解完了,具体的命令的实现细节需要看代码仔细分析。
-
MountService分析:
mountService作为系统里面服务为提供volume、obb和Asec的挂载和监听volume的状态并及时的向其他模块更新状态。
-
MountService总体框架
下面的图简单的说明MountService和其他系统模块的关系
-
MountService的启动
MountService是在SystemServie中创建的,在MountService的构造方法主要做了以下工作。
-
readStorageListLocked()读取volume配置文件,并加载mVolume,mVolumesByPath和mVolumestate.供后面查询用。
-
创建MountServiceHandler处理线程。
-
注册接收用户和usb状态更新广播。
-
将obbActionHandler添加MountSericeHandler线程中,并初始化pm和申请wakelock。
-
为NativeConnectorDaemon创建一个线程接收vold发过来msg。
在readStorageListLocked中会读取storage_list.xml文件并将里面的storage添加mVolumes数组中。
下面看一下storage_list.xml 格式:
<StorageList xmlns:android="http://schemas.android.com/apk/res/android">
<!-- removable is not set in nosdcard product -->
<storage android:mountPoint="/storage/sdcard"
android:storageDescription="@string/storage_sd_card"
android:removable="true"
android:primary="true" />
</StorageList>
这儿的storage_list.xml文件的mountPoint的/storage/后面的字符和fstab.xxx 的voldmanaged的label要对应,虚拟sdcard的配置除外。
在systemService中会调用MountSerivice.SystemReady()方法去挂载mVolumes的里面的分区,并更新volume的状态。
分析这么上面的内容,我们下面通过一个sdcard的挂载过程来分析一下整体流程。
上面的处理过程分为5各阶段:
1.NetLinkManager开启监听阶段。
2.uevent的解析阶段,为了测试方面大家可以用uevents工具查看kernel上报的uevent内容
3.通知MountSerice挂载设备。
4.在VolumeManager中的具体挂载过程。
5.挂载完成通知MountService,在mountSerivce中更新mVolume的状态并通知其他模块。
随便也看一下sdcard的unmount的过程如下图:
在卸载sdcard时会调到PackageManagerService的updateExternalMediaStatus函数去更新sdcard的状态,检查是挂载还是卸载。接着搜集安装在sdcard的应用,搜集完成后则删除这些应用并更新包信息,具体代码参考PKMS的unloadMediaPackages的代码。 MountService除了对sdcard管理外,还会挂载Obb和Asec等分区。上面了解由于uevent发起的处理流程,下面通过mountObb来了解一下由app发起的处理流程。
-
SDcard分析
在KitKat之前的Android版本会给应用程序单独分出一块外部存储空间(external storage),这块存储空间可能在sdcard(可插拔的外置sdcaard)上,也可能在仅仅是在设备内部的闪存上,我们要获得WRITE_EXTERNAL_STORAGE权限在能对这块空间进行访问,如果只是读取内容则不需要权限。在4.4 KitKat及之后的版本中,Google做了两个变化:1、进行读取时需要READ_EXTERNAL_STORAGE权限;2、访问应用所属的目录下(如:android/data/[package name])存储的数据是不需要任何权限的。
KitKat中,外部存储(external storage)被分割成了多个部分:一个“primary”部分,一个或多个“secondary”部分。在Kitkat之前的API 可以用来操作 primary external storage,secondary external storage 是对write权限做了稍微修改,与上边所述一样,在应用所所属的目录(如:android/data/[package name])下,对文件是有所有操作权限的,在应用所能管理到目录之外,该应用则不具有写的权限,不能进行任何写的操作。Google虽然没有要求各厂商在Sdcard的操作上添加额外权限,但是它却强制要求制造商对secondary external storage做了权限限制
-
常见的sdcard实现方式
Sdcard 是Android系统中主要的外部存储,主要有下面几种常见方式
- 通过fuse方式把/data/media目录虚拟成sdcard
- 在flash上划分一个分区并通过fuse方式变成sdcard
- 物理的sdcard设备
在google的nexus 5上就是通过fuse机制将/data/media目录虚拟成sdcard。下面简单的介绍一下主要步骤
- 在init.xxx.rc文件中创建挂载目录并并导入环境变量供系统使用
mkdir /mnt/shell/emulated 0700shell shell
mkdir /storage/emulated 0555root root
export EXTERNAL_STORAGE /storage/emulated/legacy
export EMULATED_STORAGE_SOURCE /mnt/shell/emulated 外部存储的原始地方
export EMULATED_STORAGE_TARGET /storage/emulated 对于app可以看到的目录
EMULATED_STORAGE_TARGET 就是对于Android的上层看到的目录,即通过getExternalStorageDirectory()获取到的目录。
- 在init.rc 中启动sdcard服务将media目录虚拟成sdcard
用sdcard将/data/media 通过sdcard 挂载在/mnt/shell/emulated上
service sdcard /system/bin/sdcard -u 1023 -g 1023 -l /data/media /mnt/shell/emulated
class late_start
- 为sdcard配置storage_list.xml 文件
storage_list.xml
<storage
android:storageDescription="@string/storage_internal" #在pc上看到的描述
android:emulated="true" #是虚拟设备
android:mtpReserve="100" #mtp模式下要保留100M的空间
/>
下面也分析一下同时存在 单独分区实现sdcard和外置sdcard情况。
在这种情况下要比直接虚拟sdcard要复杂些,
- 在fstab.hardware文件中定义需要被vold管理的分区
/devices/msm_sdcc.1/mmc_host /storage/sdcard vfat nosuid,nodev wait,formatfat,voldmanaged=sdcard:emmc@fat
/devices/msm_sdcc.2/mmc_host /storage/sdcard2 vfat nosuid,nodev wait,voldmanaged=sdcard2:auto
通过上面的vold分析,在fstab.xxx文件中只有voldmanaged的分区将会被vold管理,因此这2个分区会被vold管理。
- 在init.xxx.rc中创建挂载目录和导入环境变量
# See storage config details at http://source.android.com/tech/storage/
mkdir /storage 0755 system sdcard_r
mkdir /mnt/media_rw/sdcard 0700 media_rw media_rw
mkdir /mnt/media_rw/sdcard2 0700 media_rw media_rw
mkdir /storage/sdcard 0755 system sdcard_r
mkdir /storage/sdcard2 0755 system sdcard_r
mkdir /storage/sdcard-otg 0000 system system
export EXTERNAL_STORAGE /storage/sdcard
export SECONDARY_STORAGE /storage/sdcard2
export OTG_STORAGE /storage/sdcard-otg
export MEDIA_STORAGE /storage/sdcard
# Support legacy paths
symlink /storage/sdcard /sdcard
symlink /storage/sdcard /mnt/sdcard
symlink /storage/sdcard2 /sdcard2
symlink /storage/sdcard2 /mnt/sdcard2
- 在init.rc中启动sdcard服务挂载sdcard
service fuse_sdcard /system/bin/sdcard -u 1023 -g 1023 -d /mnt/media_rw/sdcard /storage/sdcard
service fuse_sdcard2 /system/bin/sdcard -u 1023 -g 1023 -w 1023 -d /mnt/media_rw/sdcard2 /storage/sdcard2
- 为sdcard配置storage_list.xml 文件
<StorageList
<?xml version="1.0" encoding="utf-8"?>
<StorageList xmlns:android="http://schemas.android.com/apk/res/android">
<storage
android:mountPoint="/storage/sdcard"
android:storageDescription="@string/storage_internal"
android:primary="true"
android:removable="false"
android:emulated="false"
android:mtpReserve="0"
/>
<storage
android:mountPoint="/storage/sdcard2"
android:storageDescription="@string/storage_sd_card"
android:primary="false"
android:removable="true"
android:emulated="false"
android:allowMassStorage="true"
/>
<storage
android:mountPoint="/storage/sdcard-otg" android:storageDescription="@string/storage_usb"
android:primary="false"
android:removable="true"
android:emulated="false"
android:allowMassStorage="false" 是否支持ums功能
/>
</StorageList>
上面介绍了sdcard的实现方式,其中目前越来越多的手机厂商,都不支持外置sdcad啦,主要现在内部flash分区变大,没有必要增加外置sdcard增加了反了增加成本。
-
sdcard的fuse机制
通过上面sdcard实现方式分析都采用了fuse机制来挂载。Fuse它可以为用户提供编写用户态文件系统的接口用户可以不必熟悉Kernel代码,使用标准C库、FUSE库以及GNU C库便可设计出自己需要的文件系统。Android没有直接使用libfuse的库而是重写部分代码实现可以挂载sdcard的服务sdcard。主要代码在system/core/sdcard/sdcard.c。
总体框架:
1.黑色箭头表示app访问sdcard的路径
2.红色箭头表示实际的访问流程
3.蓝色箭头表示fuse 反馈的app的流程
android_filesystem_config.h
#define AID_MEDIA_RW 1023 /* internal media storage write access */
#define AID_SDCARD_RW 1015 /* external storage write access */
#define AID_SDCARD_R 1028 /* external storage read access */
Sdcard服务分析
- 初始化fuse
static void fuse_init(struct fuse *fuse, int fd, const char *source_path,gid_t write_gid, derive_t derive, bool split_perms) { //此函数初始化重要的全局数据结构fuse,供后面创建的多线程使用这个全局结构体变量在run()函数中定义,因为run()函数永不退出,所以虽然fuse是函数内的局部变量,但它的内存其实永不释放。达相当于全局变量的效果啦。
- 启动fuse
static int run(const char* source_path, const char* dest_path, uid_t uid,
gid_t gid, gid_t write_gid, int num_threads, derive_t derive, bool split_perms) {
在这个函数中如果干了一下几件事
fd = open("/dev/fuse", O_RDWR);
//打开/dev/fuse设备
res = mount("/dev/fuse", dest_path, "fuse", MS_NOSUID | MS_NODEV, opts);
//将/mnt/shell/emulated 挂载到/dev/fuse设备
res = setgid(gid);
res = setuid(uid);
//将/mnt/shell/emulated 挂载到/dev/fuse设备
fuse_init(&fuse, fd, source_path, write_gid, derive, split_perms);
//初始化重要的fuse数据结构
res = ignite_fuse(&fuse, num_threads);
//创建 hander thread, 启动fuse
}
下面再看handler thread 创建,及启动。
static int ignite_fuse(struct fuse* fuse, int num_threads)
{
handlers = malloc(num_threads * sizeof(struct fuse_handler));
for (i = 0; i < num_threads; i++) {// 默认num_threads==2
handlers[i].fuse = fuse;
handlers[i].token = i; // 以线程号作为 token,标识handler。
}
i = (fuse->derive == DERIVE_NONE) ? 1 : 0;
for (; i < num_threads; i++) {
//service sdcard /system/bin/sdcard -u 1023 -g 1023-l /data/media /mnt/shell/emulated
// 启动sdcard service时带-l / -d参数derive 为DERIVE_LEGACY或 DERIVE_UNIFIED时。
int res = pthread_create(&thread, NULL, start_handler, &handlers[i]);
}
if (fuse->derive == DERIVE_NONE) {
handle_fuse_requests(&handlers[0]); /derive 为DERIVE_NONE主进程处理handlers[0]
} else {
watch_package_list(fuse); // 主进程watch文/data/system/packages.list"
}
- fuse请求包处理
static void handle_fuse_requests(struct fuse_handler* handler)
{
struct fuse* fuse = handler->fuse;
for (;;) {
ssize_t len = read(fuse->fd, handler->request_buffer, sizeof(handler->request_buffer));
const struct fuse_in_header *hdr = (void*)handler->request_buffer;
const void *data = handler->request_buffer + sizeof(struct fuse_in_header);
size_t data_len = len - sizeof(struct fuse_in_header);
__u64 unique = hdr->unique;
int res = handle_fuse_request(fuse, handler, hdr, data, data_len);
if (res != NO_STATUS) {
if (res) {
TRACE("[%d] ERROR %d\n", handler->token, res);
}
fuse_status(fuse, unique, res);//返回fuse request请求的处理结果。
}
}
}
static int handle_fuse_request(struct fuse *fuse, struct fuse_handler* handler,
const struct fuse_in_header *hdr, const void *data, size_t data_len)
{
switch (hdr->opcode) {
case FUSE_OPEN: { /* open_in -> open_out *///打开要操作的文件, 记录文件描述符fd
const struct fuse_open_in *req = data;
return handle_open(fuse, handler, hdr, req);
}
// READ / WRITE 操作都涉及到sdcard usrspace 与kernel fuse kernel spcace之间的读、写数据的内存copy交互。
case FUSE_READ: { /* read_in -> byte[] */
const struct fuse_read_in *req = data;
return handle_read(fuse, handler, hdr, req);
}
case FUSE_WRITE: { /* write_in, byte[write_in.size] -> write_out */
const struct fuse_write_in *req = data;
const void* buffer = (const __u8*)data + sizeof(*req);
return handle_write(fuse, handler, hdr, req, buffer);
}
}
- sdcard权限控制
先看一下定义的文件几种权限:
typedef enum {
/* Nothing special; this node should just inherit from its parent. */
PERM_INHERIT,
/* This node is one level above a normal root; used for legacy layouts
* which use the first level to represent user_id. */
PERM_LEGACY_PRE_ROOT,
/* This node is "/" */
PERM_ROOT,
/* This node is "/Android" */
PERM_ANDROID,
/* This node is "/Android/data" */
PERM_ANDROID_DATA,
/* This node is "/Android/obb" */
PERM_ANDROID_OBB,
/* This node is "/Android/user" */
PERM_ANDROID_USER,
} perm_t;
权限控制的三种方法,
/* Permissions structure to derive */
typedef enum {
DERIVE_NONE,
DERIVE_LEGACY, ====》用于内置sdcard ,主要用于多用户的权限控制
DERIVE_UNIFIED, ====》用于外置sdcard
} derive_t;
在b步骤中 watch_package_list(fuse);主要的作用是监听packages.list文件的变化,并将填充fuse->package_to_appid结构体将package和appid对应起来这样后面可以根据aapid判断是哪个应用访问的该目录,填充fuse->appid_with_rw,用来判断是aapid应用的权限。在c步骤中handle_fuse_request函数中会检查访问的进程aapid是否有权限读写。
/* Return if the calling UID holds sdcard_rw. */
static bool get_caller_has_rw_locked(struct fuse* fuse, const struct fuse_in_header *hdr) {
/* No additional permissions enforcement */
if (fuse->derive == DERIVE_NONE) {
return true;
}
appid_t appid = multiuser_get_app_id(hdr->uid);
return hashmapContainsKey(fuse->appid_with_rw, (void*) appid);
}
这个简单的分析了fuse对sdcard的权限控制流程,详细代码具体参考sdcard.c里面的实现。
总结一下如果在拥有外置sd卡的kitkat设备上进行文件操作,对于开发者而言哪些能做、哪些不能做?下图给出开发者会尝试
的一些操作及结果:
总结一下,自4.4开始Google对secondary volume做了限制之后,不仅为用户带来了不便,也为设备制造商及开发者带来了诸多不便,如今,除了一些OEM厂商自行修改权限后的Rom对第三方应用没有限制外,也为已Root的设备用户提出修改platform.xml文件来修改权限以使第三方应用可以操作外置sd卡。
-
MTP 分析
-
MTP协议介绍
根据协议,MTP的使用者包括两个部分,分别是Initiator和Responder:
- Initiator:主要是指USB Host,例如PC机,笔记本等。协议规定所有MTP操作只能由Initator发起。
- Responder:一般是诸如数码相机、智能手机等存储媒体文件的设备。Responder在MTP中的作用就是处理Initator发起的请求。同时,它还会根据自身状态的变化发送Event以通知Initiator。
注意:后文我们将统一以PC代表Initiator,Android手机代表Responder。
与很多协议一样,MTP也有自己的协议栈:
MTP协议栈由下到上分别是:
- Pyshical Layer(物理层):物理层在MTP协议中用来传输数据。目前有三种物理层可供MTP使用。它们分别是USB:其主要特点是传输文件,同步媒体文件时速度快,而且可以边工作边充电,这是目前用的最多的一种方式;IP:基于IP的MTP(简称MTP/IP)将通过UPnP来匹配和发现设备。它是家庭网络中是最理想的传输方式;Bluetooth:MTP/BT是最省电,同时也是速度最慢的一种传输方式,用处较少。
- 传输层:MTP中,数据传输格式遵循PTP协议
- 命令层:实现了MTP协议中的各种命令。
-
总体框架
在Kernel层,USB驱动负责数据交换,而MTP驱动负责和上层进行通信,同时也和USB驱动进行通信。
(01)USB驱动负责数据交换,是指Android设备和PC通过USB数据线连接之后,实际的数据交换是经过USB数据线发送给USB驱动的。
(02)对于"MTP请求"而言,MTP驱动会从USB驱动中解析出的MTP请求数据,然后传递给上层。而对于上层传来的"MTP反馈",MTP驱动也会将反馈内容打包好之后,通过传递给USB驱动。
在JNI层,MtpServer会不断地监听Kernel的消息"MTP请求",并对相应的消息进行相关处理。同时,MTP的Event事件也是通过MtpServer发送给MTP驱动的。 MtpStorage对应一个"存储单元";例如,SD卡就对应一个MtpStorage。 MtpPacket和MtpEventPacket负责对MTP消息进行打包。android_mtp_MtpServer是一个JNI类,它是"JNI层的MtpServer 和 Java层的MtpServer"沟通的桥梁。android_mtp_MtpDatabase也是一个JNI类,JNI层通过它实现了对MtpDatabase(Framework层)的操作。
在Framework层,MtpServer相当于一个服务器,它通过和底层进行通信从而提供了MTP的相关服务。MtpDatabase充当着数据库的功能,但它本身并没有数据库对数据进行保存,本质上是通过MediaProvider数据库获取所需要的数据。MtpStorage对应一个"存储单元",它和"JNI层的MtpStorage"相对应。
在Application层,MtpReceiver负责接收广播,接收到广播后会启动/关闭MtpService;例如,MtpReceiver收到"Android设备 和 PC连上"的消息时,会启动MtpService。 MtpService的作用是提供管理MTP的服务,它会启动MtpServer,以及将本地存储内容和MTP的内容同步。 MediaProvider在MTP中的角色,是本地存储内容查找和本地内容同步;例如,本地新增一个文件时,MediaProvider会通知MtpServer从而进行MTP数据同步。
-
MTP模块启动的流程:
MTP服务启动时,Java层的程序流程如下图01所示。
说明:MTP服务启动的触发事件是"PC和Android设备建立MTP连接"。当她们建立MTP连接时,USB驱动将产生USB连接消息,并最终通知UsbManager。UsbManager发出广播,并且广播被MtpReceiver收到;MtpReceiver收到广播后会启动MtpService,同时通知MediaProvider。MediaProvider会与MtpService绑定,若Android设备中的文件结构有变化(如"新键文件"),MediaProvider则会通知MtpService。MtpService启动后会创建MtpDatabase;之后,还会创建MtpServer,MtpServer会和MtpDatabase关联。然后,MtpService会遍历本地的存储设备,并建立相应的MtpStorage,并将该MtpStorage添加到MtpDatabase和MtpServer中。最后,MtpService会启动MtpServer。
MTP服务启动时,JNI层的程序流程如下图所示。
说明: 前面说过MtpService启动后会先后创建MtpDatabase对象和MtpServer对象(Java层),然后启动MtpServer(Java层)。
在创建MtpDatabase对象时,会通过native_setup()调用JNI本地方法。目的是进行初始化,为后面的MtpServer调用做准备。
在创建MtpServer对象(Java层)时,会通过native_setup()调用JNI本地方法。在本地方法中,打开MTP驱动创建的文件节点"/dev/mtp_usb",并会获取MyMtpDatabase对象,然后创建"MtpServer对象(JNI层)"。
在启动MtpServer线程时,会对应的执行MtpServer(JNI层)的run()方法。MtpServer(JNI层)的run()中会不断的从"/dev/mtp_usb"中读取数据,并进行相应的处理。
涉及到的主要文件的路径:
packages/providers/MediaProvider/src/com/android/providers/media/MtpReceiver.java
packages/providers/MediaProvider/src/com/android/providers/media/MtpService.java
packages/providers/MediaProvider/src/com/android/providers/media/MediaProvider.java
frameworks/base/media/java/android/mtp/MtpServer.java
frameworks/base/media/java/android/mtp/MtpDatabase.java
frameworks/base/media/java/android/mtp/MtpStorage.java
frameworks/base/media/jni/android_mtp_MtpServer.cpp
frameworks/base/media/jni/android_mtp_MtpDatabase.cpp
frameworks/av/media/mtp/MtpServer.h
frameworks/av/media/mtp/MtpServer.cpp
frameworks/av/media/mtp/MtpDatabase.h
-
PC查看MTP的文件
下面以"PC中打开一个MTP上的文件(读取文件内容)"来对"MTP协议中Initiator到Reponser的流程"进行说明。PC读取文件内容的时序图如图所示:
1 read()
根据MTP启动流程中分析可知: MTP启动后,MtpServer.cpp中的MtpServer::run()会通过read()不断地从"/dev/mtp_usb"中读取出"PC发来的消息"。
2 handleRequest()
read()在读取到PC来的消息之后,会交给MtpServer::handleRequest()进行处理。"PC读取文件内容"的消息的ID是MTP_OPERATION_GET_OBJECT;因此,它会通过doGetObject()进行处理。
3. doGetObject()
doGetDeviceInfo的流程就是,先通过JNI回调Java中的函数,(根据文件句柄)从MediaProvider数据库中获取文件的路径、大小和格式等信息。接着,将这些信息封装到kernel的"mtp_file_range结构体"中。最后,通过ioctl将"mtp_file_range结构体"传递给kernel。这样,kernel就收到文件的相关信息了,kernel负责将文件信息通过USB协议传递给PC。
4 getObjectFilePath()
doGetObject()会调用的getObjectFilePath()。该函数在android_mtp_MtpDatabase.cpp中实现。MyMtpDatabase::getObjectFilePath()实际上通过MtpDatabase.java中的getObjectFilePath()来获取文件的相关信息的。
5 getObjectFilePath()
getObjectFilePath()会从MediaProvider的数据库中查找相应的文件,从而获取文件信息。然后,将文件的信息回传给JNI。
6 ioctl操作
MtpServer.cpp中doGetObject()中获取到文件信息之后,会通过ioctl将MTP_SEND_FILE_WITH_HEADER消息传递给Kernel。
根据前面介绍的Kernel内容克制,ioctl在file_operations中注册,ioctl实际上会执行mtp_ioctl()函数。mtp_ioctl()在收到MTP_SEND_FILE_WITH_HEADER消息之后,会获取文件相关信息。然后将"work"添加到工作队列dev->wq中进行调度。work对应的函数指针是send_file_work()函数;这就意味着,工作队列会制定send_file_work()函数。
send_file_work()的作用就是不断地将文件中的数据读取到内存中,并封装到USB请求结构体req中。然后,再将数据req传递到USB工作队列,USB赋值将文件内容传递给PC。
至此,PC读取文件内容的流程分析完毕!
-
在手机端上增加文件
下面以"Android设备中将一个文件拷贝到其他目录"来对"MTP协议中Reponser到Initiator的流程"进行说明
1 MediaProvider.java中sendObjectAdded()
在Android设备上将一个文件拷贝到其他目录。文件浏览器中会发出一个Intent事件,通知MediaProvider更新数据库。MediaProvider更新了数据库之后,会通知MTP进行同步处理。该函数会通知MtpService,Android设备中新建了个文件。
2 MtpService.java中sendObjectAdded()
MtpService.java中sendObjectAdded()函数会发送消息给mServer,而mServer是MtpServer对象。最终会调到JNI的native_send_object_added函数中。
3native_send_object_added()
native_send_object_added()在android_mtp_MtpServer.cpp中实现。根据前面介绍相关内容可知,native_send_object_added()和android_mtp_MtpServer_send_object_added()对应。该函数会将消息发送给MtpServer。
4 MtpServer.cpp中的sendObjectAdded
MtpServer.cpp中sendObjectAdded()的源码如下:
void MtpServer::sendObjectAdded(MtpObjectHandle handle) {
sendEvent(MTP_EVENT_OBJECT_ADDED, handle);
}
说明:sendEvent()的作用,我们前面已经介绍过了。它在此处的目的,就是通过ioctl将消息发送给Kernel。
5 Kernel中的ioctl
在Kernel中,ioctl消息会调用mtp_ioctl()进行处理。对于MTP_SEND_EVENT消息,mtp_ioctl会先将"用户空间"传递来的消息拷贝到"内核空间";然后,调用mtp_send_event()进行处理。
6Kernel中的mtp_send_event()
mtp_send_event()会先将"用户空间"传递来的消息的具体内容拷贝到"内核空间",并将该消息封装在一个USB请求对象req中;然后,将USB请求添加到USB队列中。USB驱动负责将该数据通过USB线发送给PC。
-
系统存储空间监测
在Android系统中提供了DiskStateService和DeviceStorageMonitorService这个服务来监测系统的存储状态使用情况具体代码目录:
Base/services/java/com/android/server/DiskStateService.java
Base/services/java/com/android/server/DeviceStorageMonitorService
-
DiskStateService分析
系统提供了一个DiskStatsService来展示系统磁盘分区使用的状态。
这个DiskStateService本身并没有提供了对外访问的接口,但是继承了binder如这个类解释,主要是用来在dumpsys 调试用的。在SystemServer.java 中会被ServiceManager加入到管理列表中,在dumpsys中调用。
在终端里面可以使用dumpsys 看一下
-
DeviceStorageMonitorService分析
在系统中还有DeviceStorageMonitorService服务来监控系统中磁盘剩余空间的大小。主要监控的data分区。通常系统设置的安全阈值为data分区的10%或者500M,两者取最小的那个值。即如果系统分区剩余空间小于安全阈值时,
就会通过NotificationManager 发送 mStorageLowIntent 在状态栏显示存储空间低的提示。如果清理后的data分区大于阈值NotificationManager会取消通知并发送mStorageOkIntent。如果data分区已经full了系统会发送mStorageFullIntent的广播。同样如果恢复了也会发出mStorageNotFullIntent的广播。系统会通过checkMemory函数一分钟一次的监控data分区的大小。并且没12小时把cache、system和data分区的剩余大小写到Eventlog中。下面是checkMomory函数的流程。
具体代码在base/service/java/com/android/server/DeviceStorageMonitorService.java 中这个类挺简单的有兴趣的同学看一下。