Android存储系统-MountService 和vold 对外置存储的管理(1)

现状介绍

        由于技术和成本的问题,早期Android系统一般配置有较小的内置存储,另外还提供存储卡的插槽作为主存储(外置存储)。 随着技术升级和内置存储成本的降低,大部分Android系统已经取消了存储卡的插槽,取而代之的是更大的内置存储。 但是很多程序已经习惯了使用外置存储,为了兼容老的程序和开发者的使用习惯,即使在没有存储卡插槽的手机上,我们也能看到/sdcard目录,主存储也是存在的。另外一方面Android也支持外接越来越多类型的设备来作为外置存储,所以Android需要对这些外置设备进行管理。 这就是vold 和MountService的基本作用:对外置存储进行管理。

         Android有个主存储(Primary)的概念,主存储作为主要的外置存储,是提供给应用程序使用的,一般通过Context的几个获取外置存储路径的api,基本上返回的都是主存储的磁盘路径。主存储对于Android手机是必须的,除非要求所有应用都别使用读写外置存储的api,这显然是不现实的。那对于目前没有外置存储的手机是怎么做到这点的呢? Android引入了一个模拟存储的概念,就是用内置存储的空间内来模拟外置存储。不但可以使用外置存储来模拟内置存储,Android同样提供了使用外置存储来模拟内置存储的能力,这样既可以通过外接设备来扩展外置存储空间,还可以通过外接设备来扩展内置存储。但是出于安全考虑,内置存储的数据需要进行加密。在Android 5.0开始Android默认开启内置存储的全盘加密,Android 7.0开始内置存储默认开启基于文件的加密。

         对于Android系统来说,存储的配置有三种:
         1 只有一个内置存储,使用内置存储来模拟主外置存储。
         2 有一个外置存储和内置存储,使用真实的外置存储设备作为主外置存储。
         3 使用内置存储来模拟主外置存储,支持其他外置存储设备。

         针对这三种配置感兴趣的读者可以阅读一下这篇文档。
         http://androidxref.com/6.0.1_r10/xref/docs/source.android.com/src/devices/storage/config-example.jd

         在Android 4.0开始支持多用户,对于不同用户应该做到存储空间隔离,这也是Android在设计存储管理系统时要考虑的。

         下面我们以一个真实的pixel举例来看下常见的设备配置(pixel属于上述的第三种配置)。

PIXEL举例

首先df命令看下盘的挂载情况

sailfish:/ # df
/dev/root          1999708  782936   1216772  40% /
tmpfs              1898544     436   1898108   1% /dev
tmpfs              1898544       0   1898544   0% /mnt
/dev/block/sda31    292868  224200     68668  77% /vendor
/dev/block/sda25     65488   57008      8480  88% /firmware/radio
/dev/block/sda35  25497924 1306056  24191868   6% /data
/dev/block/sdd3      28144     516     27628   2% /persist
/dev/fuse         25497924 1306056  24191868   6% /mnt/runtime/default/emulated
/dev/fuse         25497924 1306056  24191868   6% /mnt/runtime/read/emulated
/dev/fuse         25497924 1306056  24191868   6% /mnt/runtime/write/emulated
  • /dev/root 对应system分区
  • /dev/block/sda* 是一个设备,分了三个分区,分别挂载到目录/vendor, /data和 /firmware/radio。
  • /dev/block/sdd3 是一个很小的设备挂载到目录 /persist, 这个干啥的我也没有研究过。

        下面/dev/fuse 对应的几个目录,就是使用外置存储来模拟内置存储,这里主要用到了fuse文件系统,三个目录对应有不同权限的应用能看到的目录,有不同的读写权限,后面我会说明。

  • /dev/fuse 25497924 1306056 24191868 6% /mnt/runtime/default/emulated
  • /dev/fuse 25497924 1306056 24191868 6% /mnt/runtime/read/emulated
  • /dev/fuse 25497924 1306056 24191868 6% /mnt/runtime/write/emulated

        下面来看下pixel的fstab文件。它就是指导系统如何挂载磁盘的文件。

sailfish:/ # cat fstab.sailfish                                                                                                                                                                                                                                                   
\# Android fstab file.
\# The filesystem that contains the filesystem checker binary (typically /system) cannot
\# specify MF_CHECK, and must come before any filesystems that do specify MF_CHECK

\#TODO: Add 'check' as fs_mgr_flags with data partition.# Currently we dont have e2fsck compiled. So fs check would failed.

#<src>                                  <mnt_point>       <type>  <mnt_flags and options>                     <fs_mgr_flags>
/dev/block/bootdevice/by-name/system    /           ext4    ro,barrier=1,discard                                wait,slotselect,verify
/dev/block/bootdevice/by-name/vendor    /vendor     ext4    ro,barrier=1,discard                                wait,slotselect
/dev/block/bootdevice/by-name/modem     /firmware/radio    vfat    ro,shortname=lower,uid=1000,gid=0,dmask=227,fmask=337,context=u:object_r:firmware_file:s0   wait,slotselect
/dev/block/bootdevice/by-name/userdata    /data             ext4    nosuid,nodev,barrier=1,noauto_da_alloc      wait,check,formattable,fileencryption=ice
/dev/block/zram0                        none              swap    defaults                                            zramsize=536870912
/dev/block/bootdevice/by-name/misc        /misc           emmc  defaults defaults
/devices/*/xhci-hcd.0.auto/usb*           auto              vfat    defaults                                            voldmanaged=usb:auto
  • /dev/block/bootdevice/by-name/system 挂载到根目录,也就是/dev/root 这个设备。
  • /dev/block/bootdevice/by-name/vendor vendor挂载到/vender目录也就是/dev/block/sda31。
  • /dev/block/bootdevice/by-name/userdata 挂载到/data目录,也就是/dev/block/sda35。
  • /dev/block/zram0 这个是一个交换分区。
  • /dev/block/bootdevice/by-name/misc 挂载到misc目录,这应该是一个内置的emmc设备。

        前面这些都是内置的设备,或者虚拟的分区。

  • /devices/*/xhci-hcd.0.auto/usb* usb设备,属于外置设备,可以再开机后进行插拔,这一行设备了voldmanaged标志,说明该设备被vold进行管理, auto代表vold自动读取分区进行挂载。

        另外vold没有设置系统属性ro.vold.primary_physical 为1,表示使用内置存储/data分区来模拟主存储(后面代码分析中会看到为什么这样)。
        到这里相信读者对内置存储,外置存储和主存储有了基本的概念,以及知道了可以使用内置存储来模拟主存储。下面咱们再来看下Android系统对目录的管理。还是以pixel为例。

sailfish:/ # ls -ld /data/user/0                                                                                                                                                                                                                                                  
lrwxrwxrwx 1 root root 10 1970-03-15 16:41 /data/user/0 -> /data/data

sailfish:/ # ls -ld /sdcard                                                                                                                                                                                                                                                       
lrw-r--r-- 1 root root 21 2020-07-12 14:00 /sdcard -> /storage/self/primary

sailfish:/ # ls -ld /storage/self/primary
lrwxrwxrwx 1 root root 19 1970-05-08 02:11 /storage/self/primary -> /mnt/user/0/primary

sailfish:/ # ls -ld /mnt/user/0/primary                                                                                                                                                                                                                                           
lrwxrwxrwx 1 root root 19 1970-05-08 02:11 /mnt/user/0/primary -> /storage/emulated/0

        目前Android系统已经支持多用户,主用户的用户id为0,所以我们可以看到一般应用程序的内置数据目录为 /data/data/,这对单用户显然没什么问题,但是要支持多用户就应该为每个用户提供不同的数据存储位置。所以Android为每个用户创建一个 用户独有的数据目录,这就是 /data/user/${userid}, 目录,对于主用户,它指向 /data/data目录。

        为了兼容旧应用程序,还必须提供一个/sdcard存储目录,指向主外置存储( /storage/self/primary), 对于不同的用户/storage/self/primary又指向不同的主外置存储目录 /mnt/user/${uid}/primary, 由于pixel是使用内置存储模拟的外置存储,所以主存储的位置为 /storage/emulated/${uid}, 这里的emulated就是代表该目录是模拟的。

        后面我们分析vold和MountService就是要弄明白vold如何挂载外置存储,确定主存储分区,对多用户如何进行管理,以及对上层应用提供怎样的api来访问外置存储。

        关于上述目录的特性,可以参考Android存储系统-开篇 这篇文章,下面我们就从代码的角度来分析下MountService和vold如何来管理Android手机上的外置存储。

整体架构

