<Android开发> Android内核系统开发- 启动过程详解 (第4部分 延伸内容 存储设备与多用户)

<Android开发> Android内核系统开发- 启动过程详解 (第4部分 延伸内容 存储设备与多用户)

Vold 和External Storage存储设备

与IOS不同的是,Android系统支持多种存储设备,包括外置的SDCARD、U盘等。这些存储设备的管理机制在不同的Android版本中差异很大, 将在这部分延伸内容分析。

Android系统中的内外存储设备定义如下:

  • Internal Storage
    按照Android的设计理念,Internal Storage代表的是/data存储目录。所以目前不少文件管理器事实上混淆了Internal Storage的概念,这点需要注意。
  • External Storage
    所有除了Internal Storage之外的可存储区域,参见下面详述。
    从物理设备的角度来看External Storage由如下几种类型组成:
  • (1)Emulated Storage
    Android设备中存在的一个典型做法,是从Internal Storage(如Flash)中划分一定的区域(如1GB)来作为外部存储设备,称为Emulated Storage。
  • (2)SDCARD/USB Devices
    通过扩展卡槽或者USB端口来扩展设备的存储能力,也是Android设备中的常见情况。

另外在某些场合,Android 6.0之前的External Storage会被称为Traditional Storage,从这个角度看它又可以细分成emulated和portable storage两个类型。Portable顾名思义就是指那些没有和系统绑定在一起的,可以随时移除的设备。正是由于这类设备的“暂时性和不稳定性”,它们并不适合用于存储一些敏感数据,例如系统代码/应用程序数据等。Android 6.0则引入了一种叫做“Adoptable storage"的存储概念,简单来说就是让外部存储设备可以像内部设备一样被处理。
为了达到上述的效果,被“Adoptable”的存储设备需要格式化并经过加密过程,以保证数据的安全性。当然,系统会在用户插入新的外部设备时首先询问是否要把它变成“Adoptable storage”。如果答案时肯定的才会执行这些处理;否则还是把它当做普通的存储设备。
Android系统中的外部存储设备由Vlod和MountService 来统一管理。其中Vlod对应的源码路径是“system/vold“。它是通过init.rc启动的,如下:

/* system/core/rootdir/init.rc */
.....
on early-fs
    # Once metadata has been mounted, we'll need vold to deal with userdata checkpointing
    # 一旦元数据被挂载,我们需要vold来处理用户数据检查点
    start vold
.......
on post-fs-data
    mark_post_data

    # Start checkpoint before we touch data
    # 在我们接触数据之前启动检查点
    start vold

值得一提的是FUSE services也不再放在init.rc中统一加载,而改由vold根据具体情况来动态决定是否需要启动。
Vold在启动后,会通过NETLINK和内核取得联系,并根据后者提供的的event来构建存储设备管理系统。和以往版本不同的是,Vold的配置文件不再是vold.fstab,而改成了/fstab.<ro.hardware>。如下:

/* device/qcom/msmnile_au/fstab.qcom */

# 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.

# Android fstab 文件。
# 包含文件系统检查器二进制文件的文件系统(通常是 /system)不能
# 指定 MF_CHECK,并且必须在任何指定 MF_CHECK 的文件系统之前

#TODO:将“检查”添加为带有数据分区的 fs_mgr_flags。
# 目前我们没有编译 e2fsck。 所以 fs 检查会失败。

