Android SDCard Mount 流程分析

时间:2022-10-26 12:11:27

前段时间对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
;
    }

在这个函数里面会根据读取到的数据存放起来,然后满足条件时执行操作。比如代码里面的:

Android SDCard Mount 流程分析

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);
        }
Android SDCard Mount 流程分析

DirectVolume后面会讲到,执行mount 和unmount 都是它在做。

另外,有时后读取配置文件会有问题,这是因为它读取是通过指标下标递增的方式在读,如果有问题可以跟踪打印一下配置文件,看哪里需要修改。

上一篇关于Mount的分析,分析了main的作用和一些挂载系统的分析。下面深入分析Mount的流程走法。

Mount流程分为两个部分

  • 主动挂载(插入SDCARD或者USB硬盘时系统自动挂载)
  • 手动挂载(卸载SDCARD或者USB硬盘后,再点击加载设备的手动挂载)
不同挂载走的流程并不相同,比如手动挂载是由上层发命令给vold 执行挂动作,而主动挂载是由kernel 分命令给vold 再由vold 发挂载消息给上层,上层得到挂载消息和状态后再发命令给vold 执行挂载。主动挂载较之复杂些。不过虽然流程不一样,但最终还是要调用Volume的挂载函数,下面将详细介绍两者的行走的流程。

由于会涉及SDCARD或者USB硬盘,其中调用的方法就不详细说明,这里只说出当插入SDCARD或者USB硬盘会走的流程。

主动挂载

主动挂载时,会走向DirectVolume类,调用DirectVolume::mountVol方法,代码如下:

Android SDCard Mount 流程分析

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;

}
Android SDCard Mount 流程分析

代码加亮部分,蓝色部分,会循环整个设备节点系统目录位于(/dev/block/vold),然后调用红色部分代码,调用Volume的挂载方法。

这里,无论是SDCARD或者USB硬盘在主动挂载时,都会走DirectVolume。

手动挂载

手动挂载是由上层发Mount 命令,代码位于MountService里面的doMountVolume方法,具体如何实现我们先不深究,它这里通过发送socket(mount)命令到Vold 的CommandListener里面的CommandListener::VolumeCmd::runCommand方法进入代码这里:

Android SDCard Mount 流程分析

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]);
        }

            
    } 
Android SDCard Mount 流程分析

这里执行挂载动作,看上面蓝色代码是为了系统第一次启动上层发送命令firstMount给CommandListener执行挂载USB硬盘的动作,红色代码即是核心要挂载的方法,调用的VolumeManage的mountVolume 方法,只需传入挂载点。该方法代码是:

Android SDCard Mount 流程分析

int VolumeManager::mountVolume(
const 
char *label) {

    Volume *v = lookupVolume(label);

if (!v) {

        errno = ENOENT;

        
return -
1;

    }

return v->mountVol();

}
Android SDCard Mount 流程分析

可以看出,这里同样调用的是Volume的mountVol方法,殊途同归,接下来着重看一下Volume类里面这个mountVol方法,究竟干了些啥。

Volume::mountVol 方法深究

别的先不管,来看一下代码

Android SDCard Mount 流程分析

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;

    
}

Android SDCard Mount 流程分析

注意:原生的代码可能跟上面贴出来的代码有点不同,上面的代码是增加了Ntfs-3g挂载的支持和多分区挂载的支持,但基本流程是相同的。

代码有详细的注释,这里要注意的是:sdcard和USB的支持不同,sdcard 挂载时需要先挂载到临时目录/mnt/secure/staging,然后再移动到最终需要挂载的挂载点,而USB硬盘特别是多分区的支持,不用先挂载到临时目录,而是可以支持挂载到想要挂载的挂载点,这里是比较需要注意到的地方(在这里栽过跟头,会出现“随机性的挂载失败”)。

ok.

vold 里面启动页面main做了些什么

main 主要是初始化socket 连接监听数据变化,在系统起来时第一时间启动,并且通过读取配置文件来识别usb口或者sdcard 的设备地址,来mount 或者unmount 。其它执行mount 、 unmount  或者删除节点等操作都是由上层或者framework 发送命令给main让其通知volumeManage 执行相应的操作。

