Android存储子系统

这篇文章主要是分析Android存储向关联的一些模块,这个分析主要从大的工作流程和代码模块分析,没有对于没有分析到地方后续遇到后在详细分析。主要从以下几个模块分析

系统分区的挂载、外部分区挂载、Vold守候进程分、MountService的业务分析、Sdcard的详细分析、MTP模式分析和设备存储空间的监控机制。

  1. 系统分区挂载

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。

  1. 外置分区挂载

上面了解了系统分区的挂载,下面分析一下外置sdcard或者usb等外部存储设备的挂载过程。整个挂载过程在Vold和MountService(SystemServer)进程中完成。sdcard插拔会被kernel监听到并发送uevent到用户空间。此时vold守候进程会收到从kernel上报uvent事件,检查和处理后并上报到MountService中,MountSerivce和上层其他模块交互确认然后通过socket发送指令给vold完成具体的磁盘Mount和Unmount操作整体框架如下图:

当然sdcard的事件也不是总是由底层上报,App应用上层可以通过format和unmount sdcard操作通知MountService进而通知vold完成对应的操作。

  1. Vold守候进程分析

  1. 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去完成。

  1. 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死循环变成守候进程,一直存在系统中。

    1. 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的框架已经讲解完了,具体的命令的实现细节需要看代码仔细分析。

  1. MountService分析:

mountService作为系统里面服务为提供volume、obb和Asec的挂载和监听volume的状态并及时的向其他模块更新状态。

  1. MountService总体框架

下面的图简单的说明MountService和其他系统模块的关系

    1. MountService的启动

MountService是在SystemServie中创建的,在MountService的构造方法主要做了以下工作。

  1. readStorageListLocked()读取volume配置文件,并加载mVolume,mVolumesByPath和mVolumestate.供后面查询用。
  2. 创建MountServiceHandler处理线程。
  3. 注册接收用户和usb状态更新广播。
  4. 将obbActionHandler添加MountSericeHandler线程中,并初始化pm和申请wakelock。
  5. 为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发起的处理流程。

  1. 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做了权限限制

  1. 常见的sdcard实现方式

Sdcard 是Android系统中主要的外部存储,主要有下面几种常见方式

  • 通过fuse方式把/data/media目录虚拟成sdcard
  • 在flash上划分一个分区并通过fuse方式变成sdcard
  • 物理的sdcard设备

在google的nexus 5上就是通过fuse机制将/data/media目录虚拟成sdcard。下面简单的介绍一下主要步骤

  1. 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()获取到的目录。

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

  1. 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要复杂些,

  1. 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管理。

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

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

  1. 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增加了反了增加成本。

    1. 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服务分析

  1. 初始化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是函数内的局部变量,但它的内存其实永不释放。达相当于全局变量的效果啦。

  1. 启动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"

}

  1. 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);
   }
}

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

  1. MTP 分析

    1. 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协议中的各种命令。
    1. 总体框架

在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数据同步。

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

    1. 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读取文件内容的流程分析完毕!

    1. 在手机端上增加文件

下面以"Android设备中将一个文件拷贝到其他目录"来对"MTP协议中Reponser到Initiator的流程"进行说明

1 MediaProvider.javasendObjectAdded()

在Android设备上将一个文件拷贝到其他目录。文件浏览器中会发出一个Intent事件,通知MediaProvider更新数据库。MediaProvider更新了数据库之后,会通知MTP进行同步处理。该函数会通知MtpService,Android设备中新建了个文件。

2 MtpService.javasendObjectAdded()

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。

  1. 系统存储空间监测

在Android系统中提供了DiskStateService和DeviceStorageMonitorService这个服务来监测系统的存储状态使用情况具体代码目录:

Base/services/java/com/android/server/DiskStateService.java

Base/services/java/com/android/server/DeviceStorageMonitorService

  1. DiskStateService分析

系统提供了一个DiskStatsService来展示系统磁盘分区使用的状态。

这个DiskStateService本身并没有提供了对外访问的接口,但是继承了binder如这个类解释,主要是用来在dumpsys 调试用的。在SystemServer.java 中会被ServiceManager加入到管理列表中,在dumpsys中调用。

在终端里面可以使用dumpsys 看一下

    1. 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 中这个类挺简单的有兴趣的同学看一下。

时间: 2024-10-07 00:14:31

Android存储子系统的相关文章

Android Wifi子系统源代码View

