码迷,mamicode.com
首页 > 移动开发 > 详细

Android存储子系统

时间:2015-08-14 22:54:18      阅读:2382      评论:0      收藏:0      [点我收藏+]

标签:


这篇文章主要是分析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 文件的systemdatacache分区的挂载。

do_mount_all函数中会fork一个子进程,在子进程中读取/fstab.xxx文件并保存在fstab结构体中,供在fs_mgr_mount_all函数中使用。

技术分享

技术分享

mount分区时会判断是含有voldmanaged 标示位,如果含有voldmanged标示则跳过mount。如果分区含有MF_CRYPT标示则mounttmpfs文件并返回encrypted的值为1,在父进程中设置  property_set("ro.crypto.state", "encrypted")供后面系统解密分区会用到。

到此系统分区的挂载已经ok



  1. 外置分区挂载


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

技术分享


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


  1. Vold守候进程分析

  1. Uevent测试程序

外置sdcardusb插拔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的一个本地服务,负责处理诸如SDUSB等存储类设备的插拔等事件。Vold服务由volumeManager统一管控,它将具体任务分别分派给netlinkManager, commandListener, directVolume, Volume去完成。


  1. Vold的启动分析

voldnative的守候进程是在init进程通过解析init.rc 启动的。

service vold /system/bin/vold

class core

socket vold stream 0660 root mount

启动vold后并同时创建了socket的用于和其他进程通信。

技术分享


具体的启动过程可以参看system/vold/main.cpp文件详细的了解启动过程。

启动主要做了以下几点工作:

创建VolumeManagerNetLinkeManager实例。

创建CommandListenser并注册到vmnm中并调用nm.start()启动netlink的监听模式。

解析fstab.xxx文件并根据voldmanaged标识创建DirectVolume并添加到vm

开启startListener()开始监听来自Frameworkcmd命令。

进入while死循环变成守候进程,一直存在系统中。

    1. vold的工作流程

vold启动后就进入的监听模式,同时开了2个线程监听来自kerneluevent和来自frmaework的指令操作。下面这幅图展示,vold监听uevent和接收来自MountService中的各种指令过程。


技术分享

在讲上面的图之前先介绍几个关键类以及作用,这样在看上面的图就很容易理解。

NetLinkManager:启动和关闭Netlink的监听模式

NetLinkHandleruevent的处理类

VolumeManager:    管理volumemount/unmount/shared/mountasec/list  等操作

Volume:  sdcard等外部磁盘卷的抽象,抽象类

DirectVolume: volume的子类代表外置的sdcard

CommandListener:    接收来自framework的指令的监听

SocketListener: socket通信的监听的基础类

NetLinkListener: 接收uevent的监听

NativeDaemonConnector: voldsocket通信的工具类

MountService: volume framework层的管理以及volume的控制中心


这几个的类之间的关系如下图


技术分享


看了这几个类的关系后先分析一下socketListener的监听框架,是如何监听事件分发这些事件的。

1.uevent的事件分发:在NetLinkManager通过创建NetlinkHandler对象并通过start方法调用sockeLisetenerstartListener方法创建一个uevent线程并执行runListener方法,在runLisenter方法中接收到client发送过来的socket并调用抽象onDateAvailable方法,具体实现在NetLinkLisenter的同名方法中,在该方法中调用NetlInkEvent类的decode方法解析uevent最后调NetLinkHandleronEvent处理uevent的插拔事件。


2.Framework的命令分发:在Vold的启动代码就执行cl->startLisenter方法进入监听状态。当frameworksocket来时,也是先调用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作为系统里面服务为提供volumeobbAsec的挂载和监听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 voldmanagedlabel要对应,虚拟sdcard的配置除外。

systemService中会调用MountSerivice.SystemReady()方法去挂载mVolumes的里面的分区,并更新volume的状态。

分析这么上面的内容,我们下面通过一个sdcard的挂载过程来分析一下整体流程。


技术分享

上面的处理过程分为5各阶段:

1.NetLinkManager开启监听阶段。

2.uevent的解析阶段,为了测试方面大家可以用uevents工具查看kernel上报的uevent内容

3.通知MountSerice挂载设备。

4.VolumeManager中的具体挂载过程。

5.挂载完成通知MountService,在mountSerivce中更新mVolume的状态并通知其他模块。

随便也看一下sdcardunmount的过程如下图:


技术分享


在卸载sdcard时会调到PackageManagerServiceupdateExternalMediaStatus函数去更新sdcard的状态,检查是挂载还是卸载。接着搜集安装在sdcard的应用,搜集完成后则删除这些应用并更新包信息,具体代码参考PKMSunloadMediaPackages的代码。 MountService除了对sdcard管理外,还会挂载ObbAsec等分区。上面了解由于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 storagesecondary external storage 是对write权限做了稍微修改,与上边所述一样,在应用所所属的目录(如:android/data/[package name])下,对文件是有所有操作权限的,在应用所能管理到目录之外,该应用则不具有写的权限,不能进行任何写的操作。Google虽然没有要求各厂商在Sdcard的操作上添加额外权限,但是它却强制要求制造商对secondary external storage做了权限限制


  1. 常见的sdcard实现方式

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

  • 通过fuse方式把/data/media目录虚拟成sdcard

  • flash上划分一个分区并通过fuse方式变成sdcard

  • 物理的sdcard设备