Android SDCard Mount 流程分析的更多相关文章

  1. 【转】linux文件系统之mount流程分析

    本质上,Ext3 mount的过程实际上是inode被替代的过程. 例如,/dev/sdb块设备被mount到/mnt/alan目录.命令:mount -t ext3 /dev/sdb /mnt/al ...

  2. Linux文件系统之Mount流程分析

    转载:原文地址http://www.linuxeye.com/linuxrumen/1121.html 本质上,Ext3 mount的过程实际上是inode被替代的过程.例如,/dev/sdb块设备被 ...

  3. Android 呼吸灯流程分析

    一.Android呼吸灯Driver实现 1.注册驱动 代码位置:mediatek/kernel/drivers/leds/leds_drv.c 602static struct platform_d ...

  4. android Camera 数据流程分析

    这篇文章主要针对其数据流程进行分析.Camera一般用于图像浏览.拍照和视频录制.这里先对图像浏览和拍照的数据流进行分析,后面再对视频电话部分进行分析. 1.针对HAL层对摄像头数据处理补充一下 Li ...

  5. android PakageManagerService启动流程分析

    PakageManagerService的启动流程图 1.PakageManagerService概述 PakageManagerService是android系统中一个核心的服务,它负责系统中Pac ...

  6. android添加账户流程分析涉及漏洞修复

    android修复了添加账户代码中的2处bug,retme取了很酷炫的名字launchAnyWhere.broadAnywhere(参考资料1.2).本文顺着前辈的思路学习bug的原理和利用思路. 我 ...

  7. Android WiFi 扫描流程分析&lpar;wpa&lowbar;supplicant选择网络&rpar;

    扫描流程 1.如果之前就已经有相关记录,优化扫描,扫描记录部分的频率信道. 2.如果1中的扫描没有结果,清除黑名单中的进行选择. 3.如果2中没有结果,进行所有频率的信道进行扫描 相关log参考: h ...

  8. Android WiFi 扫描流程分析&lpar;wpa&lowbar;supplicant&rpar;

    void wpa_supplicant_req_scan(struct wpa_supplicant *wpa_s, int sec, int usec) { int res; if (wpa_s-& ...

  9. Android FART脱壳机流程分析

    本文首发于安全客 链接:https://www.anquanke.com/post/id/219094 0x1 前言 在Android平台上,程序员编写的Java代码最终将被编译成字节码在Androi ...

随机推荐

  1. 【WPF】WPF中的List&lt&semi;T&gt&semi;和ObservableCollection&lt&semi;T&gt&semi;

    在WPF中 控件绑定数据源时,数据源建议采用 ObservableCollection<T>集合 ObservableCollection<T> 类:表示一个动态数据集合,在添 ...

  2. Serialize Documents with the C&num; Driver

    1.介绍 该文档是1.8版本的C#驱动. 本节教程讨论C#类和BSON文档之间的序列化和反序列化.序列化是将对象映射成一个可以存储在MongoDB中的BSON文档的过程,反序列化是从一个BSON文档重 ...

  3. java&lowbar;常用数据类型转换基础篇

    一.java基本数据类型 1.java基本数据类型可分四类八中 第一类:整形:byte.short.int.long 第二类:浮点型:float(单精度) .double(双精度) 第三类:逻辑类型: ...

  4. 仿酷狗音乐播放器开发日志二十三 修复Option控件显示状态不全的bug(附源码)

    转载请说明原出处,谢谢~~ 整个仿酷狗工程的开发将近尾声,现在还差选项设置窗体的部分,显然在设置窗体里用的最多的就是OptionUI控件,我在写好大致的布局后去测试效果,发现Option控件的显示效果 ...

  5. css3 回到顶部书写

    回到顶部 JS 代码  backTop = function(){  if(!document.querySelector("#backTop")){return;}        ...

  6. http&colon;&sol;&sol;www&period;ruanyifeng&period;com&sol;blog&sol;2011&sol;09&sol;restful

    http://www.ruanyifeng.com/blog/2011/09/restful

  7. 注意android裁图的Intent action

    现在很多开发者在裁图的时候还是使用com.android.camera.action.CROP 来调用 startActivity(). 这不是个好主意. 任何不是依android开头的Action ...

  8. CentOS 修改默认语言

    查看所有的locale语言 [root@centos6 ~]# locale -a ... ... ... ... xh_ZA xh_ZA.iso88591 xh_ZA.utf8 yi_US yi_U ...

  9. Java程序猿之从菜鸟到职场高手的必看

    J2SE之入门引导            Java基础系列之初识JAVA                                           Java基础系列之Java语法       ...

  10. go实例之排序

    1.默认排序 使用sort包进行排序.排序是就地排序,因此它会更改给定的切片,并且不返回新的切片. package main import "fmt" import "s ...