欢迎光临
我们一直在努力

Android 文件系统挂载

当使用不同的介质作为启动盘时,其对应的块设备控制器通常不一样,常见的有 /dev/block/nvme0n1, /dev/block/sda, /dev/block/mmcblk0。而在编译镜像时,通常需要在 fstab 中明确启动介质路径,让 init 能正确挂载文件系统。由于不确定启动介质类型,每次还需要修改 fstab 源文件进行编译打包,流程太复杂,不适合分发。

调研

调研后有三种常见解决方案:

  • 使用 /dev/block/by-name:这是由 init 通过在 uevent 自动创建的块设备链接,来规避不同的存储介质控制器造成的路径不同。
  • 直接将不同介质的 fstab 均打包到镜像中,通过 Grub 中的 cmdline 传递 androidboot.fstab_suffix 来指定 init 需要读取的文件路径。
  • 使用动态分区,让 init 自动识别逻辑分区。这样 fatab 中只需要填写动态分区名即可(system, vendor)。
  • 其中方法一与方法三,需要通过 androidboot.boot_devices 命令行参数指定启动介质的控制器。通常可以在已经运行的系统上,查看 /dev/block/platform/ 下的文件夹名,来确定控制器名,比如 soc/1d84000.ufshc。

    通常 ueventd 在监听到块设备事件时(DeviceHandler::GetBlockDeviceSymlinks),只会在 /dev/block/platform/soc/1d84000.ufshc/by-name 下创建与 GPT 分区名相同的符号链接。而如果是 androidboot.boot_devices 定义的启动设备,还会在 /dev/block/by-name 下创建符号链接。方法一与方法三正是通过 /dev/block/by-name 这个确定的路径来解决不同存储介质的问题。

    词汇表

    ueventd:User Space Device Event Daemon,用户空间设备事件守护进程;监听来自内核的 uevent 事件。类似于 systemd 系统的 udev。

    动态分区:将 system、vendor、data 等分区合并为一个 super 物理分区,再在这个物理分区中以元数据的形式动态分配传统的 system、vendor、data 等逻辑分区;可以通过修改元数据快速动态分配磁盘空间。

    Virtual A/B:以 super 动态分区为基础,将新 system 镜像 diff 下载到 data 逻辑分区,重新启动时,会合并老 system 镜像,并动态分区。

    .rc:run commands。AOSP 下运行脚本和服务的框架,类似于 systemd。Action 类似于 systemd 的 oneshot 一次性服务,Service 类似于 systemd 的 simple 常驻服务。init 会将各个 rc 文件(system/core/rootdir/*.rc,system/etc/init/*.rc,vendor/etc/init/*.rc)解析为 Action 和 Service。

    on <trigger>
    on property:<prop>=<value>
    start <service_name>
    service <name> <path>

    Property Service:一个类似于 Windows 的注册表数据库,由 init 进程管理,可提供给 Action 用作触发。

    init 挂载文件系统

    第一阶段的 init 会读取 ramdisk 中的 fstab(/fstab.xxx),并挂载带有 first_stage_mount 标志的分区(system, vendor, metadata);第二阶段的 init ,会执行 init.pd2508.rc 文件中的 mount_all 命令,挂载剩余分区(userdata)。而 mount_all 如果不明确指定路径,会像第一阶段一样读取 kernel_cmdline,寻找需要的 fstab(/vendor/etc/fstab.xxx)。

    整个 init 的源码路径为 AOSP 的 system/core/init/。

    第一阶段 init 挂载文件系统

    第一阶段 init 挂载文件系统分为三步:

  • CreateFirstStageMount:读取 fstab 配置文件
  • DoCreateDevices:等待 uevent 事件,并创建相应的块设备
  • DoFirstStageMount:挂载块设备
  • main
    FirstStageMain
    CreateFirstStageMount # 获取 mount 配置文件
    FirstStageMount::Create
    ReadFirstStageFstabAndroid # 只会从 fstab 中读取带有 first_stage_mount 标志的分区
    ReadDefaultFstab
    GetFstabPath # 找到 fstab 文件路径
    DoCreateDevices # 创建块设备
    InitDevices
    GetDmVerityDevices # 获取 fstab 中块设备最后一段 device 名
    InitRequiredDevices
    BlockDevInitializer::InitDevices # 监听 device 名的块设备,并生成设备节点
    HandleUevent # uevent 的回掉函数
    DoFirstStageMount
    MountPartitions
    MountPartition
    fs_mgr_do_mount_one # 挂载

    第二阶段 init 挂载文件系统

    第二阶段 init 挂载文件系统是将挂载命令 mount_all 写入 .rc 文件中:

    on fs
    mount_all

    而 .rc 初始化系统的框架如下:

    main
    SecondStageMain
    GetBuiltinFunctionMap # 加载 .rc 文件内置命令对应的函数
    LoadBootScripts # 加载 .rc 配置文件
    QueueBuiltinAction # 通过 C++ 代码直接硬编码添加 Action,这可以更灵活控制启动顺序
    QueueEventTrigger # 解析 .rc 文件中的 tigger,将 Action 加入队列
    ExecuteOneCommand # 执行一个 Action
    do_mount_all # 在 .rc 文件中定义了 mount_all 命令时,执行该回调函数
    ReadDefaultFstab
    GetFstabPath

  • GetBuiltinFunctionMap:加载内置命令对应的函数
  • LoadBootScripts:加载 .rc 配置文件
  • ExecuteOneCommand:执行内置命令对应的回调函数
  • 为什么不能用 /dev/block/by-name/ 来写 fstab,它会提示找不到分区?

    只有定义了 androidboot.boot_devices 的存储控制器,才能在 /dev/block/by-name 下创建链接,这是为了规避有多个启动介质时,在相同路径下创建链接导致冲突:

    androidboot.boot_devices=soc/1d84000.ufshc

    如果没有定义 androidboot.boot_devices,则只会在 /dev/block/platform/<soc>/by-name 下创建链接,比如 /dev/block/platform/soc/1d84000.ufshc/by-name/。

    进一步,自动创建链接还有一个前提,是分区表中存在的 label。

    关键函数:DeviceHandler::GetBlockDeviceSymlinks

    赞(0)
    未经允许不得转载:171主机测评 » Android 文件系统挂载
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址