#<src>                                                 <mnt_point>            <type>  <mnt_flags and options>                            <fs_mgr_flags>
/dev/block/bootdevice/by-name/system                    /                      ext4    ro,barrier=1,discard                                 wait,slotselect,avb
/dev/block/bootdevice/by-name/metadata                  /metadata              ext4    noatime,nosuid,nodev,discard                         wait,formattable
/dev/block/bootdevice/by-name/userdata                  /data                  ext4    noatime,nosuid,nodev,barrier=1,noauto_da_alloc,discard latemount,wait,check,fileencryption=ice,wrappedkey,keydirectory=/metadata/vold/metadata_encryption,quota,reservedsize=128M
/devices/platform/soc/8804000.sdhci/mmc_host*           /storage/sdcard1       vfat    nosuid,nodev                                         latemount,wait,voldmanaged=sdcard1:auto,encryptable=footer
/devices/platform/soc/1da4000.ufshc_card/host*          /storage/sdcard1       vfat    nosuid,nodev                                         latemount,wait,voldmanaged=sdcard1:auto,encryptable=footer
/dev/block/bootdevice/by-name/modem                     /vendor/firmware_mnt   vfat    ro,shortname=lower,uid=1000,gid=1000,dmask=227,fmask=337,context=u:object_r:firmware_file:s0 wait,slotselect
/dev/block/bootdevice/by-name/dsp                       /vendor/dsp            ext4    ro,nosuid,nodev,barrier=1                            latemount,wait,slotselect
/dev/block/bootdevice/by-name/persist                   /mnt/vendor/persist    ext4    noatime,nosuid,nodev,barrier=1                       wait
/dev/block/bootdevice/by-name/bluetooth                 /vendor/bt_firmware    vfat    ro,shortname=lower,uid=1002,gid=3002,dmask=227,fmask=337,context=u:object_r:bt_firmware_file:s0 latemount,wait,slotselect
# Need to have this entry in here even though the mount point itself is no longer needed.
# The update_engine code looks for this entry in order to determine the boot device address
# and fails if it does not find it.
# 即使不再需要挂载点本身,也需要在此处输入此条目。
# update_engine 代码查找此条目以确定引导设备地址,如果找不到则失败。
/dev/block/bootdevice/by-name/misc                      /misc                  emmc    defaults                                             defaults
/devices/platform/soc/*.ssusb/*.dwc3/xhci-hcd.*.auto*    /storage/usbotg    vfat    nosuid,nodev    latemount,wait,voldmanaged=usbotg:auto

Android 6.0 及以后版本中,根据设备具体情况不同主要由如下几种典型配置:
(1) Emulated primary only
即只有 Emulated storage的情况,此时fstab.device的配置范例如下:

/devices/xxx/xhci-hcd.0.auto/usb* 	auto	auto	defaults	voldmanaged=usb:auto

(2)Physical primary only
即只有一个外置物理存储设备的情况,此时fstab.device的配置范例如下:

/devices/platform/mtk-msdc.1/mmc_host*    auto      auto    defaults                     voldmanaged=sdcard0:auto,encryptable=userdata,noemulatedsd

(3)Emulated primary,Physical primary
有两种外置的物理存储设备,它们会被分别设定为primary 和secondary,此时fstab.device的配置范例如下:

/devices/platform/mtk-msdc.1/mmc_host*    auto      auto    defaults                     voldmanaged=sdcard1:auto,encryptable=userdata

fstab的语法规则如下:

<src> <mnt_point>  <type>  <mnt_flags and options>  <fs_mgr_flags>

src:在sysfs文件系统下用于描述设备节点的路径
mnt_point:设备挂载点
type:文件系统类型
mnt_flags and options:最新版本的vold会忽略这一项
fs_mgr_flags:对于没有包含“voldmanaged=”的一律会被忽略,换句话说“voldmanaged”表示的是它可以被vold管理

Vold在启动过程中会通过process——config函数来处理fstab配置文件,并把它们存储在VolumeManager的全局变量中。后续当收到内核的NetlinkEvent(add)时,VolumeManager再在handleBlockEvent中根据规则判断本次事件是否和之前记录的fstab配置相匹配,如果答案是肯定的话,则新创建一个Disk对象来管理,并将它们统一添加到mDisks中。
<Android开发> Android内核系统开发- 启动过程详解 (第4部分 延伸内容 存储设备与多用户)
我们不难发现,Emulated Storage所需的存储空间来源于设备的data分区。还句话说,Emulated Storage的存储空间和data分区是共享存储区域的。Emulated Storage当然也是有Volume Manager来统一管理的,如下所示:

/* system/vold/VolumeManager.cpp */

int VolumeManager::start() {
    ATRACE_NAME("VolumeManager::start");

    // Always start from a clean slate by unmounting everything in
    // directories that we own, in case we crashed.
    // 总是从头开始,卸载我们拥有的目录中的所有内容,以防我们崩溃。
    unmountAll();

    Devmapper::destroyAll();
    Loop::destroyAll();

    // Assume that we always have an emulated volume on internal
    // storage; the framework will decide if it should be mounted.
    // 假设我们在内部存储上总是有一个模拟卷; 框架将决定是否应该安装它。
    CHECK(mInternalEmulated == nullptr);
    mInternalEmulated = std::shared_ptr<android::vold::VolumeBase>(
        new android::vold::EmulatedVolume("/data/media"));
    mInternalEmulated->create();

    // Consider creating a virtual disk
    // 考虑创建一个虚拟磁盘
    updateVirtualDisk();

    return 0;
}

