Android(安卓)SDCard Mount 流程分析
前段时间对Android 的SDCard unmount 流程进行了几篇简短的分析,由于当时只是纸上谈兵,没有实际上的跟进,可能会有一些误导人或者小错误。今天重新梳理了头绪,针对mount的流程再重新分析一次。
本篇大纲
- android 系统如何开机启动监听mount服务
- 默认设备节点在Android 系统的哪个目录
- vold.fstab 配置文件的分析
- vold 里面启动页面main做了些什么
android 系统如何开机启动监听mount服务
android sdcard 热插拔监测和执行操作是由一个启动文件vold 所统领的,系统开机会读取初始化配置文件init.rc,该文件位于比如我的板子是:device/ti/omap3evm/init.rc,具体根据自己平台查找。里面有一个是默认启动vold 服务的代码,如下:
servicevold/system/bin/voldsocketvoldstream0660rootmount
iopriobe2
如果要对该文件做出修改之类,要重新编一下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:0179:18:08:18:28:38:4
节点的小介绍:
0代表当前的整个设备,1代码当前设备的分区名称代号。
所以你会发现,sdcard只有一个分区它却生成了两个如:179:0 179:1
而usbdisk 有四个分区,它会生成五个设备节点:8:08:18:28:38:4 就是这个原因。
vold.fstab 配置文件的分析
vold 里面会通过指定文件来读取预先配置好的sdcard或者多分区配置文件,该文件位于
/system/core/rootdir/etc/vold.fstab如以下的配置文件为:
dev_mountsdcard/mnt/sdcardauto/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_mountsdcardexternal/mnt/sdcardauto/devices/platform/mmci-omap-hs.0/mmc_host/mmc0/devices/platform/mmci-omap-hs.0/mmc_host/mmc1
dev_mountusb1external/mnt/usbdisk/usb1-disk%dall/devices/platform/ehci-omap.0/usb1/1-2/1-2.1/
dev_mountusb2external/mnt/usbdisk/usb2-disk%dall/devices/platform/ehci-omap.0/usb1/1-2/1-2.2/
dev_mountusb3external/mnt/usbdisk/usb3-disk%dall/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( " Errorparsingpartition ");
gotoout_syntax;
}
if(strcmp(part, " auto ")&&atoi(part)== 0){
SLOGE( " Partitionmusteitherbe'auto'or1basedindexinsteadof'%s' ",part);
gotoout_syntax;
}
if(!strcmp(part,"auto")){
dv=newDirectVolume(vm,label,mount_point,-1);
}else{
dv=newDirectVolume(vm,label,mount_point,atoi(part));
}
while((sysfs_path=strtok_r(NULL,delim,&save_ptr))){
if(*sysfs_path!= ' / '){
/* Ifthefirstcharacterisnota'/',itmustbeflags */
break;
}
if(dv->addPath(sysfs_path)){
SLOGE( " Failedtoadddevpath%stovolume%s ",sysfs_path,
label);
gotoout_fail;
}
}
/* Ifsysfs_pathisnon-nullatthispoint,thenitcontains
*theoptionalflagsforthisvolume
*/
if(sysfs_path)
flags=parse_mount_flags(sysfs_path);
else
flags= 0; dv->setFlags(flags);
vm->addVolume(dv); }
DirectVolume后面会讲到,执行mount 和unmount 都是它在做。
另外,有时后读取配置文件会有问题,这是因为它读取是通过指标下标递增的方式在读,如果有问题可以跟踪打印一下配置文件,看哪里需要修改。
上一篇关于Mount的分析,分析了main的作用和一些挂载系统的分析。下面深入分析Mount的流程走法。
Mount流程分为两个部分
- 主动挂载(插入SDCARD或者USB硬盘时系统自动挂载)
- 手动挂载(卸载SDCARD或者USB硬盘后,再点击加载设备的手动挂载)
由于会涉及SDCARD或者USB硬盘,其中调用的方法就不详细说明,这里只说出当插入SDCARD或者USB硬盘会走的流程。
主动挂载
主动挂载时,会走向DirectVolume类,调用DirectVolume::mountVol方法,代码如下:
intDirectVolume::mountVol(){
charerrmsg[ 255];
dev_tdeviceNodes[ 64];
inti,n= 0;
if(getState()==Volume::State_NoMedia){
snprintf(errmsg, sizeof(errmsg),
" Volume%s%smountfailed-nomedia ",
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( " Failedtogetdevicenodes(%s)\n ",strerror(errno));
return- 1;
}
boolmounted= false;
for(i=0;i<n;i++){
mDevNodeIndex=deviceNodes[i];
//XXX:hackmountpoint
if(mMountpointParsed){free(mMountpointParsed);mMountpointParsed=NULL;}
mMountpointParsed=getParsedMountPoint(mMountpoint,i);
if(isMountpointMounted(getMountpoint())){
SLOGW("Volumeisidlebutappearstobemounted-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){
// atleastonpartitionhasbeenmountedsuccessful,markdiskasmounted
setState(Volume::State_Mounted);
return 0;
}
SLOGE( " Volume%sfoundnosuitabledevicesformounting:(\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:volumemount<path> ", false);
return 0;
}
if(!strcmp(argv[2],"firstMount")){
VolumeCollection::iteratori;
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方法,只需传入挂载点。该方法代码是:
intVolumeManager::mountVolume( const char*label){Volume*v=lookupVolume(label);
if(!v){
errno=ENOENT;
return- 1;
}
returnv->mountVol(); }
可以看出,这里同样调用的是Volume的mountVol方法,殊途同归,接下来着重看一下Volume类里面这个mountVol方法,究竟干了些啥。
Volume::mountVol方法深究
别的先不管,来看一下代码
intVolume::mountVol(){
intrc= 0;
charerrmsg[ 255];
const char*mountPath;
chardevicePath[ 255];
sprintf(devicePath, " /dev/block/vold/%d:%d ",MAJOR(mDevNodeIndex),
MINOR(mDevNodeIndex));//得到设备节点,如:/dev/block/vold/8:1
SLOGI( " %sbeingconsideredforvolume%s...major:%dminor:%d\n ",devicePath,getLabel(),
MAJOR(mDevNodeIndex),MINOR(mDevNodeIndex));
errno= 0;
setState(Volume::State_Checking);//设置状态为checking整型为3
// TODO:findawaytoreadthefilesystemID
boolisFatFs= true;
boolisNtfsFS= true;
//检查设备格式是否为Fat32
if(Fat::check(devicePath)){
if(errno==ENODATA){
SLOGW( " %sdoesnotcontainaFATfilesystem\n ",devicePath);
isFatFs= false;
} else{
errno=EIO;
/* Badness-abortthemount */
SLOGE( " %sfailedFSchecks(%s) ",devicePath,strerror(errno));
setState(Volume::State_Idle);
return- 1;
}
}
//创建挂载目录
// createmountpoint
if(mkdir(getMountpoint(), 0755)){
if(errno!=EEXIST){
SLOGE( " Failedtocreatemountpoint%s(%s) ",getMountpoint(),strerror(errno));
return- 1;
}
}
/*
*Mountthedeviceonourinternalstagingmountpointsowecan
*muckwithitbeforeexposingittononpriviledgedusers.
*/
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( " %sfailedtomountviaVFAT(%s)\n ",devicePath,strerror(errno));
isFatFs= false;
}
isNtfsFS= false;
}
if(isNtfsFS){
if(Ntfs::doMount(devicePath,mountPath, true)){
SLOGE( " %sfailedtomountviaNTFS(%s)\n ",devicePath,strerror(errno));
isNtfsFS= false;
}
}
if(!isFatFs&&!isNtfsFS){
// unsupportedfilesystem
return- 1;
}
SLOGI( " Device%s,target%smounted@/mnt/secure/staging ",devicePath,getMountpoint());
if(!strcmp(getLabel(), " sdcard ")){
protectFromAutorunStupidity();
if(createBindMounts()){
SLOGE( " Failedtocreatebindmounts(%s) ",strerror(errno));
umount( " /mnt/secure/staging ");
setState(Volume::State_Idle);
return- 1;
}
}
/*
*Nowthatthebindmounttrickeryisdone,atomicallymovethe
*wholesubtreetoexposeittononpriviledgedusers.
* 如果为sdcard则将/mnt/secure/staging 目录移动到挂载点,并将该目录unmount
*/
if(!strcmp(getLabel(), " sdcard ")){
if(doMoveMount( " /mnt/secure/staging ",getMountpoint(), false)){
SLOGE( " Failedtomovemount(%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.
vold 里面启动页面main做了些什么
main 主要是初始化socket 连接监听数据变化,在系统起来时第一时间启动,并且通过读取配置文件来识别usb口或者sdcard 的设备地址,来mount 或者unmount 。其它执行mount 、 unmount 或者删除节点等操作都是由上层或者framework 发送命令给main让其通知volumeManage 执行相应的操作。
更多相关文章
- 关于android studio开发APP中,给单个Activity设置隐藏上面标题栏
- Android内核的简单分析
- Android与js交互实例
- 【Android(安卓)开发】: Android(安卓)消息处理机制之三: Handle
- android java代码的启动:app_process
- Android内核的简单分析
- Android(安卓)内核简单分析
- 获取Android(安卓)SDK 源代码并在Eclipse中关联查看的方法
- Android(安卓)Studio的Gradle文件方法说明