上一篇关于Mount的分析,分析了main的作用和一些挂载系统的分析。下面深入分析Mount的流程走法。
Mount流程分为两个部分
- 主动挂载(插入SDCARD或者USB硬盘时系统自动挂载)
- 手动挂载(卸载SDCARD或者USB硬盘后,再点击加载设备的手动挂载)
不同挂载走的流程并不相同,比如手动挂载是由上层发命令给vold 执行挂动作,而主动挂载是由kernel 分命令给vold 再由vold 发挂载消息给上层,上层得到挂载消息和状态后再发命令给vold 执行挂载。主动挂载较之复杂些。不过虽然流程不一样,但最终还是要调用Volume的挂载函数,下面将详细介绍两者的行走的流程。
由于会涉及SDCARD或者USB硬盘,其中调用的方法就不详细说明,这里只说出当插入SDCARD或者USB硬盘会走的流程。
主动挂载
主动挂载时,会走向DirectVolume类,调用DirectVolume::mountVol方法,代码如下:
int DirectVolume::mountVol() {
char errmsg[
255];
dev_t deviceNodes[
64];
int i, n =
0;
if (getState() == Volume::State_NoMedia) {
snprintf(errmsg,
sizeof(errmsg),
" Volume %s %s mount failed - no media ",
getLabel(), getMountpoint());
mVm->getBroadcaster()->sendBroadcast(
ResponseCode::VolumeMountFailedNoMedia,
errmsg,
false);
errno = ENODEV;
return -
1;
}
else if (getState() != Volume::State_Idle) {
errno = EBUSY;
return -
1;
}
n = getDeviceNodes((dev_t *) &deviceNodes,
64);
if (!n) {
SLOGE(
" Failed to get device nodes (%s)\n ", strerror(errno));
return -
1;
}
bool mounted =
false;
for (i = 0; i < n; i++) { mDevNodeIndex = deviceNodes[i]; //XXX: hack mountpoint if ( mMountpointParsed ) { free(mMountpointParsed); mMountpointParsed = NULL; } mMountpointParsed = getParsedMountPoint(mMountpoint, i); if (isMountpointMounted(getMountpoint())) { SLOGW("Volume is idle but appears to be mounted - fixing"); setState(Volume::State_Mounted); // mCurrentlyMountedKdev = XXX errno = EBUSY; continue; } if (!Volume::mountVol()) { mounted = true; } mState = Volume::State_Idle;
} if ( mMountpointParsed ) { free(mMountpointParsed); mMountpointParsed = NULL; }
if ( mounted ) {
// at least on partition has been mounted successful, mark disk as mounted setState(Volume::State_Mounted);
return 0;
}
SLOGE(
" Volume %s found no suitable devices for mounting :(\n ", getLabel());
setState(Volume::State_Idle);
return -
1;
}
代码加亮部分,蓝色部分,会循环整个设备节点系统目录位于(/dev/block/vold),然后调用红色部分代码,调用Volume的挂载方法。
这里,无论是SDCARD或者USB硬盘在主动挂载时,都会走DirectVolume。
手动挂载
手动挂载是由上层发Mount 命令,代码位于MountService里面的doMountVolume方法,具体如何实现我们先不深究,它这里通过发送socket(mount)命令到Vold 的CommandListener里面的CommandListener::VolumeCmd::runCommand方法进入代码这里:
else if (!strcmp(argv[
1],
" mount ")) {
if (argc !=
3) {
cli->sendMsg(ResponseCode::CommandSyntaxError,
" Usage: volume mount <path> ",
false);
return 0;
}
if (!strcmp(argv[2],"firstMount")){ VolumeCollection::iterator i; if(mVolumes!=NULL){ for (i = mVolumes->begin(); i != mVolumes->end(); ++i) { if (strcmp("/sdcard", (*i)->getMountpoint())) { vm->mountVolume((*i)->getMountpoint()); } } } }else{ vm->mountVolume(argv[2]); } }
这里执行挂载动作,看上面蓝色代码是为了系统第一次启动上层发送命令firstMount给CommandListener执行挂载USB硬盘的动作,红色代码即是核心要挂载的方法,调用的VolumeManage的mountVolume 方法,只需传入挂载点。该方法代码是:
int VolumeManager::mountVolume(
const char *label) {
Volume *v = lookupVolume(label);
if (!v) {
errno = ENOENT;
return -
1;
}
return v->mountVol();
}
可以看出,这里同样调用的是Volume的mountVol方法,殊途同归,接下来着重看一下Volume类里面这个mountVol方法,究竟干了些啥。
Volume::mountVol 方法深究
别的先不管,来看一下代码
int Volume::mountVol() { int rc =
0;
char errmsg[
255];
const char *mountPath;
char devicePath[
255];
sprintf(devicePath,
" /dev/block/vold/%d:%d ", MAJOR(mDevNodeIndex),
MINOR(mDevNodeIndex));//得到设备节点,如:/dev/block/vold/8:1
SLOGI(
" %s being considered for volume %s ...major : %d minor: %d\n ", devicePath, getLabel(),
MAJOR(mDevNodeIndex),MINOR(mDevNodeIndex));
errno =
0;
setState(Volume::State_Checking);//设置状态为checking整型为3
// TODO: find a way to read the filesystem ID bool isFatFs =
true;
bool isNtfsFS =
true;
//检查设备格式是否为Fat32
if (Fat::check(devicePath)) {
if (errno == ENODATA) {
SLOGW(
" %s does not contain a FAT filesystem\n ", devicePath);
isFatFs =
false;
}
else {
errno = EIO;
/* Badness - abort the mount */ SLOGE(
" %s failed FS checks (%s) ", devicePath, strerror(errno));
setState(Volume::State_Idle);
return -
1;
}
}
//创建挂载目录
// create mountpoint if (mkdir(getMountpoint(),
0755)) {
if (errno != EEXIST) {
SLOGE(
" Failed to create mountpoint %s (%s) ", getMountpoint(), strerror(errno));
return -
1;
}
}
/* * Mount the device on our internal staging mountpoint so we can * muck with it before exposing it to non priviledged users. */ errno =
0;
//如果为sdcard则挂载到
/mnt/secure/staging ,否则挂载到挂载点 if(!strcmp(getLabel(),
" sdcard "))
mountPath=
" /mnt/secure/staging ";
else mountPath=getMountpoint();
//接下来就是不同格式不同的挂载,这里支持两种格式:fat32,Ntfs
if ( isFatFs ) {
if (Fat::doMount(devicePath,mountPath,
false,
false,
1000,
1015,
0702,
true)) {
SLOGE(
" %s failed to mount via VFAT (%s)\n ", devicePath, strerror(errno));
isFatFs =
false;
}
isNtfsFS =
false;
}
if ( isNtfsFS ) {
if (Ntfs::doMount(devicePath, mountPath,
true)) {
SLOGE(
" %s failed to mount via NTFS (%s)\n ", devicePath, strerror(errno));
isNtfsFS =
false;
}
}
if ( !isFatFs && !isNtfsFS ) {
// unsupported filesystem return -
1;
}
SLOGI(
" Device %s, target %s mounted @ /mnt/secure/staging ", devicePath, getMountpoint());
if ( !strcmp(getLabel(),
" sdcard ") ) {
protectFromAutorunStupidity();
if (createBindMounts()) {
SLOGE(
" Failed to create bindmounts (%s) ", strerror(errno));
umount(
" /mnt/secure/staging ");
setState(Volume::State_Idle);
return -
1;
}
}
/* * Now that the bindmount trickery is done, atomically move the * whole subtree to expose it to non priviledged users. * 如果为sdcard则将/mnt/secure/staging 目录移动到挂载点,并将该目录unmount */ if(!strcmp(getLabel(),
" sdcard ")){
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(Volume::State_Mounted);//设置状态到MountService
mCurrentlyMountedKdev = mDevNodeIndex;
return 0;
}
注意:原生的代码可能跟上面贴出来的代码有点不同,上面的代码是增加了Ntfs-3g挂载的支持和多分区挂载的支持,但基本流程是相同的。
代码有详细的注释,这里要注意的是:sdcard和USB的支持不同,sdcard 挂载时需要先挂载到临时目录/mnt/secure/staging,然后再移动到最终需要挂载的挂载点,而USB硬盘特别是多分区的支持,不用先挂载到临时目录,而是可以支持挂载到想要挂载的挂载点,这里是比较需要注意到的地方(在这里栽过跟头,会出现“随机性的挂载失败”)。
ok.