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 服务的代码,如下:
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里面最优先执行:
;
可以根据这个目录找到如下节点:
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或者多分区配置文件,该文件位于
如以下的配置文件为:
dev_mount 代表挂载格式
sdcard 代表挂载的标签
/mnt/sdcard 代表挂载点
auto 为自定义选项可以为任何,但必须在main 里面自己判断比如这里的意思为自动挂载
后面两个目录为设备路径,第一个如果被占用会选择第二个
配置文件可以根据自己的需要编写,并不是固定的,但最好遵循google vold 启动文件代码的格式编写,要不然会给我们修改代码或者增加多分区功能带来不小的麻烦,如以下我自己编写的多分区挂载支持vold.fstab 配置文件:
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函数。看代码,里面正有一段用来读取配置文件:
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;
 
  DirectVolume后面会讲到,执行mount 和unmount 都是它在做。
另外,有时后读取配置文件会有问题,这是因为它读取是通过指标下标递增的方式在读,如果有问题可以跟踪打印一下配置文件,看哪里需要修改。
上一篇关于Mount的分析,分析了main的作用和一些挂载系统的分析。下面深入分析Mount的流程走法。
Mount流程分为两个部分
- 主动挂载(插入SDCARD或者USB硬盘时系统自动挂载)
- 手动挂载(卸载SDCARD或者USB硬盘后,再点击加载设备的手动挂载)
由于会涉及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 流程分析的更多相关文章
- 【转】linux文件系统之mount流程分析
		本质上,Ext3 mount的过程实际上是inode被替代的过程. 例如,/dev/sdb块设备被mount到/mnt/alan目录.命令:mount -t ext3 /dev/sdb /mnt/al ... 
- Linux文件系统之Mount流程分析
		转载:原文地址http://www.linuxeye.com/linuxrumen/1121.html 本质上,Ext3 mount的过程实际上是inode被替代的过程.例如,/dev/sdb块设备被 ... 
- Android 呼吸灯流程分析
		一.Android呼吸灯Driver实现 1.注册驱动 代码位置:mediatek/kernel/drivers/leds/leds_drv.c 602static struct platform_d ... 
- android Camera 数据流程分析
		这篇文章主要针对其数据流程进行分析.Camera一般用于图像浏览.拍照和视频录制.这里先对图像浏览和拍照的数据流进行分析,后面再对视频电话部分进行分析. 1.针对HAL层对摄像头数据处理补充一下 Li ... 
- android PakageManagerService启动流程分析
		PakageManagerService的启动流程图 1.PakageManagerService概述 PakageManagerService是android系统中一个核心的服务,它负责系统中Pac ... 
- android添加账户流程分析涉及漏洞修复
		android修复了添加账户代码中的2处bug,retme取了很酷炫的名字launchAnyWhere.broadAnywhere(参考资料1.2).本文顺着前辈的思路学习bug的原理和利用思路. 我 ... 
- Android WiFi 扫描流程分析(wpa_supplicant选择网络)
		扫描流程 1.如果之前就已经有相关记录,优化扫描,扫描记录部分的频率信道. 2.如果1中的扫描没有结果,清除黑名单中的进行选择. 3.如果2中没有结果,进行所有频率的信道进行扫描 相关log参考: h ... 
- [旧][Android] ButterKnifeProcessor 工作流程分析
		备注 原发表于2016.05.21,资料已过时,仅作备份,谨慎参考 前言 在 [Android] ButterKnife 浅析 中,我们了解了 ButterKnife 的用法,比较简单. 本次文章我们 ... 
- Android WiFi 扫描流程分析(wpa_supplicant)
		void wpa_supplicant_req_scan(struct wpa_supplicant *wpa_s, int sec, int usec) { int res; if (wpa_s-& ... 
随机推荐
- Android:What is ART?
			背景:Android4.2之前,安卓手机系统的应用程序均在Dalvik Java的虚拟机上执行,这样的执行模式还要依靠一个编译器来实现与应用程序的沟通.应用程序每次执行时,都须要将程序内的代码转变为机 ... 
- 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 ... 
- uva 10192 Vacation(最长公共子)
			uva 10192 Vacation The Problem You are planning to take some rest and to go out on vacation, but you ... 
- C#实现远程机器管理
			原文:C#实现远程机器管理 目前处于待离职状态,原先所有的工作都在进行交接,过程当中不乏有很多先前整理的和动手尝试实现的功能:我的主页中已经列出来一部分内容,有兴趣的可以前往看一看. 接下来的内容主要 ... 
- 【原创】《算法导论》链表一章带星习题试解——附C语言实现
			原题: 双向链表中,需要三个基本数据,一个携带具体数据,一个携带指向上一环节的prev指针,一个携带指向下一环节的next指针.请改写双向链表,仅用一个指针np实现双向链表的功能.定义np为next ... 
- 网页信息抓取进阶 支持Js生成数据  Jsoup的不足之处
			转载请标明出处:http://blog.csdn.net/lmj623565791/article/details/23866427 今天又遇到一个网页数据抓取的任务,给大家分享下. 说道网页信息抓取 ... 
- Leetcode:minimum_depth_of_binary_tree解决问题的方法
			一. 称号 并寻求最深的二元相似.给定的二进制树.求其最小深度. 最小深度是沿从根节点,到叶节点最短的路径. 二. 分析 当我看到这个题目时.我直接将最深二叉树的代码略微改了下,把ma ... 
- ZOJ3827 ACM-ICPC 2014 亚洲区域赛的比赛现场牡丹江I称号 Information Entropy 水的问题
			Information Entropy Time Limit: 2 Seconds Memory Limit: 131072 KB Special Judge Informatio ... 
- Android 角色时间戳
			我是在用MediaRecorder进行录像时发生视频和音频不同步的问题,请教了一些人后感觉应该是没有时间戳,之前一直觉得时间戳就是给用户看的一个数据,查了一下发现不是的,以下是转载的.希望对大家实用: ... 
- WTL安装
			1.在AppWiz文件夹下有多个JScript文件,依据自己的编辑器选择对应的文件执行. 假设双击无法执行的话,则执行wscript //e:jscript (文件路径) 如wscript //e:j ... 
