前段时间对Android 的SDCard unmount 流程进行了几篇简短的分析,由于当时只是纸上谈兵,没有实际上的跟进,可能会有一些误导人或者小错误。今天重新梳理了头绪,针对mount的流程再重新分析一次。
本篇大纲
• android 系统如何开机启动监听mount服务
• 默认挂载点在Android 系统的哪个目录
• vold.fstab 配置文件的分析
• vold 里面启动页面main做了些什么
android 系统如何开机启动监听mount服务
android sdcard 热插拔监测和执行操作是由一个启动文件vold 所统领的,系统开机会读取初始化配置文件init.rc,该文件位于比如我的板子是:device/ti/omap3evm/init.rc,具体根据自己平台查找。里面有一个是默认启动vold 服务的代码,如下:
service vold /system/bin/vold
socket vold stream 0660 root mount
ioprio be 2
如果要对该文件做出修改之类,要重新编一下boot.img 镜像文件,烧录进android 系统,之后可以在android的文件系统根目录找到init.rc文件。上述代码为启动vold 启动文件,也可以在init.rc 增加多一些我们想要的文件目录,比如增加一个可以存放多分区挂载的目录等,这个是后话。
默认挂载点在Android 系统的哪个目录
usbdisk 或者 sdcard 热插拔的时候,kernel 会发出命令执行mount或者unmount 操作,但这都是驱动级的。而mount 目录会在android 的文件系统目录下:/dev/block/vold 这个目录由vold 生成,用来存放所有的usbdisk 或者 sdcard 的挂载点。代码位于main里面最优先执行:
mkdir("/dev/block/vold", 0755);
可以根据这个目录找到如下节点:
sh-4.1# ls /dev/block/vold/
179:0 179:1 8:0 8:1 8:2 8:3 8:4
节点的小介绍:
0代表当前的整个设备,1代码当前设备的分区名称代号。
所以你会发现,sdcard只有一个分区它却生成了两个如:179:0 179:1
而usbdisk 有四个分区,它会生成五个挂载点: 8:0 8:1 8:2 8:3 8:4 就是这个原因。
vold.fstab 配置文件的分析
vold 里面会通过指定文件来读取预先配置好的sdcard或者多分区配置文件,该文件位于
/system/core/rootdir/etc/vold.fstab
如以下的配置文件为:
dev_mount sdcard /mnt/sdcard auto /devices/platform/goldfish_mmc.0 /devices/platform/msm_sdcc.2/mmc_host/mmc1
dev_mount 代表挂载格式
sdcard 代表挂载的标签
/mnt/sdcard 代表挂载点
auto 为自定义选项可以为任何,但必须在main 里面自己判断比如这里的意思为自动挂载
后面两个目录为设备路径,第一个如果被占用会选择第二个
配置文件可以根据自己的需要编写,并不是固定的,但最好遵循google vold 启动文件代码的格式编写,要不然会给我们修改代码或者增加多分区功能带来不小的麻烦,如以下我自己编写的多分区挂载支持vold.fstab 配置文件:
dev_mount sdcard external /mnt/sdcard auto /devices/platform/mmci-omap-hs.0/mmc_host/mmc0 /devices/platform/mmci-omap-hs.0/mmc_host/mmc1
dev_mount usb1 external /mnt/usbdisk/usb1-disk%d all /devices/platform/ehci-omap.0/usb1/1-2/1-2.1/
dev_mount usb2 external /mnt/usbdisk/usb2-disk%d all /devices/platform/ehci-omap.0/usb1/1-2/1-2.2/
dev_mount usb3 external /mnt/usbdisk/usb3-disk%d all /devices/platform/ehci-omap.0/usb1/1-2/1-2.3/
该文件修改后经系统编译会在android 系统目录里/system/etc/vold.fstab找到。
/devices/platform/ehci-omap.0/usb1/1-2/1-2.1/ 代表要挂载的USB口。
vold.fstab 只是一个单纯的配置文件,具体的读取和取数据还 是要靠main里面的process_config函数。看代码,里面正有一段用来读取配置文件:
if (!(fp = fopen("/etc/vold.fstab", "r"))) {
return -1;
}
在这个函数里面会根据读取到的数据存放起来,然后满足条件时执行操作。比如代码里面的:
if (!strcmp(type, "dev_mount")) {
DirectVolume *dv = NULL;
char *part;
if (!(part = strtok_r(NULL, delim, &save_ptr))) {
SLOGE("Error parsing partition");
goto out_syntax;
}
if (strcmp(part, "auto") && atoi(part) == 0) {
SLOGE("Partition must either be 'auto' or 1 based index instead of '%s'", part);
goto out_syntax;
}
if (!strcmp(part, "auto")) {
dv = new DirectVolume(vm, label, mount_point, -1);
} else {
dv = new DirectVolume(vm, label, mount_point, atoi(part));
}
while ((sysfs_path = strtok_r(NULL, delim, &save_ptr))) {
if (*sysfs_path != '/') {
/* If the first character is not a '/', it must be flags */
break;
}
if (dv->addPath(sysfs_path)) {
SLOGE("Failed to add devpath %s to volume %s", sysfs_path,
label);
goto out_fail;
}
}
/* If sysfs_path is non-null at this point, then it contains
* the optional flags for this volume
*/
if (sysfs_path)
flags = parse_mount_flags(sysfs_path);
else
flags = 0;
dv->setFlags(flags);
vm->addVolume(dv);
}
DirectVolume后面会讲到,执行mount 和unmount 都是它在做。
另外,有时后读取配置文件会有问题,这是因为它读取是通过指标下标递增的方式在读,如果有问题可以跟踪打印一下配置文件,看哪里需要修改。
vold 里面启动页面main做了些什么
main 主要是初始化socket 连接监听数据变化,在系统起来时第一时间启动,并且通过读取配置文件来识别usb口或者sdcard 的设备地址,来mount 或者unmount 。其它执行mount 、 unmount 或者删除节点等操作都是由上层或者framework 发送命令给main让其通知volumeManage 执行相应的操作。
由于会涉及SDCARD或者USB硬盘,其中调用的方法就不详细说明,这里只说出当插入SDCARD或者USB硬盘会走的流程。
主动挂载时,会走向DirectVolume类,调用DirectVolume::mountVol方法,代码如下:
代码加亮部分,蓝色部分,会循环整个设备节点系统目录位于(/dev/block/vold),然后调用红色部分代码,调用Volume的挂载方法。
这里,无论是SDCARD或者USB硬盘在主动挂载时,都会走DirectVolume。
手动挂载是由上层发Mount 命令,代码位于MountService里面的doMountVolume方法,具体如何实现我们先不深究,它这里通过发送socket(mount)命令到Vold 的CommandListener里面的CommandListener::VolumeCmd::runCommand方法进入代码这里:
这里执行挂载动作,看上面蓝色代码是为了系统第一次启动上层发送命令firstMount给CommandListener执行挂载USB硬盘的动作,红色代码即是核心要挂载的方法,调用的VolumeManage的mountVolume 方法,只需传入挂载点。该方法代码是:
可以看出,这里同样调用的是Volume的mountVol方法,殊途同归,接下来着重看一下Volume类里面这个mountVol方法,究竟干了些啥。
别的先不管,来看一下代码
注意:原生的代码可能跟上面贴出来的代码有点不同,上面的代码是增加了Ntfs-3g挂载的支持和多分区挂载的支持,但基本流程是相同的。
代码有详细的注释,这里要注意的是:sdcard和USB的支持不同,sdcard 挂载时需要先挂载到临时目录/mnt/secure/staging,然后再移动到最终需要挂载的挂载点,而USB硬盘特别是多分区的支持,不用先挂载到临时目录,而是可以支持挂载到想要挂载的挂载点,这里是比较需要注意到的地方(在这里栽过跟头,会出现“随机性的挂载失败”)。
ok.
前一篇讲到SDCard unmout onEvent 发送socket 到框架层,接下来分析框架层得到数据后的流程。
MoutService
当android 系统启动时,system将MountService 添加到启动服务里面,而MountService 会开启一个线程来运行NativeDaemonConnector,由它来监听vold的消息,代码:
该函数运行在MountService的构造函数里面,而NativeDaemonConnector 本身就是继承自Runnable。
NativeDaemonConnector
Framework与vold 的通信是通过socket来实现的,不过该socket 由 android做了一个封装,LocalSocket 实现的socket功能。
NativeDaecomConnector 位于framework/base/service/java/com/android/server目录下, 监听vold 的消息代码在继承自Runnable对象的run方法里面 :
NativeDaemonConnector 类实例化了一个LocalSocket来与vold 通信。LocalSocket 里面有一个类LocalSocketImpl,该类部分是通过JNI实现的。
关于socket 内部如何通信,这个不是我们所关心的内容,因为如果要深入进去估计没完没了,有兴趣的朋友可以参考源码进入SocketListener查看:
建立连接
SocketListener::SocketListener
当main初始化CommandListener 后,会为socketName 传入一个叫vold 的字符串
SocketListener::startListener
再回过头看NativeDaemonConnector 的listenToSocket,代码中实例化了一个LocalSocketAddress的实例,并传入一个叫"vold"字符串的socket 名称,这与CommandListener中继承了FrameworkListener时给的"vold"名称是一致的,两个socket名称一致则可以互相进行通讯了,代码如下:
上面代码,通过socket 并event 解析出来,并通handler 发送到handleMessage 中,当handleMessage接收到传过来的消息时,会调用MountService 的onEvent 方法将code和event和sdcard 的状态传递进去。代码如下:
又回到MountService ,在onEvent里面当接收到的code ==VoldResponseCode.VolumeBadRemoval时会调用updatePublicVolumeState,发送unmount改变的广播,代码如下:
到这里,进入updatePublicVolumeState看该函数里的主要代码:
并且调用sendStorageIntent 方法将SDCard的Action:android.intent.action.MEDIA_BAD_REMOVAL 和dat:file:///mnt/sdcard 通过这个广播发送出去,代码如下:
再回到 updatePublicVolumeState ,调用了stateChange 后,将状态为632的标识发送到NativeDaemonConnector 的handlemessage,当NativeDaemonConnector 发现SDCard的状态发送改变时,比如unmount 的时候,从632(VolumeBadRemoval)变到605(VolumeStateChange)到onEvent,当onEvent再次得到请求时,进入判断会直接执行notifyVolumeStateChange 函数,代码如下:
notifyStateChange 会调用updatePublicVolumeState通知packageManger SDCard己经unmount.
再回到Vold
由于vold 启动文件一开始就启动了CommandListener的runcommand由于socket 一直在通讯,当发现值改变后,进入以下代码runCommand 方法里面:
这时调用VolumeManage的unmoutVolume。该方法来源于Volume 的unmountVol,调用这个函数会unmount 三个挂载点,并同时调用setState通知框架unmount 成功,可以改变UI等一系列动作。
MountService: 实现用于管理存储设备的后台服务
StorageManage:访问MountService 接口,并向应用层提供接口
PackageMangeService:是用于管理系统中所有apk,当SDCard发生变化时,向应用层发送消息
NativeDaemonConnector:创建socket实现mountservice 和vold 的通信
可以这么说:当vold 捕获到uevent 事件,会将事件消息通知framework,framework 进行判断,然后再下发执行命令。
最后附一张比较粗糙的结构图,时间较急,没仔细画好