vold/MountService系统架构
        如上图,整个架构分为kernel层,system层和framework层。

        kernel层主要发出udev的通知给vold,通知设备的热插拔事件,对于不支持热插拔的设备,udev支持写uevent文件,比如给/sys/block/sda/uevent文件写一个add字符串,内核就会通过netlink发出一个udev的add事件给用户空间的发送一个消息。用户空间就可以通过监听netlink的消息来处理磁盘设备的热插拔事件。

        vold进程是system层的代表,主要负责监听前面说的uevent事件。vold只关注块(磁盘)设备的事件,在vold启动之初,会创建一个模拟分区,路径为/data/media,也就是在userdata里面划出来一块空间做为模拟主设备的分区,如果需要使用模拟设备来作为主外置存储,就使用这个模拟分区来创建主存储。 之后向/sys/block下面的子目录的uevent文件写入add,冷启这些设备。vold只关心fstab文件中设置了 voldmanaged标志的设备,该标志表示该设备归vold管理,其他设备的挂载都通过init进程初始化阶段进行挂载。(参考 fstab.sailfish,/data, /system, /vender 这些内置存储都不需要vold来管理)。vold启动的时候来会读取fstab文件来确定自己要管理的磁盘,vold要管理的磁盘使用DiskSource数据结构来描述。vold会使用NetLinkManager和内核建立链接,监听udev事件。vold进程收到udev事件的时候,读取设备信息,对于需要vold管理的设备会创建Disk数据结构来代表一块设备,然后读取分区表(使用/system/bin/sgdisk命令读取),最后创建Volume结构描述分区,并放到Disk下的volumes集合中,来建立关系。vold就是通过DiskSource来确认设备是否由自己管理。

        在vold管理的Disk和Volume创建过程中,会通过CommandListener通知MountService进程中的NativeDaemonConnector。 所以vold和MountService使用NativeDaemonConnector进行通信,NativeDaemonConnector代表一个unix 域套接字的链接,该套接字在vold创建之初进行创建。MountService启动的时候通过NativeDaemonConnector链接该套接字。MountService中的NativeDaemonConnector收到磁盘和分区相关的状态消息后,会在framework层建立相应的Disk和Volume关系。 当收到volume的创建消息后,会通过NativeDaemonConnector发送命令给vold进程,通知挂载Volume。这样在framework层就建立起来了Volume数据结构,以供系统相关api返回Volume信息给应用程序使用。另外MountService和会给vold来发送多用户相关的信息,以便vold进程为不同用户准备目录信息。在vold的这端使用CommandListener来接收MountService发来的命令,最终调用vold相关函数来处理命令。

        下面我们来看一下vold的类图:

类图

vold类图
        vold的基本类图如上图, DiskSource为解析fstab文件创建的数据结构,用于描述fstab中带有voldmanaged标志的行, 其中mSysPattern为要挂载的设备。 mNikname则表示要挂载到的文件目录,mFlags 是根据fstab解析出来的挂载标志。针对插入的外置设备, vold会根据对应的DiskSource来创建Disk数据结构,然后根据该设备的分区信息创建VolumeBase数据结构,VolumeBase数据结构用来描述一个分区。 Android支持三种分区:

  • PublicVolume分区:代表公共分区,也就非Android系统也可以支持挂载的分区。如果一个Disk设备是可加密的,则该设备可以被格式化成PrivateVolume。因为PublicVolume分区可以跨设备使用,所以被成为公有(Public)分区,公有分区只能作为外置存储使用。
  • PrivateVolume:Android私有的分区,这种类型的分区是加密分区,只有创建该分区的设备才可以挂载这个分区,私有分区是用于扩展内置存储的。
  • EmulatedVolume分区: 表示模拟分区,使用fuse文件系统在内置存储上模拟外置存储。

        关于PublicVolume和PrivateVolume可以参考Android M能让外部存储变成内部存储 支持U盘热插拔。 主要分区的创建请看下图。
分区创建
        当一个正常可以挂载的设备被插入Android系统后,可以对该设备重新格式化, 格式化有两个选项:

  • Use as portable storage:格式化成可移动存储,也就是可以将该磁盘拔下来挂载到其他设备使用。这种请看创建一个PublicVolume。
  • Use as internal storage: 格式化成内置存储, 将一个外置存储格式化成内置存储,格式化后分区是加密分区,只能在创建分区的设备上使用, 对应PrivateVolume。

         对于PrivateVolume,挂载后作为内置存储的扩展还需要创建一个EmulatedVolume,在该分区上模拟一个外置存储,用于存储对应的媒体文件。
         那么主存储如何确定呢,Android提供了三种方式。

  • userdata分区是Android必须的一个分区,作为内置存储,一般也是加密后的分区。 但是userdata需要在vold启动之前挂载,所以它并不归vold管理,而是由init进程挂载。如果没有外接设备,Android系统应该在data分区上模拟主存储。

         有些设备本为了减少成本,内置设备做的很少,会使用外接存储卡作为主存存储, 在这种情况下又分为两种情况:

  • PublicVolume分区作为外置存储。
  • PrivateVolume上的EmulatedVolume作为外置存储。

多用户

关于多用户部分,vold进程提供四个api,如下:

    int onUserAdded(userid_t userId, int userSerialNumber);
    int onUserRemoved(userid_t userId);
    int onUserStarted(userid_t userId);
    int onUserStopped(userid_t userId);

        MountService会通过unix域套接字(NativeDaemonConnector)来发送命令给vold进程的CommandListener,最终调用这四个api来处理多用户事件。

        下面我们就直接进入代码分析。

代码分析

1. 分区挂载

system/core/rootdir/init.rc

service vold /system/bin/vold \
        --blkid_context=u:r:blkid:s0 --blkid_untrusted_context=u:r:blkid_untrusted:s0 \
        --fsck_context=u:r:fsck:s0 --fsck_untrusted_context=u:r:fsck_untrusted:s0
    class core
    socket vold stream 0660 root mount
    socket cryptd stream 0660 root mount
    ioprio be 2


on post-fs-data
    ......
    # Make sure we have the device encryption key
    start logd
    start vold

        在init进程挂载完内置分区后,就会启动vold进程来处理外置分区。vold可能需要在内置分区/data下面来模拟外置主分区,是依赖内置分区的,所以必须等待/data分区挂载好之后才会启动vold进程。另外vold进程启动的时候来创建了两个stream类型的unix域套接字,用于和MountService通信。

        vold进程的启动在system/vold/main.cpp 代码文件中(后面均以Android 6.0代码为例)

int main(int argc, char** argv) {
    ......
    
    VolumeManager *vm;
    CommandListener *cl;
    CryptCommandListener *ccl;
    NetlinkManager *nm;

    ......
    mkdir("/dev/block/vold", 0755); // 创建/dev/block/vold目录,用于创建设备文件

    /* For when cryptfs checks and mounts an encrypted filesystem */
    klog_set_level(6);

    /* Create our singleton managers */
    if (!(vm = VolumeManager::Instance())) {   //初始化VolumeManager
        LOG(ERROR) << "Unable to create VolumeManager";
        exit(1);
    }

    if (!(nm = NetlinkManager::Instance())) { // 初始化NetlinkManager
        LOG(ERROR) << "Unable to create NetlinkManager";
        exit(1);
    }

    if (property_get_bool("vold.debug", false)) { // 设置debug参数
        vm->setDebug(true);
    }

    cl = new CommandListener(); //初始化CommandListener 用于监听MountService管理卷的消息。
    ccl = new CryptCommandListener(); //初始化CryptCommandListener 用于监听MountServic分区加密解析的消息。
    vm->setBroadcaster((SocketListener *) cl);
    nm->setBroadcaster((SocketListener *) cl);

    if (vm->start()) { // 启动VolumeManager
        PLOG(ERROR) << "Unable to start VolumeManager";
        exit(1);
    }

    if (process_config(vm)) { //读取fstab文件,创建DiskSource数据结构
        PLOG(ERROR) << "Error reading configuration... continuing anyways";
    }

    if (nm->start()) { //启动NetlinkManager,监听udev事件。
        PLOG(ERROR) << "Unable to start NetlinkManager";
        exit(1);
    }

    coldboot("/sys/block"); //冷启动/sys/block对应的块设备
//    coldboot("/sys/class/switch");

    /*
     * Now that we're up, we can respond to commands
     */
    if (cl->startListener()) { //启动监听MountService下发的消息
        PLOG(ERROR) << "Unable to start CommandListener";
        exit(1);
    }

    if (ccl->startListener()) { //启动监听MountService下发的消息
        PLOG(ERROR) << "Unable to start CryptCommandListener";
        exit(1);
    }

    // Eventually we'll become the monitoring thread
    while(1) {
        sleep(1000);
    }

    LOG(ERROR) << "Vold exiting";
    exit(0);
}

总结下启动的主要几个步骤:

  1. 启动VolumeManager。
  2. 解析fstab生成DiskSource数据结构。
  3. 启动NetlinkManager,监听udev事件。
  4. 冷启动/sys/block下对应的块设备。
  5. 启动监听MountService下发的消息。

        这里我们不关系加密解析分区相关消息处理,后面单拿出来一篇文章进行分析,按照上面五个步骤来分析vold启动做的事情。

vm->start()
/system/vold/VolumeManager.cpp

 255 int VolumeManager::start() {
 256     // Always start from a clean slate by unmounting everything in
 257     // directories that we own, in case we crashed.
 258     unmountAll();
 259 
 260     // Assume that we always have an emulated volume on internal
 261     // storage; the framework will decide if it should be mounted.
 262     CHECK(mInternalEmulated == nullptr);
 263     mInternalEmulated = std::shared_ptr<android::vold::VolumeBase>(
 264             new android::vold::EmulatedVolume("/data/media"));
 265     mInternalEmulated->create();
 266 
 267     return 0;
 268 }

        函数首先调用umountAll() 函数来卸载所有归vold服务管理的分区,然后创建一个内部的模拟分区,对应的内置目录为/data/media。这里为什么要创建一个EmulatedVolume类型的分区呢,是因为这个分区可能做为模拟主外置分区来挂载,模拟分区没有对应的真实设备,所以这里硬编码创建一个EmulatedVolume,对应VolumeManager的mInternalEmulated成员变量。
        unmountAll() 函数相同的文件下面,代码如下