VolumeManager::start()会被vold的main函数调用,因而从vold的角度来看所有android设备都带有Emulated Storage的,只不过最终是否需要执行mount操作则由MountService来决定。从EmulatedVolume构造函数的参数可以看到,它在data分区中对应的路径是/data/media。VolumeManager的全局变量mInternalEmulated用于记录系统的Emulated Storage。
接下来mInternalEmulated->create()除了给Storage创建运行环*,还会向MountService发送一个VolumeCreated的消息,并将自身的状态迁移到kUnmounted。MountService收到这一信息后,会根据系统的实际情况决定是否挂载这个Storage,如果答案是肯定的话,那么它会回应一个mount指令给vold,而vold对此的处理过程中会进一步调用到doMount函数,这个函数最关键的步骤之一是fork一个新的进程,用于运行/system/bin/sdcard。doMount()函数内容如下:

/* system/vold/model/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());
    mFuseFull = StringPrintf("/mnt/runtime/full/%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) ||
        fs_prepare_dir(mFuseFull.c_str(), 0700, AID_ROOT, AID_ROOT)) {
        PLOG(ERROR) << getId() << " failed to create mount points";
        return -errno;
    }

    dev_t before = GetDevice(mFuseFull);

    if (!(mFusePid = fork())) {
        // clang-format off
        // clang 格式关闭
        if (execl(kFusePath, kFusePath,
                "-u", "1023", // AID_MEDIA_RW
                "-g", "1023", // AID_MEDIA_RW
                "-m",
                "-w",
                "-G",
                "-i",
                "-o",
                mRawPath.c_str(),
                label.c_str(),
                NULL)) {
            // clang-format on
            // clang 格式开启
            PLOG(ERROR) << "Failed to exec";
        }

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

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

    nsecs_t start = systemTime(SYSTEM_TIME_BOOTTIME);
    while (before == GetDevice(mFuseFull)) {
        LOG(DEBUG) << "Waiting for FUSE to spin up...";
        usleep(50000);  // 50ms

        nsecs_t now = systemTime(SYSTEM_TIME_BOOTTIME);
        if (nanoseconds_to_milliseconds(now - start) > 5000) {
            LOG(WARNING) << "Timed out while waiting for FUSE to spin up";
            return -ETIMEDOUT;
        }
    }
    /* sdcardfs will have exited already. FUSE will still be running */
    /* sdcardfs 已经退出。 FUSE 仍将运行 */
    TEMP_FAILURE_RETRY(waitpid(mFusePid, nullptr, 0));
    mFusePid = 0;

    return OK;
}

变量kFusePath指向的是“/system/bin/sdcard”,我们可以通过execl系统调用将其启动起来。sdcard daemon对应的源码目录是“system/core/sdcard/”。
sdcard daemon属于FUSE Service,FUSE的全称是“Filesystem in Userspace”,即在用户太实现的一种file system。它的典型框架图如下所示:
<Android开发> Android内核系统开发- 启动过程详解 (第4部分 延伸内容 存储设备与多用户)
当使用者(左半部分)希望访问FUSE文件系统时,这一请求会经过Kernel的VFS首先传递给FUSE对应的驱动模块,然后在通知到用户层的fuse管控程序(例如这个场景中的sdcard)。后者处理请求完成后会将结果数据返回给最初的调用者,从而完成一次交互过程。可见与传统的文件系统相比,FUSE文件系统因处理层次较多,所以在效率上注定会存在不足之处。不过“瑕不掩瑜”,FUSE文件系统的灵活性依然为其获得了广泛的应用。
了解了FUSE文件系统后,再来看看sdcard daemon是怎么做的。简单来说它会执行以下几个核心操作。
(1)将/dev/fuse挂载到3个目录路径下
3个目录路径分别是:/mnt/runtime/default/%s、/mnt/runtime/read/%s、/mnt/runtime/write/%s,其中%s代表的是Volume的label,在Emulated Storage这个场景下对应的是“emulated”。

