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

前言

        前边我在Android存储系统-MountService 和vold 对外置存储的管理(1) 中说了Android系统的磁盘挂载,知道外接磁盘可以格式化为PublicVolume分区, PublicVolume分区的设备可以在其他设备上进行挂载使用。设置了ro.vold.primary_physical=true属性的Android手机默认使用外接存储作为主分区。 PrivateVolume分区用于将外置存储转为内置存储使用的,并且PrivateVolume是使用全盘加密的。所以PrivateVolume是和Android设备强相关的,把包含PrivateVolume的磁盘介质拿到其他手机上是无法解密挂载的。所以PrivateVolume必然是在Android设备上来进行格式化的。每个PrivateVolume还对应一个EmulatedVolume分区,这个EmulatedVolume可以作为主分区挂载。 对于/data分区的挂载不由Vold服务来管理,所以MountService会主动添加一个PrivateVolume来对应/data分区,Vold服务会主动添加一个EmulatedVolume来对应/data这个PrivateVolume分区。当没有其他设备被格式化为PrivateVolume且要求使用Private分区来作为主存储的时候,就使用/data分区对应的EmulatedVolume来作为主存储。 当我们格式化了一个PrivateVolume的时候,我们可以选择将主分区移动到新增的这个PrivateVolume对应的EmulatedVolume上。

        EmulatedVolume 为主分区的时才会被挂载,一个EmulatedVolume一般使用PrivateVolume挂载的根目录下的media目录,作为fuse的后端使用,使用fuse主要方便存储权限的管理,提供更灵活的权限管理机制。我们会在后面还分析fuse实现的存储目录。下面我们来分析下磁盘的格式化过程。

代码分析

        在Android存储系统-MountService 和vold 对外置存储的管理(1) 一文中我们知道当一个磁盘插入手机之后会通过uevent机制将设备插入事件通知vold服务,vold服务会对磁盘进行扫面,然后读取磁盘分区,我们来回顾下读取分区的代码。
system/vold/Disk.cpp

status_t Disk::readPartitions() {
    int8_t maxMinors = getMaxMinors();
    if (maxMinors < 0) {
        return -ENOTSUP;
    }

    destroyAllVolumes();

    // Parse partition table

    std::vector<std::string> cmd;
    cmd.push_back(kSgdiskPath);
    cmd.push_back("--android-dump");
    cmd.push_back(mDevPath);

    std::vector<std::string> output;
    status_t res = ForkExecvp(cmd, output);
    if (res != OK) {
        LOG(WARNING) << "sgdisk failed to scan " << mDevPath;
        notifyEvent(ResponseCode::DiskScanned);
        mJustPartitioned = false;
        return res;
    }
    ......

    notifyEvent(ResponseCode::DiskScanned);
    mJustPartitioned = false;
    return OK;
}

        读取磁盘分区不论成功还是失败都会发送ResponseCode::DiskScanned消息给MountService,接下来看下MountService如何处理DiskScanned消息。

    private boolean onEventLocked(int code, String raw, String[] cooked) {
     ......
         case VoldResponseCode.DISK_SCANNED: {
                if (cooked.length != 2) break;
                final DiskInfo disk = mDisks.get(cooked[1]);
                if (disk != null) {
                    onDiskScannedLocked(disk);
                }
                break;
            }
            ......
    }

    private void onDiskScannedLocked(DiskInfo disk) {
        int volumeCount = 0;
        ......
        disk.volumeCount = volumeCount;
        mCallbacks.notifyDiskScanned(disk, volumeCount);
    }

        上面的代码中略去了一些我们不关注的细节,代码最终调用mCallbacks.notifyDiskScanned(disk, volumeCount) 发送了一个通知。mCallbacks的实现也在MountService中,定义如下:

3577     private static class Callbacks extends Handler {
             ......
3585         private final RemoteCallbackList<IMountServiceListener>
3586                 mCallbacks = new RemoteCallbackList<>();
             ......
3592         public void register(IMountServiceListener callback) {
3593             mCallbacks.register(callback);
3594         }
3595 
             ......
3600         @Override
3601         public void handleMessage(Message msg) {
3602             final SomeArgs args = (SomeArgs) msg.obj;
3603             final int n = mCallbacks.beginBroadcast();
3604             for (int i = 0; i < n; i++) {
3605                 final IMountServiceListener callback = mCallbacks.getBroadcastItem(i);
3606                 try {
3607                     invokeCallback(callback, msg.what, args);
3608                 } catch (RemoteException ignored) {
3609                 }
3610             }
3611             mCallbacks.finishBroadcast();
3612             args.recycle();
3613         }
3614 
3615         private void invokeCallback(IMountServiceListener callback, int what, SomeArgs args)
3616                 throws RemoteException {
3617             switch (what) {
3618                 ......
3635                 case MSG_DISK_SCANNED: {
3636                     callback.onDiskScanned((DiskInfo) args.arg1, args.argi2);
3637                     break;
3638                 }
                    ......
3643             }
3644         }
3645 
             ......
3674         private void notifyDiskScanned(DiskInfo disk, int volumeCount) {
3675             final SomeArgs args = SomeArgs.obtain();
3676             args.arg1 = disk.clone();
3677             args.argi2 = volumeCount;
3678             obtainMessage(MSG_DISK_SCANNED, args).sendToTarget();
3679         }
3680 
           ......
3686     }

        Callbacks要实现的功能就是进行会调,需要关注磁盘变化的应用会通过3592行的register函数订阅事件。然后事件触发就会调用注册的Callback来进行通知,这是Android实现的跨进程回调的模板。 所以我们要关注的是谁注册了Callback,才能知道对磁盘扫描事件的处理。我们感兴趣的注册端在Systemui进程中。

frameworks/base/packages/SystemUI/src/com/android/systemui/usb/StorageNotification.java

public class StorageNotification extends SystemUI {
    @Override
    public void start() {
        mStorageManager.registerListener(mListener);
        ......
    }
}

        mStorageManager.registerListener(mListener)就是注册CallBack到MountService的Callbacks,具体细节不是我们关注的重点,所谓直接看下mListener对Disk_Scan的处理。

    private final StorageEventListener mListener = new StorageEventListener() {
       ......
        @Override
        public void onDiskScanned(DiskInfo disk, int volumeCount) {
            onDiskScannedInternal(disk, volumeCount);
        }
        ......
    };
220     private void onDiskScannedInternal(DiskInfo disk, int volumeCount) {
221         if (volumeCount == 0 && disk.size > 0) {  //磁盘读取分区失败,发送通知
222             // No supported volumes found, give user option to format
223             final CharSequence title = mContext.getString(
224                     R.string.ext_media_unsupported_notification_title, disk.getDescription());
225             final CharSequence text = mContext.getString(
226                     R.string.ext_media_unsupported_notification_message, disk.getDescription());
227 
228             Notification.Builder builder = new Notification.Builder(mContext)
229                     .setSmallIcon(getSmallIcon(disk, VolumeInfo.STATE_UNMOUNTABLE))
230                     .setColor(mContext.getColor(R.color.system_notification_accent_color))
231                     .setContentTitle(title)
232                     .setContentText(text)
233                     .setContentIntent(buildInitPendingIntent(disk))
234                     .setStyle(new Notification.BigTextStyle().bigText(text))
235                     .setVisibility(Notification.VISIBILITY_PUBLIC)
236                     .setLocalOnly(true)
237                     .setCategory(Notification.CATEGORY_ERROR);
238             SystemUI.overrideNotificationAppName(mContext, builder);
239 
240             mNotificationManager.notifyAsUser(disk.getId(), DISK_ID, builder.build(),
241                     UserHandle.ALL);
242 
243         } else { // 分区读取成功,取消通知
244             // Yay, we have volumes!
245             mNotificationManager.cancelAsUser(disk.getId(), DISK_ID, UserHandle.ALL);
246         }
247     }

        对于磁盘扫描结果分为两种情况, 220-242行为读取磁盘分区失败的情况,这种情况需要对磁盘进行重新格式化才能使用,所以会发出通知。 另外一种情况(244-245行)为磁盘分区读取成功,这种情况要尝试取消之前发出去的磁盘分区读取失败通知。通知内容如下:xxx磁盘已损坏。
)

         所以通知的点击动作应该是进行格式化磁盘,设置点击相应函数为buildInitPendingIntent(disk)。

    private PendingIntent buildInitPendingIntent(DiskInfo disk) {
        final Intent intent = new Intent();
        intent.setClassName("com.android.settings",
                "com.android.settings.deviceinfo.StorageWizardInit");
        intent.putExtra(DiskInfo.EXTRA_DISK_ID, disk.getId());

        final int requestKey = disk.getId().hashCode();
        return PendingIntent.getActivityAsUser(mContext, requestKey, intent,
                PendingIntent.FLAG_CANCEL_CURRENT, null, UserHandle.CURRENT);
    }

        buildInitPendingIntent 函数设置点击通知的处理方法为跳转到com.android.settings.deviceinfo.StorageWizardInit页面。 所以接下来就是StorageWizardInit页面了。

        StorageWizardInit界面如上图,可以将磁盘格式化为便携式存储(PublicVolume)和用作内部存储(PrivateVolume)两种选项。但是有一点需要注意,如果一个磁盘设备不支持加密,是没有用作内部存储选项的。 格式话成便携式存储的处理比较简单我们就不分析了,我们来分析下格式化为内部存储选项的情况,,也就是创建PrivateVolume分区。

packages/apps/Settings/src/com/android/settings/deviceinfo/StorageWizardInit.java

    @Override
    public void onNavigateNext() {
        if (mRadioExternal.isChecked()) {
           ......

        } else if (mRadioInternal.isChecked()) {
            final Intent intent = new Intent(this, StorageWizardFormatConfirm.class);
            intent.putExtra(DiskInfo.EXTRA_DISK_ID, mDisk.getId());
            intent.putExtra(StorageWizardFormatConfirm.EXTRA_FORMAT_PRIVATE, true);
            startActivity(intent);
        }
    }

        选择格式化为内部存储设备之后进入的页面就是StorageWizardFormatConfirm页面,如下图:

        StorageWizardFormatConfirm 页面只是提示作用,并无具体逻辑。
packages/apps/Settings/src/com/android/settings/deviceinfo/StorageWizardFormatConfirm.java

    @Override
    public void onNavigateNext() {
        final Intent intent = new Intent(this, StorageWizardFormatProgress.class);
        intent.putExtra(DiskInfo.EXTRA_DISK_ID, mDisk.getId());
        intent.putExtra(EXTRA_FORMAT_PRIVATE, mFormatPrivate);
        intent.putExtra(EXTRA_FORGET_UUID, getIntent().getStringExtra(EXTRA_FORGET_UUID));
        startActivity(intent);
        finishAffinity();
    }

        StorageWizardFormatConfirm 页面下点击清空并格式化后将跳转到StorageWizardFormatProgress页面,该页面会请求发起格式化操作。StorageWizardFormatProgress页面如下图所示:

        StorageWizardFormatProgress会创建一个PartitionTask任务来进行格式化, PartitionTask继承自AsyncTask,所以doInBackground为主要工作逻辑:
packages/apps/Settings/src/com/android/settings/deviceinfo/StorageWizardFormatProgress.java

 84     public static class PartitionTask extends AsyncTask<Void, Integer, Exception> {
 85         public StorageWizardFormatProgress mActivity;
 86 
 87         private volatile int mProgress = 20;
 88 
 89         private volatile long mInternalBench;
 90         private volatile long mPrivateBench;
 91 
 92         @Override
 93         protected Exception doInBackground(Void... params) {
 94             final StorageWizardFormatProgress activity = mActivity;
 95             final StorageManager storage = mActivity.mStorage;
 96             try {
 97                 if (activity.mFormatPrivate) {  //格式化为PrivateVolume的处理
 98                     storage.partitionPrivate(activity.mDisk.getId()); //格式化
 99                     publishProgress(40);
100 
101                     mInternalBench = storage.benchmark(null); //基准测试
102                     publishProgress(60);
103 
104                     final VolumeInfo privateVol = activity.findFirstVolume(VolumeInfo.TYPE_PRIVATE);
105                     mPrivateBench = storage.benchmark(privateVol.getId()); //分区基准测试
106 
107                     // If we just adopted the device that had been providing
108                     // physical storage, then automatically move storage to the
109                     // new emulated volume.
110                     if (activity.mDisk.isDefaultPrimary()
111                             && Objects.equals(storage.getPrimaryStorageUuid(),
112                                     StorageManager.UUID_PRIMARY_PHYSICAL)) { //作为主分区使用,更新主分区uuid
113                         Log.d(TAG, "Just formatted primary physical; silently moving "
114                                 + "storage to new emulated volume");
115                         storage.setPrimaryStorageUuid(privateVol.getFsUuid(), new SilentObserver());
116                     }
117 
118                 } else { //格式化为PublicVolume的处理
119                     storage.partitionPublic(activity.mDisk.getId());
120                 }
121                 return null;
122             } catch (Exception e) {
123                 return e;
124             }
125         }
            ......
196     }

        代码119行代码分支主要将磁盘格式化为便携式存储,不是我们关注的点,这里不进行分析。98-116行用于在磁盘上创建PublicVolume分区, 其中98行是主要的格式化操作, 101行对/data分区进行基准测试, 105行对新创建的PrivateVolume分区做了基准测试,110-115行判断新创建的分区是否可以作为主分区使用,如果可以作为主分区使用则调用MountService来更新主分区。基准测试的目的是对比/data分区和新创建的PrivateVolume性能,如果新创建的分区性能太弱,会提示用户新设备速度慢,慎重移动应用数据到新分区。
        我们按照创建PrivateVolume的过程来进行分析,所以先来分析storage.partitionPrivate(activity.mDisk.getId()), 该函数会通过binder调用到MountService中,如下

frameworks/base/services/core/java/com/android/server/MountService.java

    @Override
    public void partitionPrivate(String diskId) {
        enforcePermission(android.Manifest.permission.MOUNT_FORMAT_FILESYSTEMS);
        enforceAdminUser();
        waitForReady();

        final CountDownLatch latch = findOrCreateDiskScanLatch(diskId);
        try {
            mConnector.execute("volume", "partition", diskId, "private");
            waitForLatch(latch, "partitionPrivate", 3 * DateUtils.MINUTE_IN_MILLIS);
        } catch (NativeDaemonConnectorException e) {
            throw e.rethrowAsParcelableException();
        } catch (TimeoutException e) {
            throw new IllegalStateException(e);
        }
    }

        通过Socket调用到vold服务,vold对创建分区命令处理如下

system/vold/CommandListener.cpp

int CommandListener::VolumeCmd::runCommand(SocketClient *cli,
                                           int argc, char **argv) {
    ......
    else if (cmd == "partition" && argc > 3) {
        // partition [diskId] [public|private|mixed] [ratio]
        std::string id(argv[2]);
        auto disk = vm->findDisk(id);
        if (disk == nullptr) {
            return cli->sendMsg(ResponseCode::CommandSyntaxError, "Unknown disk", false);
        }

        std::string type(argv[3]);
        if (type == "public") {
            return sendGenericOkFail(cli, disk->partitionPublic());
        } else if (type == "private") {
            return sendGenericOkFail(cli, disk->partitionPrivate());
        } else if (type == "mixed") {
            if (argc < 4) {
                return cli->sendMsg(ResponseCode::CommandSyntaxError, nullptr, false);
            }
            int frac = atoi(argv[4]);
            return sendGenericOkFail(cli, disk->partitionMixed(frac));
        } else {
            return cli->sendMsg(ResponseCode::CommandSyntaxError, nullptr, false);
        }

    } 
    ......
}

        partition命令最终实现是通过disk->partitionPrivate()来完成的。
system/vold/Disk.cpp

status_t Disk::partitionPrivate() {
    return partitionMixed(0);
}

        partitionPrivate调用partitionMixed来创建私有分区,partitionMixed用于在一块磁盘上按照一定的比例来创建一个PublicVolume和一个PrivateVolume分区,这里参数为0 表示只创建PrivateVolume。partitionMixed同样为Disk类的方法。

438 status_t Disk::partitionMixed(int8_t ratio) {
        ......
442     mJustPartitioned = true;  // 1 设置磁盘正在创建分区状态
443 
444     // First nuke any existing partition table
445     std::vector<std::string> cmd;
446     cmd.push_back(kSgdiskPath);
447     cmd.push_back("--zap-all");
448     cmd.push_back(mDevPath);
449 
450     // Zap sometimes returns an error when it actually succeeded, so
451     // just log as warning and keep rolling forward.
452     if ((res = ForkExecvp(cmd)) != 0) { // 2 /system/bin/sgdisk --zap-all ${devpath}  清空磁盘分区
453         LOG(WARNING) << "Failed to zap; status " << res;
454     }
455 
456     // We've had some success above, so generate both the private partition
457     // GUID and encryption key and persist them.
458     std::string partGuidRaw;
459     std::string keyRaw;
460     if (ReadRandomBytes(16, partGuidRaw) || ReadRandomBytes(16, keyRaw)) { //3 生成partguid 和 主秘钥
461         LOG(ERROR) << "Failed to generate GUID or key";
462         return -EIO;
463     }
464 
465     std::string partGuid;
466     StrToHex(partGuidRaw, partGuid); 
467 
468     if (!WriteStringToFile(keyRaw, BuildKeyPath(partGuid))) { // 4 将主秘钥保存到/data/misc/vold/${partGuid} 文件,用于加密解密数据使用
469         LOG(ERROR) << "Failed to persist key";
470         return -EIO;
471     } else {
472         LOG(DEBUG) << "Persisted key for GUID " << partGuid;
473     }
474 
475     // Now let's build the new GPT table. We heavily rely on sgdisk to
476     // force optimal alignment on the created partitions.
477     cmd.clear();
478     cmd.push_back(kSgdiskPath);
479 
480     // If requested, create a public partition first. Mixed-mode partitioning
481     // like this is an experimental feature.
482     if (ratio > 0) { // 5 ratio参数大于0,根据比例创建PublicVolume 分区,注意PublicVolume分区的--typecode为kGptBasicData
483         if (ratio < 10 || ratio > 90) {
484             LOG(ERROR) << "Mixed partition ratio must be between 10-90%";
485             return -EINVAL;
486         }
487 
488         uint64_t splitMb = ((mSize / 100) * ratio) / 1024 / 1024;
489         cmd.push_back(StringPrintf("--new=0:0:+%" PRId64 "M", splitMb));
490         cmd.push_back(StringPrintf("--typecode=0:%s", kGptBasicData));
491         cmd.push_back("--change-name=0:shared");
492     }
493 
494     // Define a metadata partition which is designed for future use; there
495     // should only be one of these per physical device, even if there are
496     // multiple private volumes.
497     cmd.push_back("--new=0:0:+16M");           // 6 创建16M的 AndroidMeta分区,供未来使用,--typecode为kGptAndroidMeta。
498     cmd.push_back(StringPrintf("--typecode=0:%s", kGptAndroidMeta));
499     cmd.push_back("--change-name=0:android_meta");
500 
501     // Define a single private partition filling the rest of disk.
502     cmd.push_back("--new=0:0:-0");  // 7 剩余空间创建为PrivateVolume分区,--typecode为kGptAndroidExpand
503     cmd.push_back(StringPrintf("--typecode=0:%s", kGptAndroidExpand));
504     cmd.push_back(StringPrintf("--partition-guid=0:%s", partGuid.c_str()));
505     cmd.push_back("--change-name=0:android_expand");
506 
507     cmd.push_back(mDevPath);
508 
509     if ((res = ForkExecvp(cmd)) != 0) {  //8 执行分区创建命令。
510         LOG(ERROR) << "Failed to partition; status " << res;
511         return res;
512     }
513 
514     return OK;
515 }

         partitionMixed的作用是根据ratio参数来创建一个PublicVolume和一个PrivateVolume分区,当ratio<=0的时候表示不创建PublicVolume分区,只创建PrivateVolume。 partitionMixed创建分区主要使用/system/bin/sgdisk命令来完成。创建分区前要擦除设备原有分区表,使用命令

/system/bin/sgdisk --zap-all ${devpath}

擦除设备分区后可以进行新分区的建立了,命令如下

/system/bin/sgdisk --new=0:0:+${PublicVolume size}M --typecode=0:EBD0A0A2-B9E5-4433-87C0-68B6B72699C7 --new=0:0:+16M --typecode=0:19A710A2-B3CA-11E4-B026-10604B889DCF --change-name=0:android_meta --new=0:0:-0 --typecode=0:193D1EA4-B3CA-11E4-B075-10604B889DCF --partition-guid=0:${partGuid} --change-name=0:android_expand

        sgdisk命令比较简单 ,这里创建了三个分区,第一个分区为PublicVolume分区,大小为指定ratio决定,第二个分区为一个16M的Android meta分区, 大小为16m, 第三个分区为Android expand分区,磁盘剩余大小全都划分到该分区内。 16M的Android meta分区用于日后扩展使用。 如果ratio为0 则不创建PublicVolume分区。 最后值得注意的是三种分区的typecode是不同的,这也是Disk.readPartitions()函数确定分区类型的依据。 分区创建后就会触发uevent相关事件,来触发MountService对分区的挂载,这个过程我们已经相当熟悉了,不熟悉的请参阅 Android存储系统-MountService 和vold 对外置存储的管理(1)

         到这里我们看到了PrivateVolume的格式化,我们回过头来看下挂载分区过程中的PrivateVolume函数,这个函数用于初始化PrivateVolume。

system/vold/PrivateVolume.cpp

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;
}

         前面我们只说了cryptfs_setup_ext_volume函数用于做device mapping,将mRawDevPath映射到crypto_blkdev,然后对crypto_blkdev进行挂载,之后写入的数据就会被加密写入到mRawDevPath对应的设备。读crypto_blkdev设备的时候数据实际是从mRawDevPath读出,经过解密写入用户提供的缓冲区。当时我们并没有进入到cryptfs_setup_ext_volume函数来分析,现在我们可以进行分析了。

system/vold/cryptfs.c

/*
 * Called by vold when it's asked to mount an encrypted external
 * storage volume. The incoming partition has no crypto header/footer,
 * as any metadata is been stored in a separate, small partition.
 *
 * out_crypto_blkdev must be MAXPATHLEN.
 */
