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)\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,  falsefalse100010150702true)) {
                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 执行相应的操作。


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/301044.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

深圳dotnet俱乐部新群

qq群号:16708579加入时声明来自cnblogs希望大家加进来不只是灌水。 转载于:https://www.cnblogs.com/cfans/archive/2005/11/19/280132.html

6月统计|.NET薪资一旦高起来,岂是其他语言能比的!

全国程序员6月平均薪资出来了&#xff0c;帝都和魔都平均工资超过18k&#xff0c;你被平均了吗&#xff1f;而中位数17k&#xff0c;你拖后腿了吗&#xff1f;当然&#xff0c;.NET开发者的目标不是平均工资&#xff0c;而是double&#xff01;年初跳槽季&#xff0c;腾讯、阿里…

matlab画孔斯曲面,CAD CAM技术基础:第五讲 孔斯曲面

《CAD CAM技术基础&#xff1a;第五讲 孔斯曲面》由会员分享&#xff0c;可在线阅读&#xff0c;更多相关《CAD CAM技术基础&#xff1a;第五讲 孔斯曲面(33页珍藏版)》请在人人文库网上搜索。1、CAD/CAM技术基础,南京航空航天大学 2021年1月22日,2021年1月22日,CAD/CAM技术基础…

剑桥大学的下午茶,为何能喝出六十位诺贝尔奖获得者?

全世界只有3.14 %的人关注了青少年数学之旅英国人的骄傲是他们有世界上最古老的大学&#xff0c;创立于1209年的剑桥大学。英国剑桥大学每天下午有两个小时的时间&#xff0c;常常有计划、有组织地安排不同学科的权威教授一起在学校咖啡屋或茶园共进下午茶。在这里&#xff0c;…

阿贾克斯踵

AJAX 是一个很优秀的技术&#xff0c;不过也会产生些小问题。虽然可以通过 JavaScript 避免客户端页面刷新&#xff0c;可是客户端与服务器之间的交互时间总是不可省略的。这段时间的处理似乎就是个问题。 M$ 的作法是什么都不做。于是&#xff0c;在 MSN SPACES 上发表评论…

使用bat来运行cygwin,执行脚本(命令)

2019独角兽企业重金招聘Python工程师标准>>> 这是一个没有意义的问题。 既然要跑脚本。为啥不直接在linux服务器上运行。 这个不是个人能决定的。 一般我都将功能写成jar&#xff0c;或者py, 然后编写bat文件&#xff0c; 如果是java的话&#xff0c;“java -jar…

.NET 下载、文档访问新姿势

dot.net 新的重定向Intro不知道大家之前有没有注意过&#xff0c;如果你访问 https://dot.net 的时候会自动地重定向到 https://dotnet.microsoft.com&#xff0c;想要访问 https://dot.net/download 的时候还是会重定向到 https://dotnet.microsoft.com&#xff0c;不会带着…

excel保存成matlab,matlab数据保存为excel文件

读取&#xff1a;A xlsread(‘Excel路径Excel的名称‘&#xff0c;‘工作表名称‘)例子&#xff1a;A xlsread(‘创新班.xlsx‘,‘Sheet2‘)A xlsread(‘创新班.xlsx‘,‘Sheet2‘,‘a1:c1‘) %读取a1 b1 c1的数据不写工作表名称&#xff0c;默认是第一个。写入&#xff1a;…

客户想你死系列,哈哈哈设计师不容易啊! | 今日最佳

全世界只有3.14 % 的人关注了青少年数学之旅&#xff08;图源网络&#xff0c;侵权删&#xff09;学到了吗&#xff1f;↓ ↓ ↓

DropDownList 選項改變確認腳本

需要這個, 到網找了某先生的腳本改了改 staticpublicstringDropDownListSelectChangeConfirmScript(stringdropDownListId,stringmessage){ string script " <script> \n" " var fooIndex; \n" " function saveIndexes() \n&…

王炸!Azure云助力.NET6现高光时刻(VS2022实战尝鲜)

Azure 是一个云平台&#xff0c;旨在简化构建新式应用程序的过程。无论是选择完全在 Azure 中托管应用程序&#xff0c;还是使用 Azure 服务扩展本地应用程序&#xff0c;Azure 都可以帮助你创建可缩放且可维护的可靠应用程序。凭借你已在使用的工具&#xff08;如 Visual Stud…

求生之路:博士生涯的17条简单生存法则

全世界只有3.14 % 的人关注了青少年数学之旅Next Scientist 是一个专门帮助博士生获取并保持动力、实现毕业和指导在业界求职的网站。本文作者 Julio Peironcely 就是 Next Scientist 的创始人和编辑&#xff0c;曾在荷兰莱顿大学的 PhD 期间做代谢组学和代谢产物鉴定的研究。在…

如何让自己更自律

之前写过一篇如何克服拖延的文章《想得很好&#xff0c;做起来总是不行&#xff1f;》&#xff0c;这次聊得更主动一些&#xff0c;「如何让自己变得自律」&#xff0c;更积极主动地拥抱未来的自己。Sam Thomas Davies曾经精辟地概括过「自律」。他认为自律就是学会抵抗。不管你…

Linux集群服务知识点总结及通过案例介绍如何实现高性能web服务(三)

三&#xff1a;通过corosyncpacemaker实现web服务高可用&#xff0c;主要实现步骤&#xff1a;既然给web应用服务器做高可用&#xff0c;那么httpd进程就不能开机自动运行&#xff0c;而且当前httpd服务属于停止状态&#xff0c;有corosync来自动启动某台的httpd进程12[rootRea…

个人电脑的楷模:新款IMac G5

当苹果公司最近为iPod视频设备的发布大作宣传时&#xff0c;另一个重头产品被这一片喧嚣淹没了。在推出iPod的同一天&#xff0c;苹果公司也推出了它的主打消费台式电脑&#xff0c;这款电脑是目前已被证明是性能优越的iMac G5的最新升级版本&#xff0c;价格更加便宜。与此同时…

java hashmap实例,关于java中的HashMap的实例操作

HashMap简介&#xff1a;1、以(键&#xff0c;值)对存储数据。2、不允许有重复的键&#xff0c;但允许有重复的值。3、不同步(多个线程可以同时访问)相关视频教程推荐&#xff1a;java在线学习实例演示如下&#xff1a;1、添加HashMap hash_map new HashMap();hash_map.put( &…

WPF实现仪表盘(刻度跟随)

WPF开发者QQ群&#xff1a; 340500857 | 微信群 -> 进入公众号主页 加入组织“ 前言&#xff0c;接着上一篇圆形进度条。”欢迎转发、分享、点赞、在看&#xff0c;谢谢~。 01—效果预览效果预览&#xff08;更多效果请下载源码体验&#xff09;&#xff1a;02—代码如下一…

js 技巧杂引(转)

js 技巧杂引(转) posted on 2005年9月28日 1:12 由 Snow 事件源对象 event.srcElement.tagName event.srcElement.type 捕获释放 event.srcElement.setCapture(); event.srcElement.releaseCapture(); 事件按键 event.keyCode event.shiftKey event.altKey event.ctrlKey 事…

数学特级教师:数学除了做习题,我还他让他们看这十部纪录片!

全世界只有3.14 % 的人关注了青少年数学之旅今天我们要向大家强烈推荐一个分享数学知识、严选数学好物公众号“数学好物”。“数学好物”是一个致力为数学爱好者与家长&#xff0c;提供丰富的数学文化、数理思维知识、最新数学好物的公众号。就是他啦&#xff1a;长按二维码可以…

自适应布局

浮动 一列绝对定位,一列用margin撑开空间 margin负值:主体用一层包裹,浮动,内层用margin留出空间;其他列浮动,使用margin调整到空出的位置 1 <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN"2 "http://www.w3.org/TR/html4/strict.dtd">3 …