前言
前边我在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作为主存储。