[TOC]
原文地址:
http://blog.chinaunix.net/uid-26281173-id-3761483.html
Storage Manager
在Android系统中,常用的存储介质是Nand Flash;系统的二进制镜像、Android的文件系统等通常都保存在Nand Flash 中。通常使用的Micro-SD卡的管理则是由卷守护进程(Volume Daemon ,vold)去完成的,包括SD卡的插拔事件检测、挂载、卸载、格式化等。这里会从Volume的原理和机制去分析Android对于UMS的管理;并通过一个SD Card的挂载过程详细说明。当热插拔SD Card的时候所引起的Linux Kernel的变化,如如何发出uevent消息等没深入研究,Vold接收这些uevent消息也仅仅是提到而已;因此这几着重介绍Vold、syslibutils以及MountService。
有关Vold的架构可以用图1-1来表示
图1-1 Vold架构
从图1-1Vold架构,可以大致的了解Android针对外部存储的管理流程。
这里需要走出一个误区:当插入SD卡的时候Kernel发出uevent、Vold处理event,MountService从Vold获取相应信息发出广播,app在接收到广播之后会作出相应的处理;其实到这里SD卡并没有真正的被挂载到系统中,仅仅是触发了相应的uevent,而真正的挂载并没有执行。实际情况是如图1-1Vold架构所示,先得到Uevent在交由Vold进行解析,然后由MountService获取信息发出mount、unmount等命令给Vold,在由Kernel进行针对存储设备的挂载、卸载、格式化等操作。
上面有讲到,Vold不仅仅要处理来自Kernel的Uevent等信息,还得处理来自MountService的信息。那么到底Vold是如何获取到Kernel发上来的Uevent信息?Vold又是如何从MountService获取到上层要发给Kernel的命令的呢?
在这里拥有一个重要的基类SocketListener。不仅对kernel发出的uevent进行监听还对MountService想kernel发出的命令进行监听。其常用类之间的结构关系如图1-2 Vold重要类关系图
图1-2 Vold重要类关系图
通过类图,我们可以大致的了解到Vold的工作流程、工作原理:Vold是Android平台中外部存储系统的管理中心;从kernel来的相关event以及从frameworks来的commond都要通过Vold来进行通信。这里Kernel和Frameworks不仅仅是从Vold获取东西也会想Vold发出命令。下图我们将会更加详细的介绍存储设备的挂载流程。
如图1-2 处理流程,更加清晰的说明了Android针对Storage Manager的处理流程。就更加凸显了SocketListener这个类的重要性;并且Vold和MountService之间在进行进程间通信的时候并没有用到复杂Binder,而是直接的使用了Socket;这样就是的本身简单的Vold系统更加易懂。
图1-2 处理流程
2、Vold工作过程
2.1处理Kernel信息
当外部存储SD Card插入之后,Kernel会发出相应的Uevent消息;而这时Vold也正在监听,所以会有Vold当中的NetLinkManager接受到消息。当然在这消息的接受-在发送过程中肯定是有消息的解析并针对解析出的相关信息在作出动作。设计到的主要类如下图所示
图2-1Sock相关类
在能否获取Kernel的Uevent之前,SocketListener便会启动一个线程去针对Kernel的event进行监听;同时还会创建一个用于传输数据的pipe。当Kernel有event抵达,也就是有数据通过Socket发送过来,就会调用SocketListener自己的onDataAvailable对数据进行处理。而这里SocketListener的onDataAvailable是一个纯虚函数,其真正的实现在NetlinkListener中,所以数据的真正解析在NetlinkListener中。如果解析event OK,那么这个时候就该发出一个event给Volume Manager了。
其实通过这里,我们也可以看出Vold这个较小的进程也分为两个部分:一个同Kernel接触,一个桶Framework接触。
2.2向Framework发出通知
几乎所有从Kernel上来的mount信息以及从Frameworks到Kernel的mount消息都需要经过DirectVolume.cpp的handleBlockEvent来进行处理。
int DirectVolume::handleBlockEvent(NetlinkEvent*evt) {
const char *dp = evt->findParam("DEVPATH");
PathCollection::iterator it;
for (it= mPaths->begin(); it!= mPaths->end();++it){
if (!strncmp(dp,*it, strlen(*it))){
/* We can handle this disk*/
int action = evt->getAction();
const char *devtype = evt->findParam("DEVTYPE");
if (action == NetlinkEvent::NlActionAdd){
int major = atoi(evt->findParam("MAJOR"));
int minor = atoi(evt->findParam("MINOR"));
char nodepath[255];
/*获得当前node的信息*/
snprintf(nodepath,
sizeof(nodepath),"/dev/block/vold/%d:%d",
major, minor);
/*创建node mknod*/
if (createDeviceNode(nodepath, major, minor)){
SLOGE("Error making device node '%s' (%s)", nodepath,strerror(errno));
}
/*是否有分区,disk 没有分区*/
if (!strcmp(devtype,"disk")){
handleDiskAdded(dp, evt);
} else {
handlePartitionAdded(dp, evt);
}
/* Send notification iff diskis ready (ie all partitions found)*/
/*开始给Frameworks发广播了。。*/
if (getState()== Volume::State_Idle){
char msg[255];
snprintf(msg, sizeof(msg),
"Volume %s %s disk inserted (%d:%d)", getLabel(),getMountpoint(), mDiskMajor, mDiskMinor);
mVm->getBroadcaster()->sendBroadcast(
ResponseCode::VolumeDiskInserted, msg,false);
}
/*下面的两个不注释了。。。*/
} else if (action== NetlinkEvent::NlActionRemove){
if (!strcmp(devtype,"disk")){
handleDiskRemoved(dp, evt);
} else {
handlePartitionRemoved(dp, evt);
}
} else if (action== NetlinkEvent::NlActionChange){
if (!strcmp(devtype,"disk")){
handleDiskChanged(dp, evt);
} else {
handlePartitionChanged(dp, evt);
}
} else {
SLOGW("Ignoring non add/remove/change event");
}
return 0;
}
}
errno = ENODEV;
return -1;
}
在调用handleDiskAdded(dp, evt);完成之后,便会从系统中获取该存储设备的相关信息;准备发广播给Frameworks了。
snprintf(msg, sizeof(msg),"Volume %s %s disk inserted (%d:%d)", getLabel(),
getMountpoint(), mDiskMajor, mDiskMinor);
mVm->getBroadcaster()->sendBroadcast(ResponseCode::VolumeDiskInserted, msg, false);
这里需要注意一点:在handleDiskAdded中,处理完成了之后,会更新当前磁盘的状态
setState(Volume::State_Idle);这个函数需要重点关注
这样Frameworks就收到了有存储设备的消息;下面来看看Framew是如何处理这样的信息的。
public boolean onEvent(int code,String raw,String[] cooked){
/*……………*/
} else if ((code== VoldResponseCode.VolumeDiskInserted)||
(code == VoldResponseCode.VolumeDiskRemoved)||
(code == VoldResponseCode.VolumeBadRemoval)){
// FMT: NNN Volume
// FMT: NNN Volume
// FMT: NNN Volume
final String path= cooked[3];
/*这里是见证奇迹的时刻之一。。哈哈*/
if (code== VoldResponseCode.VolumeDiskInserted){
if ((rc= doMountVolume(path))!= StorageResultCode.OperationSucceeded){
这个onEvent目前先看到这里,去看看doMountVolume
private int doMountVolume(String path){
mConnector.execute("volume","mount", path);
这里mConnector 是一个 NativeDaemonConnector对象,算是一个守护程序吧,跟守护进程差不多,专门用来处理 libsysutils中通过FrameworkListener协议来进行信息传递。
这里转了一大圈又回到了Vold。接下来继续回到Vold进行查看。在这其中会经过libsysutils的一些转换,这里直接去Volume.cpp 中的mountVol查看。首先会检测是否已经被mount了。
if (isMountpointMounted(getMountpoint())){
setState(Volume::State_Mounted);
// mCurrentlyMountedKdev= XXX
return 0;
}
到这里,setState() 的作用,我觉得可以这么来理解,他同Activity的生命周期相同,只不过这里是“显示”的去修改各个状态,而Activity Android系统本身已经做好了。这里假设目前还没有被mount,接着往下看:
/* Wenow have the new sysfs path for the decrypted block device, and the
* majore and minor numbersfor it. So, create the device, update the
* path to the new sysfs path,and continue.
*/
snprintf(nodepath, sizeof(nodepath),"/dev/block/vold/%d:%d",
new_major, new_minor);
if (createDeviceNode(nodepath, new_major, new_minor)){
SLOGE("Error making device node '%s' (%s)", nodepath, strerror(errno));
}
// Todo: Either create sys filename from nodepath,or pass in bogus path so
// vold ignores state changeson this internal device.
updateDeviceInfo(nodepath, new_major, new_minor);
/* Get the device nodes again, because they just changed*/
n = getDeviceNodes((dev_t*) &deviceNodes, 4);
。。。。
sprintf(devicePath,"/dev/block/vold/%d:%d", MAJOR(deviceNodes[i]),
MINOR(deviceNodes[i]));
setState(Volume::State_Checking);
。。。。。。
if (Fat::doMount(devicePath,"/mnt/secure/staging",false, false, false,
AID_SYSTEM, gid, 0702,true)){
continue;
}
/*。。。。*/
/*删除autorun.inf.为啥??不为啥?就是要删!!*/
protectFromAutorunStupidity();
/*
* Now that the bindmount trickeryis done, atomically move the
* whole subtree to expose it to non priviledged users.*/
if (doMoveMount("/mnt/secure/staging", getMountpoint(),false)){
SLOGE("Failed to move mount (%s)", strerror(errno));
umount("/mnt/secure/staging");
setState(Volume::State_Idle);
return -1;
}
/* 又是这个神一样的函数,setState,这次可是设置了State_Mounted,
* 很明显,这里要发广播了,给Frameworks发!告诉Frameworks,
* 我这儿已经mounted完成了,剩下就是你的事儿了
*/
setState(Volume::State_Mounted);
mCurrentlyMountedKdev = deviceNodes[i];
return 0;
}
从现在开始,磁盘的大部分操作同Vold、Kernel基本上没什么关系了,继续来看看MountService对消息的处理。
if (code == VoldResponseCode.VolumeStateChange){
/*
* One of the volumes we're managing has changed state.
* Format: "NNN Volume <label> <path> state changed
* from <old_#> (<old_str>) to <new_#> (<new_str>)"
*/
notifyVolumeStateChange(cooked[2], cooked[3],
Integer.parseInt(cooked[7]),Integer.parseInt(cooked[10]));
notifyVolumeStateChange函数基本上就干了一件事儿:发ACTION_MEDIA_* 各种广播。。这就不多说了。到这儿所有APP,该接受的接受,该处理的处理,干干啥的干啥。
Over,完了
EMMC: Embedded MultiMedia Card 内嵌式多媒体卡
Volume 状态