前段时间对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)\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.

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 扫描流程分析(wpa_supplicant选择网络)

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

  8. [旧][Android] ButterKnifeProcessor 工作流程分析

    备注 原发表于2016.05.21,资料已过时,仅作备份,谨慎参考 前言 在 [Android] ButterKnife 浅析 中,我们了解了 ButterKnife 的用法,比较简单. 本次文章我们 ...

  9. Android WiFi 扫描流程分析(wpa_supplicant)

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

随机推荐

  1. Android:What is ART?

    背景:Android4.2之前,安卓手机系统的应用程序均在Dalvik Java的虚拟机上执行,这样的执行模式还要依靠一个编译器来实现与应用程序的沟通.应用程序每次执行时,都须要将程序内的代码转变为机 ...

  2. uva-442 Matrix Chain Multiplication

    Suppose you have to evaluate an expression like A*B*C*D*E where A,B,C,D and E are matrices. Since ma ...

  3. uva 10192 Vacation(最长公共子)

    uva 10192 Vacation The Problem You are planning to take some rest and to go out on vacation, but you ...

  4. C#实现远程机器管理

    原文:C#实现远程机器管理 目前处于待离职状态,原先所有的工作都在进行交接,过程当中不乏有很多先前整理的和动手尝试实现的功能:我的主页中已经列出来一部分内容,有兴趣的可以前往看一看. 接下来的内容主要 ...

  5. 【原创】《算法导论》链表一章带星习题试解——附C语言实现

    原题: 双向链表中,需要三个基本数据,一个携带具体数据,一个携带指向上一环节的prev指针,一个携带指向下一环节的next指针.请改写双向链表,仅用一个指针np实现双向链表的功能.定义np为next ...

  6. 网页信息抓取进阶 支持Js生成数据 Jsoup的不足之处

    转载请标明出处:http://blog.csdn.net/lmj623565791/article/details/23866427 今天又遇到一个网页数据抓取的任务,给大家分享下. 说道网页信息抓取 ...

  7. Leetcode:minimum_depth_of_binary_tree解决问题的方法

    一.     称号 并寻求最深的二元相似.给定的二进制树.求其最小深度. 最小深度是沿从根节点,到叶节点最短的路径. 二.     分析 当我看到这个题目时.我直接将最深二叉树的代码略微改了下,把ma ...

  8. ZOJ3827 ACM-ICPC 2014 亚洲区域赛的比赛现场牡丹江I称号 Information Entropy 水的问题

    Information Entropy Time Limit: 2 Seconds      Memory Limit: 131072 KB      Special Judge Informatio ...

  9. Android 角色时间戳

    我是在用MediaRecorder进行录像时发生视频和音频不同步的问题,请教了一些人后感觉应该是没有时间戳,之前一直觉得时间戳就是给用户看的一个数据,查了一下发现不是的,以下是转载的.希望对大家实用: ...

  10. WTL安装

    1.在AppWiz文件夹下有多个JScript文件,依据自己的编辑器选择对应的文件执行. 假设双击无法执行的话,则执行wscript //e:jscript (文件路径) 如wscript //e:j ...