int VolumeManager::unmountAll() {
    std::lock_guard<std::mutex> lock(mLock);

    // First, try gracefully unmounting all known devices
    if (mInternalEmulated != nullptr) {
        mInternalEmulated->unmount();
    }
    for (auto disk : mDisks) {
        disk->unmountAll();
    }

    // Worst case we might have some stale mounts lurking around, so
    // force unmount those just to be safe.
    FILE* fp = setmntent("/proc/mounts", "r");
    if (fp == NULL) {
        SLOGE("Error opening /proc/mounts: %s", strerror(errno));
        return -errno;
    }

    // Some volumes can be stacked on each other, so force unmount in
    // reverse order to give us the best chance of success.
    std::list<std::string> toUnmount;
    mntent* mentry;
    while ((mentry = getmntent(fp)) != NULL) {
        if (strncmp(mentry->mnt_dir, "/mnt/", 5) == 0
                || strncmp(mentry->mnt_dir, "/storage/", 9) == 0) {
            toUnmount.push_front(std::string(mentry->mnt_dir));
        }
    }
    endmntent(fp);

    for (auto path : toUnmount) {
        SLOGW("Tearing down stale mount %s", path.c_str());
        android::vold::ForceUnmount(path);
    }

    return 0;
}

         函数不但要卸载自己管理的多个Disk和相关的分区,还要卸载/mnt和/storage/相关的目录,是因为mInternalEmulated如果被挂载,它会被挂载到/mnt/目录或者/sdcard/目录,但是它不属于任何一个Disk,所以要单独进行卸载。

         我们先跳过EmulatedVolume的创建,后面和其他类型卷的创建一起分析。先来分析DiskSource的创建。process_config(vm)函数在system/vold/main.cpp文件中。

static int process_config(VolumeManager *vm) {
    std::string path(android::vold::DefaultFstabPath());
    fstab = fs_mgr_read_fstab(path.c_str()); // 解析fstab文件
    if (!fstab) {
        PLOG(ERROR) << "Failed to open default fstab " << path;
        return -1;
    }

    /* Loop through entries looking for ones that vold manages */
    bool has_adoptable = false;
    for (int i = 0; i < fstab->num_entries; i++) {
        if (fs_mgr_is_voldmanaged(&fstab->recs[i])) {  //针对有voldmanaged标志的磁盘创建DiskSource
            if (fs_mgr_is_nonremovable(&fstab->recs[i])) {
                LOG(WARNING) << "nonremovable no longer supported; ignoring volume";
                continue;
            }

            std::string sysPattern(fstab->recs[i].blk_device);
            std::string nickname(fstab->recs[i].label);
            int flags = 0;

            if (fs_mgr_is_encryptable(&fstab->recs[i])) { //加密设备的盘添加kAdoptable标志
                flags |= android::vold::Disk::Flags::kAdoptable;
                has_adoptable = true;
            }
            if (fs_mgr_is_noemulatedsd(&fstab->recs[i])
                    || property_get_bool("vold.debug.default_primary", false)) { //添加kDefaultPrimary标志,为默认的主外置存储
                flags |= android::vold::Disk::Flags::kDefaultPrimary;
            }

            vm->addDiskSource(std::shared_ptr<VolumeManager::DiskSource>(
                    new VolumeManager::DiskSource(sysPattern, nickname, flags)));
        }
    }
    property_set("vold.has_adoptable", has_adoptable ? "1" : "0");
    return 0;
}

         process_config 函数如前面所说,解析fstab文件,然后创建DiskSource。 这里可能设置两个标志:

  • kAdoptable 表示该设备是加密设备。
  • kDefaultPrimary 表示该设备是默认的主存储分区。

        我们继续分析启动流程,下面NetlinkManager的启动过程。
system/vold/NetlinkManager.cpp

int NetlinkManager::start() {
    ....
    if ((mSock = socket(PF_NETLINK, SOCK_DGRAM | SOCK_CLOEXEC,
            NETLINK_KOBJECT_UEVENT)) < 0) {
        SLOGE("Unable to create uevent socket: %s", strerror(errno));
        return -1;
    }

    if (setsockopt(mSock, SOL_SOCKET, SO_RCVBUFFORCE, &sz, sizeof(sz)) < 0) {
        SLOGE("Unable to set uevent socket SO_RCVBUFFORCE option: %s", strerror(errno));
        goto out;
    }

    if (setsockopt(mSock, SOL_SOCKET, SO_PASSCRED, &on, sizeof(on)) < 0) {
        SLOGE("Unable to set uevent socket SO_PASSCRED option: %s", strerror(errno));
        goto out;
    }

    if (bind(mSock, (struct sockaddr *) &nladdr, sizeof(nladdr)) < 0) {
        SLOGE("Unable to bind uevent socket: %s", strerror(errno));
        goto out;
    }

    mHandler = new NetlinkHandler(mSock);
    if (mHandler->start()) {
        SLOGE("Unable to start NetlinkHandler: %s", strerror(errno));
        goto out;
    }

    return 0;

out:
    close(mSock);
    return -1;
}

        NetLinkManager主要设置套接字,然后传递给NetlinkHanler,调用NetllinkHandler来进行监听。 关于netlink主要就是通过套接字的形式和内核建立起链接,来接收内核发送上来的事件。对于Netlink事件的监听,在Android系统的libsysutils库里面提供了一套基础的实现,这个实现不是我们关注的重点,我们只简单进行介绍。 最关键的类为NetlinkListener类,这个类启动后监听netlink套接字,收到数据后会将数据解析封装成数据结构NetlinkEvent,然后调用该类下的虚函数 virtual void onEvent(NetlinkEvent *evt)来对事件进行处理。vold的NetlinkManager创建的NetlinkHandler就是NetlinkListener的子类,它实现了onEvent函数,我们来看下它的实现。

system/vold/NetlinkHandler.cpp

void NetlinkHandler::onEvent(NetlinkEvent *evt) {
    VolumeManager *vm = VolumeManager::Instance();
    const char *subsys = evt->getSubsystem();

    if (!subsys) {
        SLOGW("No subsystem found in netlink event");
        return;
    }

    if (!strcmp(subsys, "block")) {
        vm->handleBlockEvent(evt);
    }
}

        它是显现很简单。就是将块设备的事件交由VolumeManager来处理,也就是调用VolumeManager的handleBlockEvent函数。

        我们先放一下handleBlockEvent函数,顺着启动流程往下看。coldboot("/sys/block")函数,从函数名称来看是冷启的设备,冷启哪些设备呢?
system/vold/main.cpp

167 static void do_coldboot(DIR *d, int lvl) {
168     struct dirent *de;
169     int dfd, fd;
170 
171     dfd = dirfd(d);
172 
173     fd = openat(dfd, "uevent", O_WRONLY | O_CLOEXEC);
174     if(fd >= 0) {
175         write(fd, "add\n", 4);
176         close(fd);
177     }
178 
179     while((de = readdir(d))) {
180         DIR *d2;
181 
182         if (de->d_name[0] == '.')
183             continue;
184 
185         if (de->d_type != DT_DIR && lvl > 0)
186             continue;
187 
188         fd = openat(dfd, de->d_name, O_RDONLY | O_DIRECTORY);
189         if(fd < 0)
190             continue;
191 
192         d2 = fdopendir(fd);
193         if(d2 == 0)
194             close(fd);
195         else {
196             do_coldboot(d2, lvl + 1);
197             closedir(d2);
198         }
199     }
200 }
201 
202 static void coldboot(const char *path) {
203     DIR *d = opendir(path);
204     if(d) {
205         do_coldboot(d, 0);
206         closedir(d);
207     }
208 }

        coldboot实现的功能就是遍历path下所有的uevent文件,然后写入”add\n“四个字节到uevent文件(175行)。uevent主要是udev协议的一部分,当设备进行热插拔的时候,上层应用程序可以通过netlink收到相应的add,remove,change等消息,这样就可以调用mknod在/dev/目录下创建对应的驱动设备文件,这样才可以使用设备文件来操作设备。 在vold的启动过程中,由于块设备早就准备好了,不会收到设备插拔的消息,没有办法对固有的设备来创建对应的Disk和Volume结构。好在udev提供了一套机制,就是可以通过写uevent来触发热插拔事件,这里写入add就代表触发一个设备插入事件。前面我们已经启动了NetlinkManager来监听netlink事件,这里来触发uevent的设备插入事件,VolumeManager就可以创建相应的Disk和Volume数据结构了。

        最后就是启动监听MountService消息了。CommandListener->startListener()

        CommandListener类主要的作用是通过unix域套接字来和MountService进行通行,它借助了Android的libsysutils库提供的api来对命令进行分发。 这里我们也不打算深入分析,只是简单介绍下和我们分析代码相关的细节。 CommandListener的构造函数如下:
system/vold/CommandListener.cpp

CommandListener::CommandListener() :
                 FrameworkListener("vold", true) {
    registerCmd(new DumpCmd());
    registerCmd(new VolumeCmd());
    registerCmd(new AsecCmd());
    registerCmd(new ObbCmd());
    registerCmd(new StorageCmd());
    registerCmd(new FstrimCmd());
}

        CommandListener在创建的时候注册了一些列支持的命令处理类,当通过socket收到命令后就会匹配相应的命令处理类来处理命令。我们这里来看下VolumeCmd类如何处理命令。
system/vold/CommandListener.cpp