本文基于Android 4.2.2+Linux3.6.9+SAMA5D3 SoC从源代码的角度审视Android Wifi子系统. 软件平台:Linux3.6.9 + Android 4.2.2 硬件平台:Atmel SAMA5 Wifi模组:RTL8723AU(USB接口) Android的WiFi子系统自上而下包括如下一些内容: 应用层 Androd系统自带Settings应用 /system/app/Settings.apk http://androidxref.com/4.2.2_r1/

Android Camera子系统之进程/文件View

本文基于Android 4.2.2从进程/文件的角度审视Android Camera子系统. AndroidCamera子系统的整体架构分成客户端(Client)和服务器(Server)两个部分,它们建立在Android的进程间通讯机制Binder的基础之上. 查看进程 [email protected]:/# ps USER  PID  PPID  VSIZE   RSS    WCHAN   PC        NAME media 1012    1  37484  8740 ffffff

Android Camera子系统之用户View

一.拍照模式 打开原生Camera应用,将出现如下所示拍照界面 左边为预览区域,右边为控制面板. 控制面板分为三部分,从上到下依次为缩略图.快门按钮和模式选择器. 模式选择器中显示当前模式为拍照模式. 按下快门按钮将会执行拍照操作,缩略图区域显示所拍照片的缩略图. 二.录像模式 点击模式选择器区域,选择录像模式,将会进入录像界面,如下图所示 点击快门按钮将会开始录像,再次点击快门,结束录像. Android Camera子系统之用户View,码迷,mamicode.com

Android Camera子系统之源代码View

本文基于Android 4.2.2+Linux3.6.9+SAMA5D3 SoC从源代码的角度审视Android Camera子系统. 应用层 Androd原生Camera应用 /system/app/LegacyCamera.apk http://androidxref.com/4.2.2_r1/xref/packages/apps/LegacyCamera/ Camera应用调用Android应用框架提供的Camera API import android.hardware.Camera;

Android 存储学习之在外部存储中读写文件

上节学习了如何在手机内部存储中读写文件,本节学习如何在手机的外部存储中读写文件.那就是如何在Sdcard中读写文件. 那我们还是用以前登录界面的例子举例说明,(登录界面请看上节Android 存储学习之在内部存储中读写文件) 先我们显示写的代码: 当点击确定并且自动登录的钩是选中的,则就会在sdcard文件夹写创建一个info.txt文件 public void login(View v) { String name = ed_nam.getText().toString(); String p

Android 存储学习之使用SharedPreference保存文件

上两节我们都是使用文本文件保存用户的信息,这明显是存在漏洞的.同时对文件中的内容不好管理.今天我们学习用SharedPreference保存.sharedPreference是专门保存一些比较零散的数据的. 我们还是用上节的例子分析,将用户的信息使用SharedPreference来保存. 注意:如果不知道是什么例子,请看Android 存储学习之在内部存储中读写文件 当点击确定按钮后,就会保存用户的信息: public void login(View v) { String name = ed

存储子系统剖析——从存储子系统角度看FCoE模块

        note:在之前的一篇FCoE模块设计和实现的文章,大致讲到了FCoE模块的设计.虽然很清楚地讲了FcoE模块的组成,可是没有站在整个存储子系统的角度来看FCoE,总是有点局限的感觉,然后存储子系统和网络子系统是怎么交互的也没有说清楚,希望在这里这些疑问都能得到解答. 我们知道linux内核是层次设计的设计模式,存储子系统也不例外.下面这张图(来自网络)表示了linux下面存储子系统的层次化模块. e.g.Application 访问文件 根据上图,如果一个用户空间中的应用程序(

Android Camera子系统之Linux C应用开发者View

Android Camera HAL通过V4L2接口与内核Camera Driver交互.本文从Linux应用开发者的角度审视Android Camera子系统. V4L2应用开发一般流程: 1. 打开设备文件. int fd=open("/dev/videoX″,O_RDWR); 2.取得设备的capability,看看设备具有什么功能,比如是否具有视频输入,或者音频输入输出等.VIDIOC_QUERYCAP,structv4l2_capability 3.选择视频输入,一个视频设备可以有多个

Android 存储(本地存储 SD卡存储 SharedPreference SQLite ContentProvider)

本文出自:http://blog.csdn.net/dt235201314/article/details/73176149 源码下载欢迎Star(updating):https://github.com/JinBoy23520/CoderToDeveloperByTCLer 一丶慨述 本周的学习内容是Android存储,要求:数据库Sqlite相关操作,常用的文件存取方式,以及实用场景学习,主要学习Sqlite,SD卡文件操作,SharedPreference 二丶效果演示: