概述
现状介绍
由于技术和成本的问题,早期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手机上的外置存储。
整体架构
如上图,整个架构分为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的基本类图如上图, 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);
}
总结下启动的主要几个步骤:
- 启动VolumeManager。
- 解析fstab生成DiskSource数据结构。
- 启动NetlinkManager,监听udev事件。
- 冷启动/sys/block下对应的块设备。
- 启动监听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, "addn", 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文件,然后写入”addn“四个字节到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启动的流程后我们留下了三件事要分析:
- VolumeManager.handleBlockEvent处理设备插拔事件。
- MountService和Vold通信的建立。
- 卷的挂载。
- 多用户的管理。
下面我们逐一分析。
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的创建都是两步:
- 构造函数。
- 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个步骤:
- 创建Handler,用于处理vold消息 (1373-1376),对应MountService的一个独立线程。
- 读取配置文件,1401行readSettingsLocked(),我们稍后分析。
- 与vold建立链接,用于处理vold卷相关消息和给vold发送命令。(1142-1147)
- 与vold建立链接, 处理vold加密相关消息和给vold发送命令。(1420-1425)
- 注册广播接收者用于处理多用户相关消息。(1427-1430)
- 创建内置卷。对应vold的内置EmulatedVolume("/data/media")。1432
- 添加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);
}
}
}
主要做了三件事:
- 下发volume的reset命令。
- 下发volume的user_added命令。
- 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)
最后
以上就是开放狗为你收集整理的Android存储系统-MountService 和vold 对外置存储的管理(1)的全部内容,希望文章能够帮你解决Android存储系统-MountService 和vold 对外置存储的管理(1)所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复