zoukankan      html  css  js  c++  java
  • 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 服务的代码,如下:

    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 都是它在做。

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

     上一篇关于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) ", 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 :( ", 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 ", 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 ", 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,  falsefalse100010150702true)) {
                    SLOGE( " %s failed to mount via VFAT (%s) ", devicePath, strerror(errno));
                    
                    isFatFs =  false;
                }
                isNtfsFS =  false;
            }
            
             if ( isNtfsFS ) {
                 if (Ntfs::doMount(devicePath, mountPath,  true)) {
                    SLOGE( " %s failed to mount via NTFS (%s) ", 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. 

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

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


  • 相关阅读:
    灵魂有香气的女子IOS版本APP,近期将考虑开放源代码
    PHP中$_SERVER获取当前页面的完整URL地址
    zabbix监控报错zabbix server is not running解决方法
    Linux重启inotify配置max_user_watches无效被恢复默认值8192的正确修改方法
    centos在yum install报错:Another app is currently holding the yum lock解决方法
    nginx去掉单个目录和多个目录PHP执行权限方法
    express搭建权限管理系统
    在express项目中使用formidable & multiparty实现文件上传
    vue生成图片验证码
    第0步:OracleRAC软件准备
  • 原文地址:https://www.cnblogs.com/jiangu66/p/3239052.html
Copyright © 2011-2022 走看看