int cryptfs_setup_ext_volume(const char* label, const char* real_blkdev,
        const unsigned char* key, int keysize, char* out_crypto_blkdev) {
    int fd = open(real_blkdev, O_RDONLY|O_CLOEXEC);
    if (fd == -1) {
        SLOGE("Failed to open %s: %s", real_blkdev, strerror(errno));
        return -1;
    }

    unsigned long nr_sec = 0;
    get_blkdev_size(fd, &nr_sec);
    close(fd);

    if (nr_sec == 0) {
        SLOGE("Failed to get size of %s: %s", real_blkdev, strerror(errno));
        return -1;
    }

    struct crypt_mnt_ftr ext_crypt_ftr;
    memset(&ext_crypt_ftr, 0, sizeof(ext_crypt_ftr));
    ext_crypt_ftr.fs_size = nr_sec;
    ext_crypt_ftr.keysize = keysize;
    strcpy((char*) ext_crypt_ftr.crypto_type_name, "aes-cbc-essiv:sha256");

    return create_crypto_blk_dev(&ext_crypt_ftr, key, real_blkdev,
            out_crypto_blkdev, label);
}

         函数有5个参数,第一个参数为分区的label,也就是分区名称,第二个参数real_blkdev是我们需要进行映射的块设备,第三个参数key就是我们创建PrivateVolume的时候生成的主秘钥,第四个参数keysize为主密钥的长度,第五个参数out_crypto_blkdev为一个传出参数,表示real_blkdev被映射后的设备路径。

         这一层主要为设备映射准备参数,实际工作函数为create_crypto_blk_dev。

system/vold/cryptfs.c

1154 static int create_crypto_blk_dev(struct crypt_mnt_ftr *crypt_ftr,
1155         const unsigned char *master_key, const char *real_blk_name,
1156         char *crypto_blk_name, const char *name) {
1157   char buffer[DM_CRYPT_BUF_SIZE];
1158   struct dm_ioctl *io;
1159   unsigned int minor;
1160   int fd=0;
1161   int err;
1162   int retval = -1;
1163   int version[3];
1164   char *extra_params;
1165   int load_count;
1166 
1167   if ((fd = open("/dev/device-mapper", O_RDWR|O_CLOEXEC)) < 0 ) { // 1 打开/dev/device-mapper设备
1168     SLOGE("Cannot open device-mapper\n");
1169     goto errout;
1170   }
1171 
1172   io = (struct dm_ioctl *) buffer;
1173 
1174   ioctl_init(io, DM_CRYPT_BUF_SIZE, name, 0);
1175   err = ioctl(fd, DM_DEV_CREATE, io); // 2 创建dm设备
1176   if (err) {
1177     SLOGE("Cannot create dm-crypt device %s: %s\n", name, strerror(errno));
1178     goto errout;
1179   }
1180 
1181   /* Get the device status, in particular, the name of it's device file */
1182   ioctl_init(io, DM_CRYPT_BUF_SIZE, name, 0);
1183   if (ioctl(fd, DM_DEV_STATUS, io)) {  // 2 获取状态
1184     SLOGE("Cannot retrieve dm-crypt device status\n");
1185     goto errout;
1186   }
1187   minor = (io->dev & 0xff) | ((io->dev >> 12) & 0xfff00);
1188   snprintf(crypto_blk_name, MAXPATHLEN, "/dev/block/dm-%u", minor);
1189 
1190   extra_params = "";
1191   if (! get_dm_crypt_version(fd, name, version)) { //3 获取支持的加密版本
1192       /* Support for allow_discards was added in version 1.11.0 */
1193       if ((version[0] >= 2) ||
1194           ((version[0] == 1) && (version[1] >= 11))) {
1195           extra_params = "1 allow_discards";
1196           SLOGI("Enabling support for allow_discards in dmcrypt.\n");
1197       }
1198   }
1199 
1200   load_count = load_crypto_mapping_table(crypt_ftr, master_key, real_blk_name, name,
1201                                          fd, extra_params);  // 4 与target driver建立映射
1202   if (load_count < 0) {
1203       SLOGE("Cannot load dm-crypt mapping table.\n");
1204       goto errout;
1205   } else if (load_count > 1) {
1206       SLOGI("Took %d tries to load dmcrypt table.\n", load_count);
1207   }
1208 
1209   /* Resume this device to activate it */
1210   ioctl_init(io, DM_CRYPT_BUF_SIZE, name, 0); 
1211 
1212   if (ioctl(fd, DM_DEV_SUSPEND, io)) { // 5 设备设备为活跃状态
1213     SLOGE("Cannot resume the dm-crypt device\n");
1214     goto errout;
1215   }
1216 
1217   /* We made it here with no errors.  Woot! */
1218   retval = 0;
1219 
1220 errout:
1221   close(fd);   /* If fd is <0 from a failed open call, it's safe to just ignore the close error */
1222 
1223   return retval;
1224 }

         关于设备映射的过程这里不打算详细分析,粗略过程请参见注释,感兴趣的读者可以阅读Linux 内核中的 Device Mapper 机制 这篇文章。 我们回过头来继续分析PrivateVolume的基准测试过程,基准测试的实际命令发起也是在MountService中:

    @Override
    public long benchmark(String volId) {
       ......   
         // TODO: make benchmark async so we don't block other commands
            final NativeDaemonEvent res = mConnector.execute(3 * DateUtils.MINUTE_IN_MILLIS,
                    "volume", "benchmark", volId);
            return Long.parseLong(res.getMessage());
      ......
    }