int CommandListener::VolumeCmd::runCommand(SocketClient *cli,
                                           int argc, char **argv) {
    ......
    VolumeManager *vm = VolumeManager::Instance();
    std::lock_guard<std::mutex> lock(vm->getLock());

    // TODO: tease out methods not directly related to volumes

    std::string cmd(argv[1]);
    if (cmd == "reset") {
        return sendGenericOkFail(cli, vm->reset());
    ......
    } else if (cmd == "user_added" && argc > 3) {
        // user_added [user] [serial]
        return sendGenericOkFail(cli, vm->onUserAdded(atoi(argv[2]), atoi(argv[3])));

    } else if (cmd == "user_removed" && argc > 2) {
    ......
    return cli->sendMsg(ResponseCode::CommandSyntaxError, nullptr, false);
}

        这里省略了大量具体命令处理的分之,但是我们可以看到VolumeCmd主要调用VolumeManager来处理MountService发出的命令。

        看完vold启动的流程后我们留下了三件事要分析:

  1. VolumeManager.handleBlockEvent处理设备插拔事件。
  2. MountService和Vold通信的建立。
  3. 卷的挂载。
  4. 多用户的管理。

        下面我们逐一分析。

 277 void VolumeManager::handleBlockEvent(NetlinkEvent *evt) {
 278     std::lock_guard<std::mutex> lock(mLock);
 279 
         ......
 286     std::string eventPath(evt->findParam("DEVPATH"));
 287     std::string devType(evt->findParam("DEVTYPE"));
 288 
 289     if (devType != "disk") return;
 290 
 291     int major = atoi(evt->findParam("MAJOR"));
 292     int minor = atoi(evt->findParam("MINOR"));
 293     dev_t device = makedev(major, minor);
 294 
 295     switch (evt->getAction()) {
 296     case NetlinkEvent::Action::kAdd: {
 297         for (auto source : mDiskSources) {
 298             if (source->matches(eventPath)) {
 299                 // For now, assume that MMC devices are SD, and that
 300                 // everything else is USB
 301                 int flags = source->getFlags();
 302                 if (major == kMajorBlockMmc) {
 303                     flags |= android::vold::Disk::Flags::kSd;
 304                 } else {
 305                     flags |= android::vold::Disk::Flags::kUsb;
 306                 }
 307 
 308                 auto disk = new android::vold::Disk(eventPath, device,
 309                         source->getNickname(), flags);
 310                 disk->create();
 311                 mDisks.push_back(std::shared_ptr<android::vold::Disk>(disk));
 312                 break;
 313             }
 314         }
 315         break;
 316     }
 317     case NetlinkEvent::Action::kChange: {
 318         LOG(DEBUG) << "Disk at " << major << ":" << minor << " changed";
 319         for (auto disk : mDisks) {
 320             if (disk->getDevice() == device) {
 321                 disk->readMetadata();
 322                 disk->readPartitions();
 323             }
 324         }
 325         break;
 326     }
 327     case NetlinkEvent::Action::kRemove: {
 328         auto i = mDisks.begin();
 329         while (i != mDisks.end()) {
 330             if ((*i)->getDevice() == device) {
 331                 (*i)->destroy();
 332                 i = mDisks.erase(i);
 333             } else {
 334                 ++i;
 335             }
 336         }
 337         break;
 338     }
 339     default: {
 340         LOG(WARNING) << "Unexpected block event action " << (int) evt->getAction();
 341         break;
 342     }
 343     }
 344 }

         handleBlockEvent的逻辑实际上是比较简单的,从NetlinkEvent中或者主,次设备号,和ueventuevent对应的设备文件的/sys/block路径。

  • 对于add消息, 根据主设备号确定设备的类型是usb还是sd卡。从NetlinkEvent中获取uevent对应的设备文件的/sys/block路径,然后和DiskSource做比较,确定是不是对应voldmanaged标志的设备,如果是voldmanaged的设备就会创建Disk数据结构,再调用Disk.create()函数执行初始化。将初始化好的Disk数据结构保存起来。
  • 对于change消息,则重新读取设备的meta信息和分区信息。
  • remove消息则调用Disk.destory()函数,从mDisks中移除Disk数据结构。

         先来看看Disk的构造函数
system/vold/Disk.cpp

Disk::Disk(const std::string& eventPath, dev_t device,
        const std::string& nickname, int flags) :
        mDevice(device), mSize(-1), mNickname(nickname), mFlags(flags), mCreated(
                false), mJustPartitioned(false) {
    mId = StringPrintf("disk:%u,%u", major(device), minor(device));
    mEventPath = eventPath;
    mSysPath = StringPrintf("/sys/%s", eventPath.c_str());
    mDevPath = StringPrintf("/dev/block/vold/%s", mId.c_str());
    CreateDeviceNode(mDevPath, mDevice);
}

status_t CreateDeviceNode(const std::string& path, dev_t dev) {
    const char* cpath = path.c_str();
    status_t res = 0;
    ......
    mode_t mode = 0660 | S_IFBLK;
    if (mknod(cpath, mode, dev) < 0) {
        ......
    }
    ......
    return res;
}

         前面还没说明Disk的成员变量,这里简答说说:

  • mId :设备的唯一id,默认disk:${主设备号}😒{次设备号}。
  • mEventPath: uevent文件路径。
  • mSysPath: /sys/block 下的设备路径。
  • mDevPath: /dev下的设备路径。

         CreateDeviceNode函数调用mknod 创建dev而被文件,目录为/dev/block/vold/。相当于vold实现了部分udev的功能。

        Disk的主要的初始化工作都在Disk.create()函数下面。

status_t Disk::create() {
    CHECK(!mCreated);
    mCreated = true;
    notifyEvent(ResponseCode::DiskCreated, StringPrintf("%d", mFlags));
    readMetadata();
    readPartitions();
    return OK;
}

notifyEvent函数向MountService发送消息。
readMetadata函数读取磁盘的meta信息。
readPartitions()读取设备的分区信息,创建Volume。

        先来看看读取设备的meta信息都读了哪些信息。

status_t Disk::readMetadata() {
    mSize = -1;
    mLabel.clear();

    int fd = open(mDevPath.c_str(), O_RDONLY | O_CLOEXEC);
    if (fd != -1) {
        if (ioctl(fd, BLKGETSIZE64, &mSize)) {
            mSize = -1;
        }
        close(fd);
    }

    switch (major(mDevice)) {
    case kMajorBlockScsiA: case kMajorBlockScsiB: case kMajorBlockScsiC: case kMajorBlockScsiD:
    case kMajorBlockScsiE: case kMajorBlockScsiF: case kMajorBlockScsiG: case kMajorBlockScsiH:
    case kMajorBlockScsiI: case kMajorBlockScsiJ: case kMajorBlockScsiK: case kMajorBlockScsiL:
    case kMajorBlockScsiM: case kMajorBlockScsiN: case kMajorBlockScsiO: case kMajorBlockScsiP: {
        std::string path(mSysPath + "/device/vendor");
        std::string tmp;
        if (!ReadFileToString(path, &tmp)) {
            PLOG(WARNING) << "Failed to read vendor from " << path;
            return -errno;
        }
        mLabel = tmp;
        break;
    }
    case kMajorBlockMmc: {
        std::string path(mSysPath + "/device/manfid");
        std::string tmp;
        if (!ReadFileToString(path, &tmp)) {
            PLOG(WARNING) << "Failed to read manufacturer from " << path;
            return -errno;
        }
        uint64_t manfid = strtoll(tmp.c_str(), nullptr, 16);
        // Our goal here is to give the user a meaningful label, ideally
        // matching whatever is silk-screened on the card.  To reduce
        // user confusion, this list doesn't contain white-label manfid.
        switch (manfid) {
        case 0x000003: mLabel = "SanDisk"; break;
        case 0x00001b: mLabel = "Samsung"; break;
        case 0x000028: mLabel = "Lexar"; break;
        case 0x000074: mLabel = "Transcend"; break;
        }
        break;
    }
    default: {
        LOG(WARNING) << "Unsupported block major type" << major(mDevice);
        return -ENOTSUP;
    }
    }

    notifyEvent(ResponseCode::DiskSizeChanged, StringPrintf("%" PRId64, mSize));
    notifyEvent(ResponseCode::DiskLabelChanged, mLabel);
    notifyEvent(ResponseCode::DiskSysPathChanged, mSysPath);
    return OK;
}

         readMetadata函数主要通过ioctl函数获取磁盘的大小,保存到mSize变量中,然后根据设备vendor或者manfid 获取Disk的mLabel。

         Disk::readPartitions() 函数用于读取分区创建Volume数据结构。是磁盘挂载的最重要步骤。

245 status_t Disk::readPartitions() {
246     int8_t maxMinors = getMaxMinors();
247     if (maxMinors < 0) {
248         return -ENOTSUP;
249     }
250 
251     destroyAllVolumes();  // 卸载所有卷
252 
253     // Parse partition table
254 
255     std::vector<std::string> cmd;
256     cmd.push_back(kSgdiskPath);
257     cmd.push_back("--android-dump");
258     cmd.push_back(mDevPath);
259 
260     std::vector<std::string> output;
261     status_t res = ForkExecvp(cmd, output); //调用/system/bin/sgdisk --android-dump /dev/block/vold/${disk_id}读取分区信息
262     if (res != OK) {
263         LOG(WARNING) << "sgdisk failed to scan " << mDevPath;
264         notifyEvent(ResponseCode::DiskScanned);
265         mJustPartitioned = false;
266         return res;
267     }
268 
269     Table table = Table::kUnknown;
270     bool foundParts = false;
271     for (auto line : output) {
272         char* cline = (char*) line.c_str();
273         char* token = strtok(cline, kSgdiskToken);
274         if (token == nullptr) continue;
275 
276         if (!strcmp(token, "DISK")) { //获取分区类型,mbr或者gtk
277             const char* type = strtok(nullptr, kSgdiskToken);
278             if (!strcmp(type, "mbr")) {
279                 table = Table::kMbr;
280             } else if (!strcmp(type, "gpt")) {
281                 table = Table::kGpt;
282             }
283         } else if (!strcmp(token, "PART")) {
284             foundParts = true;
285             int i = strtol(strtok(nullptr, kSgdiskToken), nullptr, 10);
286             if (i <= 0 || i > maxMinors) { //次设备号不合理, 处理下一个分区
287                 LOG(WARNING) << mId << " is ignoring partition " << i
288                         << " beyond max supported devices";
289                 continue;
290             }
291             dev_t partDevice = makedev(major(mDevice), minor(mDevice) + i); //获取分区设备号。
292 
293             if (table == Table::kMbr) { //根据mbr分区表设置分区信息
294                 const char* type = strtok(nullptr, kSgdiskToken);
295 
296                 switch (strtol(type, nullptr, 16)) {
297                 case 0x06: // FAT16
298                 case 0x0b: // W95 FAT32 (LBA)
299                 case 0x0c: // W95 FAT32 (LBA)
300                 case 0x0e: // W95 FAT16 (LBA)
301                     createPublicVolume(partDevice); //mbr分区表直接创建PublicVolume
302                     break;
303                 }
304             } else if (table == Table::kGpt) { //根据gpt分区表设置分区信息
305                 const char* typeGuid = strtok(nullptr, kSgdiskToken);
306                 const char* partGuid = strtok(nullptr, kSgdiskToken);
307 
308                 if (!strcasecmp(typeGuid, kGptBasicData)) { //分区类型为基础数据分区,代表一个PublicVolume。
309                     createPublicVolume(partDevice);
310                 } else if (!strcasecmp(typeGuid, kGptAndroidExpand)) {//分区类型为Android扩展,创建一个私有分区。
311                     createPrivateVolume(partDevice, partGuid);
312                 }
313             }
314         }
315     }
316     //最后如果没有找到分区类型或者没有找到分区,尝试将整个设备作为一个分区去创建PublicVolume。
317     // Ugly last ditch effort, treat entire disk as partition
318     if (table == Table::kUnknown || !foundParts) {
319         LOG(WARNING) << mId << " has unknown partition table; trying entire device";
320 
321         std::string fsType;
322         std::string unused;
323         if (ReadMetadataUntrusted(mDevPath, fsType, unused, unused) == OK) {
324             createPublicVolume(mDevice);
325         } else {
326             LOG(WARNING) << mId << " failed to identify, giving up";
327         }
328     }
329 
330     notifyEvent(ResponseCode::DiskScanned);
331     mJustPartitioned = false;
332     return OK;
333 }

         关于代码,我们可以知道readPartitions函数通过读取mbr或者gpt分区表,还获取分区的信息,创建相应的卷。对于mbr分区,全都调用createPublicVolume函数来创建PublicVolume分区。对于gpt分区,要根据分区的typeGuid来创建Volume, 如果typeGuid为kGptBasicData表示这是一个基础数据分区,所以并非一个Android特有的卷,使用createPublicVolume来创建PublicVolume。 对于typeGuid为kGptAndroidExpand的卷则表示这个卷为一个Android扩展的卷,在其他系统上是无法处理的,这对应一个vold的PrivateVolume,使用createPrivateVolume函数来创建。回过头来想想,我们对于EmulatedVolume的创建也还没有分析,现在时机成熟了,我们来一起分析下三种卷的创建。

先来看PublicVomule的创建。
system/vold/Disk.cpp

void Disk::createPublicVolume(dev_t device) {
    auto vol = std::shared_ptr<VolumeBase>(new PublicVolume(device));
    if (mJustPartitioned) {
        LOG(DEBUG) << "Device just partitioned; silently formatting";
        vol->setSilent(true);
        vol->create();
        vol->format("auto");
        vol->destroy();
        vol->setSilent(false);
    }

    mVolumes.push_back(vol);
    vol->setDiskId(getId());
    vol->create();
}

         PublicVolume的创建首先创建一个PublicVolume实例,然后如果Disk刚刚进行分区,要先进行格式化。mJustPartitioned为true的情况进行格式化,这不是我们关注的重点。创建完PublicVolume实例后最重要的是调用create函数来创建, 这其实和我们的EmulatedVolume的创建基本是一致的,先创建实例再调用create()函数。

再来看下PrivateVolume的创建。
system/vold/Disk.cpp

void Disk::createPrivateVolume(dev_t device, const std::string& partGuid) {
    std::string normalizedGuid;
    if (NormalizeHex(partGuid, normalizedGuid)) {
        LOG(WARNING) << "Invalid GUID " << partGuid;
        return;
    }

    std::string keyRaw;
    if (!ReadFileToString(BuildKeyPath(normalizedGuid), &keyRaw)) {
        PLOG(ERROR) << "Failed to load key for GUID " << normalizedGuid;
        return;
    }

    LOG(DEBUG) << "Found key for GUID " << normalizedGuid;

    auto vol = std::shared_ptr<VolumeBase>(new PrivateVolume(device, keyRaw));
    if (mJustPartitioned) {
        LOG(DEBUG) << "Device just partitioned; silently formatting";
        vol->setSilent(true);
        vol->create();
        vol->format("auto");
        vol->destroy();
        vol->setSilent(false);
    }

    mVolumes.push_back(vol);
    vol->setDiskId(getId());
    vol->setPartGuid(partGuid);
    vol->create();
}

        私有卷的创建略微有点麻烦。要先读取/data/misc/vold/expand_${hex(partguid)}.key读取用于解密的key,然后将这个key作为参数用于创建PrivateVolume实例子,后面的流程和创建PublicVolume一样,就是调用create()函数。

        所以无论PublicVolume,PrivateVolume还是EmulatedVolue的创建都是两步:

  1. 构造函数。
  2. create()函数。

        我们逐一分析。
system/vold/VolumeBase.cpp

VolumeBase::VolumeBase(Type type) :
        mType(type), mMountFlags(0), mMountUserId(-1), mCreated(false), mState(
                State::kUnmounted), mSilent(false) {
}

system/vold/PublicVolume.cpp

PublicVolume::PublicVolume(dev_t device) :
        VolumeBase(Type::kPublic), mDevice(device), mFusePid(0) {
    setId(StringPrintf("public:%u,%u", major(device), minor(device)));
    mDevPath = StringPrintf("/dev/block/vold/%s", getId().c_str());
}

        PublicVolume的的几个成员变量:

  • mType: kPublic。
  • mId为public:${major},${minor}。
  • mDevPath: /dev/block/vold/${mId}
  • mDevice: major:minor

PrivateVolume构造如下:
system/vold/PrivateVolume.cpp

PrivateVolume::PrivateVolume(dev_t device, const std::string& keyRaw) :
        VolumeBase(Type::kPrivate), mRawDevice(device), mKeyRaw(keyRaw) {
    setId(StringPrintf("private:%u,%u", major(device), minor(device)));
    mRawDevPath = StringPrintf("/dev/block/vold/%s", getId().c_str());
}

        PrivateVolume的的几个成员变量:

  • mType: kPrivate。
  • mId为private:${major},${minor}。
  • mDevPath: /dev/block/vold/${mId}
  • mRawDevice:major:minor
  • mKeyRaw: /data/misc/vold/expand_${hex(partguid)}.key 度到的key。

EmulatedVolume构造如下:
system/vold/EmulatedVolume.cpp

EmulatedVolume::EmulatedVolume(const std::string& rawPath) :
        VolumeBase(Type::kEmulated), mFusePid(0) {
    setId("emulated");
    mRawPath = rawPath;
    mLabel = "emulated";
}

EmulatedVolume::EmulatedVolume(const std::string& rawPath, dev_t device,
        const std::string& fsUuid) : VolumeBase(Type::kEmulated), mFusePid(0) {
    setId(StringPrintf("emulated:%u,%u", major(device), minor(device)));
    mRawPath = rawPath;
    mLabel = fsUuid;
}

        EmulatedVolume有两个构造函数,第一个构造函数对应VolumeManager的mInternalEmulated模拟分区,第二个构造函数对应为PrivateVolume创建的EmulatedVolume创建的模拟分区,后面我们会看到,每个PrivateVolume分区都会创建一个EmulatedVolume分区,用来控制读写权限:

        EmulatedVolume成员变量:

  • mType: kEmulated
  • mId: emulated 或者emulated:${major}😒{minor}
  • mRawPath: /data/media| ${rawPath}。
  • mLabel: emulated | fsUuid。

         构造函数我们看完了,主要就是设置了几个成员变量。下面来看下create()函数。
system/vold/VolumeBase.cpp

status_t VolumeBase::create() {
   CHECK(!mCreated);

   mCreated = true;
   status_t res = doCreate();
   notifyEvent(ResponseCode::VolumeCreated,
           StringPrintf("%d \"%s\" \"%s\"", mType, mDiskId.c_str(), mPartGuid.c_str()));
   setState(State::kUnmounted);
   return res;
}

         create()函数并非一个虚函数,所以只有VolumeBase实现了该函数。函数首先调用虚函数doCreate()去真正创建不同类型的分区。然后调用notifyEvent通知MountService新卷的创建,最后更新状态。所以我们真正要关心的函数为doCreate函数。
system/vold/PublicVolume.cpp

status_t PublicVolume::doCreate() {
    return CreateDeviceNode(mDevPath, mDevice);
}

status_t CreateDeviceNode(const std::string& path, dev_t dev) {
     const char* cpath = path.c_str();
    ......
    mode_t mode = 0660 | S_IFBLK;
    if (mknod(cpath, mode, dev) < 0) {
        if (errno != EEXIST) {
            PLOG(ERROR) << "Failed to create device node for " << major(dev)
                    << ":" << minor(dev) << " at " << path;
            res = -errno;
        }
    }
    ......
    return res;
}

         函数主要调用mknod来创建卷对应的/dev/block/vold/${mId}文件。

        再来看下PrivateVolume的doCreate函数。

status_t PrivateVolume::doCreate() {
    if (CreateDeviceNode(mRawDevPath, mRawDevice)) {  // 创建
        return -EIO;
    }

    // Recover from stale vold by tearing down any old mappings
    cryptfs_revert_ext_volume(getId().c_str());

    // TODO: figure out better SELinux labels for private volumes

    unsigned char* key = (unsigned char*) mKeyRaw.data();
    char crypto_blkdev[MAXPATHLEN];
    int res = cryptfs_setup_ext_volume(getId().c_str(), mRawDevPath.c_str(),
            key, mKeyRaw.size(), crypto_blkdev);
    mDmDevPath = crypto_blkdev;
    if (res != 0) {
        PLOG(ERROR) << getId() << " failed to setup cryptfs";
        return -EIO;
    }

    return OK;
}

        PrivateVolume的创建首先调用mknode创建 /dev/block/vold/${mId}设备。然后使用主秘钥去调用cryptfs_setup_ext_volume函数创建一个加密的设备,这个设备的路径通过crypto_blkdev变量返回,保存在mDmDevPath中,以后挂载mDmDevPath对应的设备后,写入的数据就会被加密的存到mRawDevPath对应的卷上,读数据则会被解密后返回给调用方。 主密钥的生成主要在设备格式化过程中(也就是创建PrivateVolume分区时)。这里对于PrivateVolume再多说几句,我们知道可以使用外置存储设备来模拟内置存储设备,这种情况就会创建PrivateVolume分区,PrivateVolume分区的加密使用全盘加密,PrivateVolume对应两个设备文件,分别是mRawDevPath对应的设备文件,这个文件代表一个原始设备,通过device-mapping技术映射到mDmDevPath对应的逻辑设备,对mDmDevPath设备的读写最终都会被转发到mRawDevPath设备,但是在转发之前要对数据进行加解密,所以mRawDevPath设备上的数据都是加密的,如果直接挂载mRawDevPath设备是无法看到正常的文件内容的。后面我们会专门拿出一篇文章来写磁盘加密这块技术。

        最后看下EmulatedVolume没有实现doCreare函数,所以这一步它什么都没做。

        Disk和Volume的创建到这里就完成了,但是我们并没有看到分区的挂载。挂载的命令主要由MountService来发起。所以下面我们开对MountService来进行分析。

先简单看下MountService的构造函数:
frameworks/base/services/core/java/com/android/server/MountService.java

1360     /**
1361      * Constructs a new MountService instance
1362      *
1363      * @param context  Binder context for this service
1364      */
1365     public MountService(Context context) {
1366         sSelf = this;
1367 
1368         mContext = context;
1369         mCallbacks = new Callbacks(FgThread.get().getLooper());
1370 
1371         // XXX: This will go away soon in favor of IMountServiceObserver
1372         mPms = (PackageManagerService) ServiceManager.getService("package");
1373         //1 创建Handler,用于处理vold消息
1374         HandlerThread hthread = new HandlerThread(TAG);
1375         hthread.start();
1376         mHandler = new MountServiceHandler(hthread.getLooper());
1377 
1378         // Add OBB Action Handler to MountService thread.
1379         mObbActionHandler = new ObbActionHandler(IoThread.get().getLooper());
1380 
1381         // Initialize the last-fstrim tracking if necessary
1382         File dataDir = Environment.getDataDirectory();
1383         File systemDir = new File(dataDir, "system");
             ......
1396 
1397         mSettingsFile = new AtomicFile(
1398                 new File(Environment.getSystemSecureDirectory(), "storage.xml"));
1399 
1400         synchronized (mLock) { // 2读取配置文件
1401             readSettingsLocked();
1402         }
1403 
1404         LocalServices.addService(MountServiceInternal.class, mMountServiceInternal);
1405 
1406         /*
1407          * Create the connection to vold with a maximum queue of twice the
1408          * amount of containers we'd ever expect to have. This keeps an
1409          * "asec list" from blocking a thread repeatedly.
1410          */
1411         //3 与vold建立链接,处理卷相关消息和发送命令
1412         mConnector = new NativeDaemonConnector(this, "vold", MAX_CONTAINERS * 2, VOLD_TAG, 25,
1413                 null);
1414         mConnector.setDebug(true);
1415 
1416         Thread thread = new Thread(mConnector, VOLD_TAG);
1417         thread.start();
1418          //4 与vold建立链接, 处理加密相关命令和消息
1419         // Reuse parameters from first connector since they are tested and safe
1420         mCryptConnector = new NativeDaemonConnector(this, "cryptd",
1421                 MAX_CONTAINERS * 2, CRYPTD_TAG, 25, null);
1422         mCryptConnector.setDebug(true);
1423 
1424         Thread crypt_thread = new Thread(mCryptConnector, CRYPTD_TAG);
1425         crypt_thread.start();
1426         // 5注册广播接受者用于处理用户相关消息
1427         final IntentFilter userFilter = new IntentFilter();
1428         userFilter.addAction(Intent.ACTION_USER_ADDED);
1429         userFilter.addAction(Intent.ACTION_USER_REMOVED);
1430         mContext.registerReceiver(mUserReceiver, userFilter, null, mHandler);
1431 
             // 6 创建内置卷对应vold的内置EmulatedVolume("/data/media")
1432         addInternalVolume();
1433         // 7 添加到watchdog监控
1434         // Add ourself to the Watchdog monitors if enabled.
1435         if (WATCHDOG_ENABLE) {
1436             Watchdog.getInstance().addMonitor(this);
1437         }
1438     }


    private void addInternalVolume() {
        // Create a stub volume that represents internal storage
        final VolumeInfo internal = new VolumeInfo(VolumeInfo.ID_PRIVATE_INTERNAL,
                VolumeInfo.TYPE_PRIVATE, null, null);
        internal.state = VolumeInfo.STATE_MOUNTED;
        internal.path = Environment.getDataDirectory().getAbsolutePath();
        mVolumes.put(internal.id, internal);
    }

    private void readSettingsLocked() {
        mRecords.clear();
        mPrimaryStorageUuid = getDefaultPrimaryStorageUuid();
        mForceAdoptable = false;

        FileInputStream fis = null;
        try {
            fis = mSettingsFile.openRead();
            final XmlPullParser in = Xml.newPullParser();
            in.setInput(fis, StandardCharsets.UTF_8.name());

            int type;
            while ((type = in.next()) != END_DOCUMENT) {
                if (type == START_TAG) {
                    final String tag = in.getName();
                    if (TAG_VOLUMES.equals(tag)) {
                        final int version = readIntAttribute(in, ATTR_VERSION, VERSION_INIT);
                        final boolean primaryPhysical = SystemProperties.getBoolean(
                                StorageManager.PROP_PRIMARY_PHYSICAL, false);
                        final boolean validAttr = (version >= VERSION_FIX_PRIMARY)
                                || (version >= VERSION_ADD_PRIMARY && !primaryPhysical);
                        if (validAttr) {
                            mPrimaryStorageUuid = readStringAttribute(in,
                                    ATTR_PRIMARY_STORAGE_UUID);
                        }
                        mForceAdoptable = readBooleanAttribute(in, ATTR_FORCE_ADOPTABLE, false);

                    } else if (TAG_VOLUME.equals(tag)) {
                        final VolumeRecord rec = readVolumeRecord(in);
                        mRecords.put(rec.fsUuid, rec);
                    }
                }
            }
        } catch (FileNotFoundException e) {
            // Missing metadata is okay, probably first boot
        } catch (IOException e) {
            Slog.wtf(TAG, "Failed reading metadata", e);
        } catch (XmlPullParserException e) {
            Slog.wtf(TAG, "Failed reading metadata", e);
        } finally {
            IoUtils.closeQuietly(fis);
        }
    }

        MountService的初始化主要经过一下7个步骤:

  1. 创建Handler,用于处理vold消息 (1373-1376),对应MountService的一个独立线程。
  2. 读取配置文件,1401行readSettingsLocked(),我们稍后分析。
  3. 与vold建立链接,用于处理vold卷相关消息和给vold发送命令。(1142-1147)
  4. 与vold建立链接, 处理vold加密相关消息和给vold发送命令。(1420-1425)
  5. 注册广播接收者用于处理多用户相关消息。(1427-1430)
  6. 创建内置卷。对应vold的内置EmulatedVolume("/data/media")。1432
  7. 添加watchdog监控。

        我们不重点分析和vold链接的代码,这不是我们关注的重点。对于添加内置卷的代码也比较简单,不去进行分析了。 读取配置文件这步有一些关键代码,来看下。

    private void readSettingsLocked() {
        mRecords.clear();
        mPrimaryStorageUuid = getDefaultPrimaryStorageUuid();
        mForceAdoptable = false;

       ......
    }

        读取配置文件的代码也比较枯燥,另外配置文件只是为了兼容老版本。就不分析肋。 主要需要关注的是mPrimaryStorageUuid变量的设置。

    private String getDefaultPrimaryStorageUuid() {
        if (SystemProperties.getBoolean(StorageManager.PROP_PRIMARY_PHYSICAL, false)) {
            return StorageManager.UUID_PRIMARY_PHYSICAL;
        } else {
            return StorageManager.UUID_PRIVATE_INTERNAL;
        }
    }
    
public static final String PROP_PRIMARY_PHYSICAL = "ro.vold.primary_physical"

        也就是说如果设置了ro.vold.primary_physica系统属性为真表示使用物理私有的卷作为主存储。否则使用私有内置卷作为主存储。 我们后面还会结合代码来分析主存储的确定。在这之前先来看下systemReady做了啥

    private void systemReady() {
        mSystemReady = true;
        mHandler.obtainMessage(H_SYSTEM_READY).sendToTarget();
    }

    private void handleSystemReady() {
        synchronized (mLock) {
            resetIfReadyAndConnectedLocked();
        }

        // Start scheduling nominally-daily fstrim operations
        MountServiceIdler.scheduleIdlePass(mContext);
    }
    private void resetIfReadyAndConnectedLocked() {
        Slog.d(TAG, "Thinking about reset, mSystemReady=" + mSystemReady
                + ", mDaemonConnected=" + mDaemonConnected);
        if (mSystemReady && mDaemonConnected) {
            killMediaProvider();

            mDisks.clear();
            mVolumes.clear();

            addInternalVolume();

            try {
                mConnector.execute("volume", "reset");

                // Tell vold about all existing and started users
                final UserManager um = mContext.getSystemService(UserManager.class);
                final List<UserInfo> users = um.getUsers();
                for (UserInfo user : users) {
                    mConnector.execute("volume", "user_added", user.id, user.serialNumber);
                }
                for (int userId : mStartedUsers) {
                    mConnector.execute("volume", "user_started", userId);
                }
            } catch (NativeDaemonConnectorException e) {
                Slog.w(TAG, "Failed to reset vold", e);
            }
        }
    }

主要做了三件事:

  1. 下发volume的reset命令。
  2. 下发volume的user_added命令。
  3. volume的user_started命令。

        回到vold先看下reset命令,多用户的命令留到后面分析。

int VolumeManager::reset() {
    // Tear down all existing disks/volumes and start from a blank slate so
    // newly connected framework hears all events.
    mInternalEmulated->destroy();
    mInternalEmulated->create();
    for (auto disk : mDisks) {
        disk->destroy();
        disk->create();
    }
    mAddedUsers.clear();
    mStartedUsers.clear();
    return 0;
}

        这里做的事情我们已经比较熟悉了,就是重新创建了Disk和Volume。 在Disk创建过程会发送DISK_CREATED消息给MountService。我们再来回顾下Disk的创建:
system/vold/Disk.cpp

status_t Disk::create() {
   ......
    notifyEvent(ResponseCode::DiskCreated, StringPrintf("%d", mFlags));
  ......
    return OK;
}

        具体怎么发到MountService就不说明了,读者可以自己研究下。我们来关注MountService如何处理这个消息。
framework/base/services/core/java/com/android/server/MountService.java

private boolean onEventLocked(int code, String raw, String[] cooked) {
        switch (code) {
            case VoldResponseCode.DISK_CREATED: {
                if (cooked.length != 3) break;
                final String id = cooked[1];
                int flags = Integer.parseInt(cooked[2]);
                if (SystemProperties.getBoolean(StorageManager.PROP_FORCE_ADOPTABLE, false)
                        || mForceAdoptable) {
                    flags |= DiskInfo.FLAG_ADOPTABLE;
                }
                mDisks.put(id, new DiskInfo(id, flags));
                break;
            }

        只不过创建了一个DiskInfo数据结构。

        接下来我们再看下新加卷的处理。同样在vold中创建卷会通知MountService。framework/base/services/core/java/com/android/server/MountService.java

private boolean onEventLocked(int code, String raw, String[] cooked) {
        switch (code) {
        ......
        case VoldResponseCode.VOLUME_CREATED: {
                final String id = cooked[1];
                final int type = Integer.parseInt(cooked[2]);
                final String diskId = TextUtils.nullIfEmpty(cooked[3]);
                final String partGuid = TextUtils.nullIfEmpty(cooked[4]);

                final DiskInfo disk = mDisks.get(diskId);
                final VolumeInfo vol = new VolumeInfo(id, type, disk, partGuid);
                mVolumes.put(id, vol);
                onVolumeCreatedLocked(vol);
                break;

1257     private void onVolumeCreatedLocked(VolumeInfo vol) {
1258         if (mPms.isOnlyCoreApps()) { //OnlyCore 模式不挂载外置存储
1259             Slog.d(TAG, "System booted in core-only mode; ignoring volume " + vol.getId());
1260             return;
1261         }
1262 
1263         if (vol.type == VolumeInfo.TYPE_EMULATED) { //模拟存储
1264             final StorageManager storage = mContext.getSystemService(StorageManager.class);
1265             final VolumeInfo privateVol = storage.findPrivateForEmulated(vol);
1266 
1267             if (Objects.equals(StorageManager.UUID_PRIVATE_INTERNAL, mPrimaryStorageUuid)
1268                     && VolumeInfo.ID_PRIVATE_INTERNAL.equals(privateVol.id)) { //使用内置存储作为主分区
1269                 Slog.v(TAG, "Found primary storage at " + vol);
1270                 vol.mountFlags |= VolumeInfo.MOUNT_FLAG_PRIMARY;
1271                 vol.mountFlags |= VolumeInfo.MOUNT_FLAG_VISIBLE;
1272                 mHandler.obtainMessage(H_VOLUME_MOUNT, vol).sendToTarget();
1273 
1274             } else if (Objects.equals(privateVol.fsUuid, mPrimaryStorageUuid)) {//指定私有存储作为主存储
1275                 Slog.v(TAG, "Found primary storage at " + vol);
1276                 vol.mountFlags |= VolumeInfo.MOUNT_FLAG_PRIMARY;
1277                 vol.mountFlags |= VolumeInfo.MOUNT_FLAG_VISIBLE;
1278                 mHandler.obtainMessage(H_VOLUME_MOUNT, vol).sendToTarget();
1279             }
1280 
1281         } else if (vol.type == VolumeInfo.TYPE_PUBLIC) {
1282             // TODO: only look at first public partition
1283             if (Objects.equals(StorageManager.UUID_PRIMARY_PHYSICAL, mPrimaryStorageUuid) //使用外置存储作为主分区
1284                     && vol.disk.isDefaultPrimary()) {
1285                 Slog.v(TAG, "Found primary storage at " + vol);
1286                 vol.mountFlags |= VolumeInfo.MOUNT_FLAG_PRIMARY;
1287                 vol.mountFlags |= VolumeInfo.MOUNT_FLAG_VISIBLE;
1288             }
1289 
1290             // Adoptable public disks are visible to apps, since they meet
1291             // public API requirement of being in a stable location.
1292             if (vol.disk.isAdoptable()) {
1293                 vol.mountFlags |= VolumeInfo.MOUNT_FLAG_VISIBLE;
1294             }
1295 
1296             vol.mountUserId = mCurrentUserId;
1297             mHandler.obtainMessage(H_VOLUME_MOUNT, vol).sendToTarget();
1298 
1299         } else if (vol.type == VolumeInfo.TYPE_PRIVATE) {
1300             mHandler.obtainMessage(H_VOLUME_MOUNT, vol).sendToTarget();
1301 
1302         } else {
1303             Slog.d(TAG, "Skipping automatic mounting of " + vol);
1304         }
1305     }

        注意这里首先会确定主存储,给主存储盘设置VolumeInfo.MOUNT_FLAG_PRIMARY标志,另外 VolumeInfo.MOUNT_FLAG_VISIBLE代表该盘是否对应用程序可见,也就是Context的那些api能否获取到该路径。 对于非PublicVolume,只有主存储对app可见。 PublicVolume则如果是加密的盘驱或者主存储,对应用程序可见,因为加密的盘驱有固定的存储位置。无论选择哪个卷都会发送H_VOLUME_MOUNT消息来挂载卷

 608         public void handleMessage(Message msg) {
                     ....
 668                 case H_VOLUME_MOUNT: {
 669                     final VolumeInfo vol = (VolumeInfo) msg.obj;
 670                     if (isMountDisallowed(vol)) {
 671                         Slog.i(TAG, "Ignoring mount " + vol.getId() + " due to policy");
 672                         break;
 673                     }
 674                     try {
 675                         mConnector.execute("volume", "mount", vol.id, vol.mountFlags,
 676                                 vol.mountUserId);
 677                     } catch (NativeDaemonConnectorException ignored) {
 678                     }
 679                     break;
 680                 }

         再次回到vold

int CommandListener::VolumeCmd::runCommand(SocketClient *cli,
                                           int argc, char **argv) {
        ....
        } else if (cmd == "mount" && argc > 2) {
            // mount [volId] [flags] [user]
           .....
            vol->setMountFlags(mountFlags);
             vol->setMountUserId(mountUserId);

            int res = vol->mount();
            if (mountFlags & android::vold::VolumeBase::MountFlags::kPrimary) {
                vm->setPrimary(vol);
           }
           return sendGenericOkFail(cli, res);
    }

         对于mount命令。首先根据命令找到对应卷,然后调用VolumeBase的mount方法进行挂载。
system/vold/VolumeBase.cpp

status_t VolumeBase::mount() {
    ......
    setState(State::kChecking);
    status_t res = doMount();
   ......
    return res;
}

         VolumeBase的mount方法调用doMount方法,该方法是一个虚函数,对于EmulatedVolume,PublicVolume和PrivateVolume有不同的实现,老规矩我们一一分析。先来分析PublicVolume

system/vold/PublicVolume.cpp

status_t PublicVolume::doMount() {
    // TODO: expand to support mounting other filesystems
    //读取卷的信息
    readMetadata();

    if (mFsType != "vfat") { //Android 只支持vfat格式的PublicVolume,其他类型直接返回
        LOG(ERROR) << getId() << " unsupported filesystem " << mFsType;
        return -EIO;
    }

    if (vfat::Check(mDevPath)) { //再次确认格式无误
        LOG(ERROR) << getId() << " failed filesystem check";
        return -EIO;
    }

    // Use UUID as stable name, if available
    std::string stableName = getId(); // 使用fsuuid作为稳定名称
    if (!mFsUuid.empty()) { 
        stableName = mFsUuid;
    }
    // 准备fuse相关文件夹
    mRawPath = StringPrintf("/mnt/media_rw/%s", stableName.c_str());

    mFuseDefault = StringPrintf("/mnt/runtime/default/%s", stableName.c_str());
    mFuseRead = StringPrintf("/mnt/runtime/read/%s", stableName.c_str());
    mFuseWrite = StringPrintf("/mnt/runtime/write/%s", stableName.c_str());

    setInternalPath(mRawPath);
    if (getMountFlags() & MountFlags::kVisible) {
        setPath(StringPrintf("/storage/%s", stableName.c_str()));
    } else {
        setPath(mRawPath);
    }
    // 创建相关文件夹
    if (fs_prepare_dir(mRawPath.c_str(), 0700, AID_ROOT, AID_ROOT) ||
            fs_prepare_dir(mFuseDefault.c_str(), 0700, AID_ROOT, AID_ROOT) ||
            fs_prepare_dir(mFuseRead.c_str(), 0700, AID_ROOT, AID_ROOT) ||
            fs_prepare_dir(mFuseWrite.c_str(), 0700, AID_ROOT, AID_ROOT)) {
        PLOG(ERROR) << getId() << " failed to create mount points";
        return -errno;
    }
    //挂载设备
    if (vfat::Mount(mDevPath, mRawPath, false, false, false,
            AID_MEDIA_RW, AID_MEDIA_RW, 0007, true)) {
        PLOG(ERROR) << getId() << " failed to mount " << mDevPath;
        return -EIO;
    }

    if (getMountFlags() & MountFlags::kPrimary) {
        initAsecStage();
    }
    // 对app不可见直接返回
    if (!(getMountFlags() & MountFlags::kVisible)) {
        // Not visible to apps, so no need to spin up FUSE
        return OK;
    }

    dev_t before = GetDevice(mFuseWrite);

    if (!(mFusePid = fork())) { 
        if (getMountFlags() & MountFlags::kPrimary) {//对于主存储使用fuse文件系统挂载,使用参数-w,fullwirte模式
            if (execl(kFusePath, kFusePath,
                    "-u", "1023", // AID_MEDIA_RW
                    "-g", "1023", // AID_MEDIA_RW
                    "-U", std::to_string(getMountUserId()).c_str(),
                    "-w",
                    mRawPath.c_str(),
                    stableName.c_str(),
                    NULL)) {
                PLOG(ERROR) << "Failed to exec";
            }
        } else { // 非主存储,不使用fullwrite模式
            if (execl(kFusePath, kFusePath,
                    "-u", "1023", // AID_MEDIA_RW
                    "-g", "1023", // AID_MEDIA_RW
                    "-U", std::to_string(getMountUserId()).c_str(),
                    mRawPath.c_str(),
                    stableName.c_str(),
                    NULL)) {
                PLOG(ERROR) << "Failed to exec";
            }
        }

        LOG(ERROR) << "FUSE exiting";
        _exit(1);
    }

    if (mFusePid == -1) {
        PLOG(ERROR) << getId() << " failed to fork";
        return -errno;
    }

    while (before == GetDevice(mFuseWrite)) { //确保挂载完成。
        LOG(VERBOSE) << "Waiting for FUSE to spin up...";
        usleep(50000); // 50ms
    }

    return OK;
}

         PublicVolume作为外置存储,先通过mount进行挂载,然后为了实现外置存储的读写权限,使用fuse来进行挂载。另外如果设备要对用户可见,就将/storage/${stable} 作为对外使用的路径,我们分析外置存储的时候会对这个路径加以说明。如果这个PublicVolume作为主存储,需要使用fuse的fullwrite模式,否则不使用该模式。

         PrivateVolume
system/vold/PrivateVolume.cpp

status_t PrivateVolume::doMount() {
    if (readMetadata()) {
        LOG(ERROR) << getId() << " failed to read metadata";
        return -EIO;
    }

    mPath = StringPrintf("/mnt/expand/%s", mFsUuid.c_str());
    setPath(mPath);

    if (PrepareDir(mPath, 0700, AID_ROOT, AID_ROOT)) {
        PLOG(ERROR) << getId() << " failed to create mount point " << mPath;
        return -EIO;
    }

    if (mFsType == "ext4") { //改在ext4格式的dm设备
        int res = ext4::Check(mDmDevPath, mPath);
        if (res == 0 || res == 1) {
            LOG(DEBUG) << getId() << " passed filesystem check";
        } else {
            PLOG(ERROR) << getId() << " failed filesystem check";
            return -EIO;
        }

        if (ext4::Mount(mDmDevPath, mPath, false, false, true)) {
            PLOG(ERROR) << getId() << " failed to mount";
            return -EIO;
        }

    } else if (mFsType == "f2fs") {// 挂载fsfs类型的dm设备
        int res = f2fs::Check(mDmDevPath);
        if (res == 0) {
            LOG(DEBUG) << getId() << " passed filesystem check";
        } else {
            PLOG(ERROR) << getId() << " failed filesystem check";
            return -EIO;
        }

        if (f2fs::Mount(mDmDevPath, mPath)) {
            PLOG(ERROR) << getId() << " failed to mount";
            return -EIO;
        }

    } else {
        LOG(ERROR) << getId() << " unsupported filesystem " << mFsType;
        return -EIO;
    }

    LOG(VERBOSE) << "Starting restorecon of " << mPath;

    // TODO: find a cleaner way of waiting for restorecon to finish
    property_set("selinux.restorecon_recursive", "");
    property_set("selinux.restorecon_recursive", mPath.c_str());

    char value[PROPERTY_VALUE_MAX];
    while (true) {
        property_get("selinux.restorecon_recursive", value, "");
        if (strcmp(mPath.c_str(), value) == 0) {
            break;
        }
        sleep(1);
        LOG(VERBOSE) << "Waiting for restorecon...";
    }

    LOG(VERBOSE) << "Finished restorecon of " << mPath;
    // 准备目录
    // Verify that common directories are ready to roll
    if (PrepareDir(mPath + "/app", 0771, AID_SYSTEM, AID_SYSTEM) ||
            PrepareDir(mPath + "/user", 0711, AID_SYSTEM, AID_SYSTEM) ||
            PrepareDir(mPath + "/media", 0770, AID_MEDIA_RW, AID_MEDIA_RW) ||
            PrepareDir(mPath + "/media/0", 0770, AID_MEDIA_RW, AID_MEDIA_RW) ||
            PrepareDir(mPath + "/local", 0751, AID_ROOT, AID_ROOT) ||
            PrepareDir(mPath + "/local/tmp", 0771, AID_SHELL, AID_SHELL)) {
        PLOG(ERROR) << getId() << " failed to prepare";
        return -EIO;
    }

    // Create a new emulated volume stacked above us, it will automatically
    // be destroyed during unmount
    // 准备media目录。生成模拟卷来模拟meida目录。
    std::string mediaPath(mPath + "/media");
    auto vol = std::shared_ptr<VolumeBase>(
            new EmulatedVolume(mediaPath, mRawDevice, mFsUuid));
    addVolume(vol);
    vol->create();

    return OK;
}

        PrivateVolume使用外置存储来模拟内置存储,需要挂载mDmDevPath作为存储设备(需要对数据进行加密/解密),然后对为该设备的media节点创建一个EmulatedVolume,来模拟外置存储使用。这里总结下EmulatedVolume, /data分区作为一个内置存储,如果使用模拟分区作为主存储则会创建一个EmulatedVolume来模拟外置存储,目录为/data/media。 其他的PrivateVolume下再这里创建一个EmulatedVolume,会通过MountService有模拟分区创建,如果该模拟分区是主存储分区,则会挂载该模拟分区,如果不是主分区,则不会挂载该模拟分区,为PrivateVolume创建EmulatedVolume,并不一定会被挂载,只有模拟的主分区才会被挂载。读者可以回过头来看看MountService中处理新家卷的时候对EmulatedVolume的处理,如果系统设置了使用PrivateVolume作为主存储的话,可以将主存储建迁移PrivateVolume对应的EmulatedVolume,我们在存储格式化的文章中将会看到这一点。
system/vold/EmulatedVolume.cpp

status_t EmulatedVolume::doMount() {
    // We could have migrated storage to an adopted private volume, so always
    // call primary storage "emulated" to avoid media rescans.
    std::string label = mLabel;
    if (getMountFlags() & MountFlags::kPrimary) {
        label = "emulated";
    }

    mFuseDefault = StringPrintf("/mnt/runtime/default/%s", label.c_str());
    mFuseRead = StringPrintf("/mnt/runtime/read/%s", label.c_str());
    mFuseWrite = StringPrintf("/mnt/runtime/write/%s", label.c_str());

    setInternalPath(mRawPath);
    setPath(StringPrintf("/storage/%s", label.c_str()));

    if (fs_prepare_dir(mFuseDefault.c_str(), 0700, AID_ROOT, AID_ROOT) ||
            fs_prepare_dir(mFuseRead.c_str(), 0700, AID_ROOT, AID_ROOT) ||
            fs_prepare_dir(mFuseWrite.c_str(), 0700, AID_ROOT, AID_ROOT)) {
        PLOG(ERROR) << getId() << " failed to create mount points";
        return -errno;
    }

    dev_t before = GetDevice(mFuseWrite);

    if (!(mFusePid = fork())) {
        if (execl(kFusePath, kFusePath,
                "-u", "1023", // AID_MEDIA_RW
                "-g", "1023", // AID_MEDIA_RW
                "-m",
                "-w",
                mRawPath.c_str(),
                label.c_str(),
                NULL)) {
            PLOG(ERROR) << "Failed to exec";
        }

        LOG(ERROR) << "FUSE exiting";
        _exit(1);
    }

    if (mFusePid == -1) {
        PLOG(ERROR) << getId() << " failed to fork";
        return -errno;
    }

    while (before == GetDevice(mFuseWrite)) {
        LOG(VERBOSE) << "Waiting for FUSE to spin up...";
        usleep(50000); // 50ms
    }

    return OK;
}

如果一个EmulatedVolume被挂载,它一定是一个主分区,没啥可说对mRawPath目录做fuse模拟。

到这里外接存储的挂载就基本解释完了,由于篇幅的关系,对于外接磁盘的格式化和多用户的管理我们再分两篇文章来完成。

2. 格式化

Android存储系统-MountService 和vold 对外置存储的管理(2)

3. 多用户

Android存储系统-MountService 和vold 对外置存储的管理(3)

  • 9
    点赞
  • 37
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值