(2)创建3个线程
在代码中对应的是thread_default、thread_read、thread_write。这3个线程启动后都会进入for死循环,然后不停地从自己对应的fuse->fd(即打开的/dev/fuse产生的文件描述符)中读取fuse,模块发过来的消息命令。并根据命令的具体类型(如FUSE_READ、FUSE_WRITE、FUSE_OPEN等)执行处理函数。

为什么需要将/dev/fuse挂载到3个路径下,并通过不同的线程来管理呢?这和Android 6.0中引入的Runtime Permission有关系。
不同于以往的Install Time Permission(应用程序所需的权限是在安装或在版本升级过程中赋予的)这种“一刀切”的管理方式,Runtime Permission允许用户在应用程序运行到某些特别功能时再动态决定是否赋予程序相应的权限。这样带来的好处是用户可以更清楚地知道应用程序需要(或者已经授予了)哪些权限,以实现更为“透明”的管理。不过Runtime Permission只对那些系统认为危险的权限进行保护,大家可以利用如下命令获取详细的权限列表:

adb shell pm list permissions -g -d

下图是再高通8155板子上运行的输出如下:

console:/ # pm list permissions -g -d
Dangerous Permissions:

group:android.permission-group.CONTACTS

group:android.permission-group.PHONE

group:android.permission-group.CALENDAR

group:android.permission-group.CALL_LOG

group:android.permission-group.CAMERA

group:android.permission-group.UNDEFINED
  permission:android.permission.READ_SMS
  permission:android.permission.READ_CALENDAR
  permission:android.permission.READ_CALL_LOG
  permission:android.permission.ACCESS_FINE_LOCATION
  permission:android.permission.ANSWER_PHONE_CALLS
  permission:android.permission.RECEIVE_WAP_PUSH
  permission:android.permission.BODY_SENSORS
  permission:android.permission.READ_PHONE_NUMBERS
  permission:android.permission.RECEIVE_MMS
  permission:android.permission.RECEIVE_SMS
  permission:android.permission.READ_EXTERNAL_STORAGE
  permission:android.permission.ACCESS_COARSE_LOCATION
  permission:android.permission.READ_PHONE_STATE
  permission:android.permission.SEND_SMS
  permission:android.permission.CALL_PHONE
  permission:android.permission.WRITE_CONTACTS
  permission:android.permission.ACCEPT_HANDOVER
  permission:android.permission.CAMERA
  permission:android.permission.WRITE_CALENDAR
  permission:android.permission.WRITE_CALL_LOG
  permission:android.permission.USE_SIP
  permission:android.permission.PROCESS_OUTGOING_CALLS
  permission:android.permission.READ_CELL_BROADCASTS
  permission:android.permission.GET_ACCOUNTS
  permission:android.permission.WRITE_EXTERNAL_STORAGE
  permission:android.permission.ACTIVITY_RECOGNITION
  permission:android.permission.RECORD_AUDIO
  permission:android.permission.READ_CONTACTS
  permission:android.permission.ACCESS_BACKGROUND_LOCATION
  permission:android.permission.ACCESS_MEDIA_LOCATION
  permission:com.android.voicemail.permission.ADD_VOICEMAIL

group:android.permission-group.ACTIVITY_RECOGNITION

group:android.permission-group.SENSORS

group:android.car.permission-group.CAR_MONITORING
  permission:android.car.permission.CAR_ENERGY

group:android.permission-group.LOCATION
  permission:android.car.permission.CAR_SPEED

group:android.permission-group.STORAGE

group:android.permission-group.MICROPHONE

group:android.permission-group.SMS

ungrouped:
console:/ #

从图中可以看到读写外置存储设备的权限就在此列。

从开发者的角度来看,当应用程序运行到需要Runtime Permission的功能时(也有应用程序再启动时一口气申请全部的Runtime Permission,从而导致很不好的用户体验,这种做法是不推荐的),手动调用相应的系统API进行权限申请,此时会弹出对话框,供用户选择。如果用户拒绝了应用程序的权限请求,并且勾选“Never ask again”等,那么应用程序下次再申请同一权限时系统将直接拒绝。这种情况下应用程序的一种常见的处理方法是自行弹出一个提示框,阐述申请此权限的重要性,以保证用户可以被充分说服,并手动去系统设置中进行授权操作。

可见Runtime Permission权限管理方式的一种重要的特性就是要求应用程序的权限可以在运行过程中进行动态调整,而且不能导致应用程序的重启。这其中就涉及package manager service、activity manager service、zygote、等多个系统服务,我们按照顺序逐一阐述。

首先需要关注的是应用程序启动时的初始化授权处理,此时AMS(activity manager service)在startProcessLocked中会做如下处理:

/* frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java */
@GuardedBy("this")
final ProcessRecord startProcessLocked(String processName,
         ApplicationInfo info, boolean knownToBeDead, int intentFlags,
         HostingRecord hostingRecord, boolean allowWhileBooting,
         boolean isolated, boolean keepIfLarge) {
     return mProcessList.startProcessLocked(processName, info, knownToBeDead, intentFlags,
             hostingRecord, allowWhileBooting, isolated, 0 /* isolatedUid */, keepIfLarge,
             null /* ABI override */, null /* entryPoint */, null /* entryPointArgs */,
             null /* crashHandler */);
             
//该函数会调用 目录“frameworks/base/services/core/java/com/android/server/am/ProcessList.java”下的mProcessList.startProcessLocked函数,
//该函数是个重载函数,由参数决定调用哪个,调用流程如下:
// 第1个调用:
			final ProcessRecord startProcessLocked(String processName, ApplicationInfo info,
            boolean knownToBeDead, int intentFlags, HostingRecord hostingRecord,
            boolean allowWhileBooting, boolean isolated, int isolatedUid, boolean keepIfLarge,
            String abiOverride, String entryPoint, String[] entryPointArgs, Runnable crashHandler)
//第2个调用:
	final boolean startProcessLocked(ProcessRecord app, HostingRecord hostingRecord,
            String abiOverride) 
//第3个调用:
	boolean startProcessLocked(ProcessRecord app, HostingRecord hostingRecord,
            boolean disableHiddenApiChecks, boolean mountExtStorageFull,
            String abiOverride)
//第4个调用:
	boolean startProcessLocked(HostingRecord hostingRecord,
            String entryPoint,
            ProcessRecord app, int uid, int[] gids, int runtimeFlags, int mountExternal,
            String seInfo, String requiredAbi, String instructionSet, String invokeWith,
            long startTime)
第3个调用函数中有
....
int mountExternal = Zygote.MOUNT_EXTERNAL_NONE;
            if (!app.isolated) {
                int[] permGids = null;
                try {
                    checkSlow(startTime, "startProcess: getting gids from package manager");
                    final IPackageManager pm = AppGlobals.getPackageManager();
                    permGids = pm.getPackageGids(app.info.packageName,
                            MATCH_DIRECT_BOOT_AUTO, app.userId);
                    if (StorageManager.hasIsolatedStorage() && mountExtStorageFull) {
                        mountExternal = Zygote.MOUNT_EXTERNAL_FULL;
                    } else {
                        StorageManagerInternal storageManagerInternal = LocalServices.getService(
                                StorageManagerInternal.class);
                        mountExternal = storageManagerInternal.getExternalStorageMountMode(uid,
                                app.info.packageName);
                    }
....

其中mountExternal变量会传入第4个调用函数中。

第4个调用函数中有
...
final Process.ProcessStartResult startResult = startProcess(hostingRecord,
                        entryPoint, app,
                        uid, gids, runtimeFlags, mountExternal, seInfo, requiredAbi, instructionSet,
                        invokeWith, startTime);
....

需要特别注意的是mountExternal这个变量的赋值过程,它将为后续的Zygote中的bind monut提供参考。从上面的代码不难看出,mountExternal是由MountService提供的,其代码实现如下:

/* StorageManagerInternalImpl类  继承 StorageManagerInternal 类*/

/*StorageManagerInternal类路径: frameworks/base/core/java/android/os/storage/StorageManagerInternal.java */
/*StorageManagerInternalImpl类路径: frameworks/base/services/core/java/com/android/server/StorageManagerService.java */

 @Override
 public int getExternalStorageMountMode(int uid, String packageName) {
     if (ENABLE_ISOLATED_STORAGE) {
         return getMountMode(uid, packageName);
     }
     try {
         if (packageName == null) {
             final String[] packagesForUid = mIPackageManager.getPackagesForUid(uid);
             packageName = packagesForUid[0];
         }
     } catch (RemoteException e) {
         // Should not happen - same process
         // 不应该发生 - 相同的过程
     }
     // No locking - CopyOnWriteArrayList
     // 无锁定 - CopyOnWriteArrayList
     int mountMode = Integer.MAX_VALUE;
     for (ExternalStorageMountPolicy policy : mPolicies) {
         final int policyMode = policy.getMountMode(uid, packageName);
         if (policyMode == Zygote.MOUNT_EXTERNAL_NONE) {
             return Zygote.MOUNT_EXTERNAL_NONE;
         }
         mountMode = Math.min(mountMode, policyMode);
     }
     if (mountMode == Integer.MAX_VALUE) {
         return Zygote.MOUNT_EXTERNAL_NONE;
     }
     return mountMode;
 }


这个函数的处理逻辑是:遍历所有的Policy规则,并从中挑选出数值最小的MountMode,按照由小到大的顺序排序,它们依次是:MOUNT_EXTERNAL_NONE、MOUNT_EXTERNAL_DEFAULT、MOUNT_EXTERNAL_READ、MOUNT_EXTERNAL_WRITE。应用程序的MountMode的具体取值主要由PMS(package manager service)的checkUidPermission来判断,而后者则会根据app在EXTERNAL_STORAGE等权限的情况来给出结论。

当Zygote孵化出一个应用程序进程后,会在MountEmulatedStorage中对Mount Mode做进一步处理,核心实现如下:

/* frameworks/base/core/jni/com_android_internal_os_Zygote.cpp */

// Create a private mount namespace and bind mount appropriate emulated
// storage for the given user.
// 创建一个私有挂载命名空间并为给定用户绑定挂载适当的模拟存储。
static void MountEmulatedStorage(uid_t uid, jint mount_mode,
        bool force_mount_namespace,
        fail_fn_t fail_fn) {
  // See storage config details at http://source.android.com/tech/storage/
  // 请参阅 http://source.android.com/tech/storage/ 上的存储配置详细信息
  ATRACE_CALL();

  String8 storage_source;
  if (mount_mode == MOUNT_EXTERNAL_DEFAULT) {
    storage_source = "/mnt/runtime/default";
  } else if (mount_mode == MOUNT_EXTERNAL_READ) {
    storage_source = "/mnt/runtime/read";
  } else if (mount_mode == MOUNT_EXTERNAL_WRITE
      || mount_mode == MOUNT_EXTERNAL_LEGACY
      || mount_mode == MOUNT_EXTERNAL_INSTALLER) {
    storage_source = "/mnt/runtime/write";
  } else if (mount_mode == MOUNT_EXTERNAL_FULL) {
    storage_source = "/mnt/runtime/full";
  } else if (mount_mode == MOUNT_EXTERNAL_NONE && !force_mount_namespace) {
    // Sane default of no storage visible
    // 没有存储可见的健全默认值
    return;
  }

  // Create a second private mount namespace for our process
  // 为我们的进程创建第二个私有挂载命名空间
  if (unshare(CLONE_NEWNS) == -1) {
    fail_fn(CREATE_ERROR("Failed to unshare(): %s", strerror(errno)));
  }

  // Handle force_mount_namespace with MOUNT_EXTERNAL_NONE.
  // 使用 MOUNT_EXTERNAL_NONE 处理 force_mount_namespace。
  if (mount_mode == MOUNT_EXTERNAL_NONE) {
    return;
  }

  if (TEMP_FAILURE_RETRY(mount(storage_source.string(), "/storage", nullptr,
                               MS_BIND | MS_REC | MS_SLAVE, nullptr)) == -1) {
    fail_fn(CREATE_ERROR("Failed to mount %s to /storage: %s",
                         storage_source.string(),
                         strerror(errno)));
  }

  // Mount user-specific symlink helper into place
  // 将用户特定的符号链接帮助器挂载到位
  userid_t user_id = multiuser_get_user_id(uid);
  const String8 user_source(String8::format("/mnt/user/%d", user_id));
  if (fs_prepare_dir(user_source.string(), 0751, 0, 0) == -1) {
    fail_fn(CREATE_ERROR("fs_prepare_dir failed on %s",
                         user_source.string()));
  }

  if (TEMP_FAILURE_RETRY(mount(user_source.string(), "/storage/self",
                               nullptr, MS_BIND, nullptr)) == -1) {
    fail_fn(CREATE_ERROR("Failed to mount %s to /storage/self: %s",
                         user_source.string(), strerror(errno)));
  }
}

从上面内容不得不承认Android的很对新功能是和Linux Kernel的更新换代息息相关的,例如上述代码中就用到了mount namespace、bind mount、等多项内核技术。建议大家可以先自行查阅相关资料了解这些技术。
Android 6.0 中引入了如下几个被称为“View”的mount point:
(1)/mnt/runtime/default
提供给没有特殊权限的应用程序,以及adbd等系统组件所在的root namespace。
(2)/mnt/runtime/read
提供给那些具有READ_EXTERNAL_STORAGE权限的应用程序。
(2)/mnt/runtime/write
提供给那些具有WRITE_EXTERNAL_STORAGE权限的应用程序。

透过不同的“view”所能“看到”的Mount tree是不一样的,从而实现了程序的外部存储上的分权限管理。更为重要的是,采用这种实现方式在应对Runtime Permission以后,PMS(Package Manager Service)会通过MountService向Vold的CommandListerner发送一条名为“remount_uid”的命令,后者则会进一步将消息传递给vold的VolumeManager::remountUid函数,在这个函数中就可以对相应的进程进行“视角”的重新调整,从而达到我们的预期效果。其它Runtime Permission的实现原理也是类似的,大家可以自行分析了解。

多用户管理
Android的多用户管理功能并不算流行,但是它会影响到不少模块的内部实现,譬如存储管理、权限管理等(参考上面分析)。
多用户管理意味着我们可以在同一台设备中支持多个使用者。类似于windows系统中的做法,Android中的用户也分类型的,具体如下。
(1) Primary
这是设备的第一个同时也是首选的用户,类似与windows中的Administrator。Primary User是不能被删除的,而且会一直处于运行状态。另外,它拥有一些特殊的权限和设置项。
(2) Secondary
这是被添加到设备中的除Primary User外的其它用户。它可以被移除,并且不能影响设备中的其它用户。
(3) Guest
临时性的Secondary User,系统中同时只能有一个 Guest User。

用户类型也直接决定了它们的权限范围,例如只有Primary User才拥有对phone call 和texts的完全控制权。而Secondary User默认情况下只能接听电话,而无权拨打或操作短信功能。当然,这也取决于Setting->Users中针对Secondary User的授权情况。
用户安装的程序虽然都在/data/app目录下,但是它们的数据存储位置则有所差异。具体来说,/data/data目录下保存的是Primary User的数据,而其它用户的数据则被放置于/data/user/<uid>/中(/data/user/0 和/data/data中的内容是一致的)。
不过从Android 5.0 开始,多用户的特性默认情况下是被关闭的。我们需要修改以下配置文件来打开:

/* frameworks/base/core/res/res/values/config.xml  */
....
 <!--  Maximum number of supported users -->
 <!-- 支持的最大用户数 -->
 <integer name="config_multiuserMaximumUsers">1</integer>

 <!-- Maximum number of users we allow to be running at a time -->
 <!-- 我们允许一次运行的最大用户数 -->
 <integer name="config_multiuserMaxRunningUsers">3</integer>

 <!-- Whether UI for multi user should be shown -->
 <!-- 是否应该显示多用户的 UI -->
 <bool name="config_enableMultiUserUI">false</bool>
....

设备商可以根据这部分来修改上述两个配置项,只有这样才能开启设备的多用户功能。

自此,延伸内容部分分析完。

Android内核系统开发- 启动过程详解部分内容分析,是作者参考网上资料并结合实际的代码分析的;对于不用厂商出版的Android源码,整个Android的流程基本不会有太大的改动,无非是函数内容的增删 或 函数多层封装调用。仔细研究自己用的源码,配合作者这部分文章的讲解,基本能熟悉Android内核系统启动过程了。

后续,作者将继续分享Android内核系统有关的其它文章知识点。

上一篇:【备战金三银四】2022最新Android大厂面试题解析大全Github标星15k+


下一篇:这款国产工具,让我电脑里的PS、XD都落灰了