int CommandListener::VolumeCmd::runCommand(SocketClient *cli,
                                           int argc, char **argv)
    ......                                       
     } else if (cmd == "benchmark" && argc > 2) {
        // benchmark [volId]
        std::string id(argv[2]);
        nsecs_t res = vm->benchmarkPrivate(id);
        return cli->sendMsg(ResponseCode::CommandOkay,
                android::base::StringPrintf("%" PRId64, res).c_str(), false);
    } 
    ......
nsecs_t VolumeManager::benchmarkPrivate(const std::string& id) {
    std::string path;
    if (id == "private" || id == "null") {
        path = "/data";
    } else {
        auto vol = findVolume(id);
        if (vol != nullptr && vol->getState() == android::vold::VolumeBase::State::kMounted) {
            path = vol->getPath();
        }
    }

    if (path.empty()) {
        LOG(WARNING) << "Failed to find volume for " << id;
        return -1;
    }

    return android::vold::BenchmarkPrivate(path);
}

        如果没有指定id参数,则使用/data分区作为基准测试的目标,否则根据分区id来找到目录进行基准测试。 基准测试的代码我们也不深入分析了,就是一个跑分的过程。 继续分析新分区的创建过程,当一个新分区作为主分区的时候就会调用storage.setPrimaryStorageUuid(privateVol.getFsUuid(), new SilentObserver());函数来设置主存储。

    public void setPrimaryStorageUuid(String volumeUuid, IPackageMoveObserver callback) {
        try {
            mMountService.setPrimaryStorageUuid(volumeUuid, callback);
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

        函数最终调用MountService的setPrimaryStorageUuid函数。

frameworks/base/services/core/java/com/android/server/MountService.java

2029     @Override
2030     public void setPrimaryStorageUuid(String volumeUuid, IPackageMoveObserver callback) {
2031         enforcePermission(android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS);
2032         waitForReady();
2033 
2034         final VolumeInfo from;
2035         final VolumeInfo to;
2036 
2037         synchronized (mLock) {
2038             if (Objects.equals(mPrimaryStorageUuid, volumeUuid)) {
2039                 throw new IllegalArgumentException("Primary storage already at " + volumeUuid);
2040             }
2041 
2042             if (mMoveCallback != null) {
2043                 throw new IllegalStateException("Move already in progress");
2044             }
2045             mMoveCallback = callback;
2046             mMoveTargetUuid = volumeUuid;
2047 
2048             // When moving to/from primary physical volume, we probably just nuked
2049             // the current storage location, so we have nothing to move.
2050             if (Objects.equals(StorageManager.UUID_PRIMARY_PHYSICAL, mPrimaryStorageUuid)
2051                     || Objects.equals(StorageManager.UUID_PRIMARY_PHYSICAL, volumeUuid)) {
2052                 Slog.d(TAG, "Skipping move to/from primary physical");
2053                 onMoveStatusLocked(MOVE_STATUS_COPY_FINISHED);
2054                 onMoveStatusLocked(PackageManager.MOVE_SUCCEEDED);
2055                 mHandler.obtainMessage(H_RESET).sendToTarget();
2056                 return;
2057 
2058             } else {
2059                 from = findStorageForUuid(mPrimaryStorageUuid);
2060                 to = findStorageForUuid(volumeUuid);
2061 
2062                 if (from == null) {
2063                     Slog.w(TAG, "Failing move due to missing from volume " + mPrimaryStorageUuid);
2064                     onMoveStatusLocked(PackageManager.MOVE_FAILED_INTERNAL_ERROR);
2065                     return;
2066                 } else if (to == null) {
2067                     Slog.w(TAG, "Failing move due to missing to volume " + volumeUuid);
2068                     onMoveStatusLocked(PackageManager.MOVE_FAILED_INTERNAL_ERROR);
2069                     return;
2070                 }
2071             }
2072         }
2073 
2074         try {
2075             mConnector.execute("volume", "move_storage", from.id, to.id);
2076         } catch (NativeDaemonConnectorException e) {
2077             throw e.rethrowAsParcelableException();
2078         }
2079     }

        2550-2556 如果主设备是一个外接设备,则可能是外接设备移除或者插入的过程,不需要移动任何文件,所以直接发送移动成功消息即可。 2059-2060 找到要移动的源和目的,2075行发送move_storage命令给vold服务。

system/vold/CommandListener.cpp

int CommandListener::VolumeCmd::runCommand(SocketClient *cli,
                                           int argc, char **argv) {
    ......
    } else if (cmd == "move_storage" && argc > 3) {
        // move_storage [fromVolId] [toVolId]
        auto fromVol = vm->findVolume(std::string(argv[2]));
        auto toVol = vm->findVolume(std::string(argv[3]));
        if (fromVol == nullptr || toVol == nullptr) {
            return cli->sendMsg(ResponseCode::CommandSyntaxError, "Unknown volume", false);
        }

        (new android::vold::MoveTask(fromVol, toVol))->start();
        return sendGenericOkFail(cli, 0);

    }
    ......      
}                                      

         代码启动一个MoveTask来移动。这里明显存在内存泄露

system/vold/MoveTask.cpp

void MoveTask::start() {
    mThread = std::thread(&MoveTask::run, this);
}

         MoveTask 创建一个线程来执行run方法

173 void MoveTask::run() {
174     acquire_wake_lock(PARTIAL_WAKE_LOCK, kWakeLock);
175 
176     std::string fromPath;
177     std::string toPath;
178 
179     // TODO: add support for public volumes
180     if (mFrom->getType() != VolumeBase::Type::kEmulated) goto fail;
181     if (mTo->getType() != VolumeBase::Type::kEmulated) goto fail;
182 
183     // Step 1: tear down volumes and mount silently without making
184     // visible to userspace apps
185     {
186         std::lock_guard<std::mutex> lock(VolumeManager::Instance()->getLock());
187         bringOffline(mFrom);
188         bringOffline(mTo);
189     }
190 
191     fromPath = mFrom->getInternalPath();
192     toPath = mTo->getInternalPath();
193 
194     // Step 2: clean up any stale data
195     if (execRm(toPath, 10, 10) != OK) {
196         goto fail;
197     }
198 
199     // Step 3: perform actual copy
200     if (execCp(fromPath, toPath, 20, 60) != OK) {
201         goto copy_fail;
202     }
203 
204     // NOTE: MountService watches for this magic value to know
205     // that move was successful
206     notifyProgress(82);
207     {
208         std::lock_guard<std::mutex> lock(VolumeManager::Instance()->getLock());
209         bringOnline(mFrom);
210         bringOnline(mTo);
211     }
212 
213     // Step 4: clean up old data
214     if (execRm(fromPath, 85, 15) != OK) {
215         goto fail;
216     }
217 
218     notifyProgress(kMoveSucceeded);
219     release_wake_lock(kWakeLock);
220     return;
221 
222 copy_fail:
223     // if we failed to copy the data we should not leave it laying around
224     // in target location. Do not check return value, we can not do any
225     // useful anyway.
226     execRm(toPath, 80, 1);
227 fail:
228     {
229         std::lock_guard<std::mutex> lock(VolumeManager::Instance()->getLock());
230         bringOnline(mFrom);
231         bringOnline(mTo);
232     }
233     notifyProgress(kMoveFailedInternalError);
234     release_wake_lock(kWakeLock);
235     return;
236 }

         函数整体还是比较简单的,首先删除目的盘,然后拷贝源盘的文件到目的盘,最后删除源盘上的文件。

         到这里PrivateVolume的创建我们就分析完了。 我们知道了Android系统使用全盘加密来加密PrivateVolume分区,然后PrivateVolume和PublicVolume都可以作为primary_physical来使用,另外还可以手动设置外接设备为主分区,前提是该设备被格式化为PrivateVolume(这个过程在packages/apps/Settings/src/com/android/settings/deviceinfo/StorageWizardMigrate.java中)。 这里我们更加深了对Vold和MountService对外置存储的管理。 其实应用也可以迁移到PrivateVolume分区,这和应用在AndroidManifest.xml中生名的android:installLocation有关。相关代码以后有机会我们再去分析。

         现在我们来总结下主存储的选择:
1 使用外接设备作为主存储:
ro.vold.primary_physical=true
两种情况:

  • 外接的PublicVolume作为主存储。
  • 外接的PrivateVolume作为主存储。

2. 模拟主存储。
ro.vold.primary_physical=false
使用/data/media 对应的EmulatedVolume作为主存储。

3. 手动选择主存储。
手动选择一个PrivateVolume对应的EmulatedVolume作为主存储。

  • 3
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值