googlenexus 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. sdcardfuse机制

通过上面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结构体将packageappid对应起来这样后面可以根据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);

}


这个简单的分析了fusesdcard的权限控制流程,详细代码具体参考sdcard.c里面的实现。

总结一下如果在拥有外置sd卡的kitkat设备上进行文件操作,对于开发者而言哪些能做、哪些不能做?下图给出开发者会尝试

的一些操作及结果:

技术分享


总结一下,自4.4开始Googlesecondary volume做了限制之后,不仅为用户带来了不便,也为设备制造商及开发者带来了诸多不便,如今,除了一些OEM厂商自行修改权限后的Rom对第三方应用没有限制外,也为已Root的设备用户提出修改platform.xml文件来修改权限以使第三方应用可以操作外置sd卡。








  1. MTP 分析

    1. MTP协议介绍

根据协议,MTP的使用者包括两个部分,分别是InitiatorResponder


技术分享



  • Initiator:主要是指USB Host,例如PC机,笔记本等。协议规定所有MTP操作只能由Initator发起。

  • Responder:一般是诸如数码相机、智能手机等存储媒体文件的设备。ResponderMTP中的作用就是处理Initator发起的请求。同时,它还会根据自身状态的变化发送Event以通知Initiator

注意:后文我们将统一以PC代表InitiatorAndroid手机代表Responder

与很多协议一样,MTP也有自己的协议栈:



MTP协议栈由下到上分别是:

  • Pyshical Layer(物理层):物理层在MTP协议中用来传输数据。目前有三种物理层可供MTP使用。它们分别是USB:其主要特点是传输文件,同步媒体文件时速度快,而且可以边工作边充电,这是目前用的最多的一种方式;IP:基于IPMTP(简称MTP/IP)将通过UPnP来匹配和发现设备。它是家庭网络中是最理想的传输方式;BluetoothMTP/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请求",并对相应的消息进行相关处理。同时,MTPEvent事件也是通过MtpServer发送给MTP驱动的。 MtpStorage对应一个"存储单元";例如,SD卡就对应一个MtpStorageMtpPacketMtpEventPacket负责对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连上"的消息时,会启动MtpServiceMtpService的作用是提供管理MTP的服务,它会启动MtpServer,以及将本地存储内容和MTP的内容同步。 MediaProviderMTP中的角色,是本地存储内容查找和本地内容同步;例如,本地新增一个文件时,MediaProvider会通知MtpServer从而进行MTP数据同步。


    1. MTP模块启动的流程:

MTP服务启动时,Java层的程序流程如下图01所示。

技术分享

说明:MTP服务启动的触发事件是"PCAndroid设备建立MTP连接"。当她们建立MTP连接时,USB驱动将产生USB连接消息,并最终通知UsbManagerUsbManager发出广播,并且广播被MtpReceiver收到;MtpReceiver收到广播后会启动MtpService,同时通知MediaProviderMediaProvider会与MtpService绑定,若Android设备中的文件结构有变化("新键文件")MediaProvider则会通知MtpServiceMtpService启动后会创建MtpDatabase;之后,还会创建MtpServerMtpServer会和MtpDatabase关联。然后,MtpService会遍历本地的存储设备,并建立相应的MtpStorage,并将该MtpStorage添加到MtpDatabaseMtpServer中。最后,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协议中InitiatorReponser的流程"进行说明。PC读取文件内容的时序图如图所示:


技术分享

1 read()

根据MTP启动流程中分析可知: MTP启动后,MtpServer.cpp中的MtpServer::run()会通过read()不断地从"/dev/mtp_usb"中读取出"PC发来的消息"

2 handleRequest()

read()在读取到PC来的消息之后,会交给MtpServer::handleRequest()进行处理。"PC读取文件内容"的消息的IDMTP_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.cppdoGetObject()中获取到文件信息之后,会通过ioctlMTP_SEND_FILE_WITH_HEADER消息传递给Kernel

根据前面介绍的Kernel内容克制,ioctlfile_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协议中ReponserInitiator的流程"进行说明


技术分享



1 MediaProvider.javasendObjectAdded()

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

2 MtpService.javasendObjectAdded()

MtpService.javasendObjectAdded()函数会发送消息给mServer,而mServerMtpServer对象。最终会调到JNInative_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.cppsendObjectAdded()的源码如下:

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系统中提供了DiskStateServiceDeviceStorageMonitorService这个服务来监测系统的存储状态使用情况具体代码目录:

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小时把cachesystemdata分区的剩余大小写到Eventlog中。下面是checkMomory函数的流程。



具体代码在base/service/java/com/android/server/DeviceStorageMonitorService.java 中这个类挺简单的有兴趣的同学看一下。


Android存储子系统

标签:

原文地址:http://my.oschina.net/xcy2011sky/blog/492590

(0)
(1)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!