waydroid源码分析

1.0 helpers/lxc.py代码功能概述

这段代码是 Waydroid 项目的一部分,主要负责配置和管理 LXC 容器,以实现 Android 环境在 Linux 系统上的运行。它包含以下核心功能:

  1. LXC 容器配置:生成容器所需的设备节点、挂载点和系统属性。
  2. 容器生命周期管理:启动、停止、冻结和解冻容器。
  3. 硬件访问支持:配置图形设备、Binder 驱动和其他硬件节点。
  4. 用户会话集成:动态挂载用户目录和通信套接字。
  5. Android 环境模拟:设置系统属性和环境变量,模拟 Android 运行环境。

2.0 代码结构与关键模块分析

2.1. LXC 容器配置生成
  • generate_nodes_lxc_config

    • 功能:生成容器所需的设备节点和挂载点配置。
    • 关键逻辑
      • 挂载 /dev 下的基本设备节点(如 zeronullashmem)。
      • 配置图形设备节点(如 kgsl-3d0mali0dri 节点)。
      • 处理 Binder 驱动节点(bindervndbinderhwbinder)。
      • 挂载用户目录和系统权限文件。
    • 示例
      make_entry("/dev/ashmem")  # 挂载 ashmem 设备
      make_entry("/sys/module/lowmemorykiller", options="bind,create=dir")  # 低内存管理节点
      
  • set_lxc_config

    • 功能:初始化 LXC 配置文件,处理不同版本兼容性。
    • 关键逻辑
      • 从模板文件生成基础配置。
      • 替换架构相关变量(如 LXCARCH)。
      • 配置 AppArmor 或 Seccomp 安全策略。
      • 写入设备节点配置到容器目录。
2.2. 用户会话配置
  • generate_session_lxc_config
    • 功能:根据用户会话动态添加挂载点。
    • 关键逻辑
      • 挂载 XDG_RUNTIME_DIR 和 Wayland 套接字。
      • 绑定用户数据目录(waydroid_data)。
      • 示例
        make_entry(session["xdg_runtime_dir"], options="create=dir")  # 创建用户运行时目录
        
2.3. Android 系统属性设置
  • make_base_props
    • 功能:生成 Android 系统属性文件(waydroid_base.prop)。
    • 关键逻辑
      • 检测硬件 HAL 库(如 grallocegl)。
      • 配置 Vulkan 和 OpenGL ES 版本。
      • 模拟设备指纹和厂商信息。
    • 示例
      props.append("ro.hardware.gralloc=gbm")  # 图形内存分配器
      props.append("ro.build.fingerprint=vendor/model")  # 设备指纹
      
2.4. 容器生命周期管理
  • start/stop/freeze/unfreeze
    • 功能:控制容器的启动、停止和状态。
    • 关键逻辑
      • 使用 lxc-start 启动容器并等待状态为 RUNNING
      • 处理容器权限和日志文件权限。
    • 示例
      command = ["lxc-start", "-F", "-n", "waydroid", "--", "/init"]  # 启动容器
      
2.5. 容器交互
  • shell
    • 功能:进入容器执行命令。
    • 关键逻辑
      • 设置 Android 环境变量(如 PATHANDROID_ROOT)。
      • 支持用户权限和 SELinux 上下文。
    • 示例
      command = ["lxc-attach", "--clear-env", "--set-var", "ANDROID_ROOT=/system"]  # 附加到容器
      

3.0技术细节与设计亮点

  1. 设备节点管理

    • 通过 generate_nodes_lxc_config 动态挂载必要的设备节点,确保容器能访问硬件资源(如 GPU、Binder 驱动)。
    • 使用 optional 0 0 标记可选挂载,提高兼容性。
  2. 多版本兼容性

    • set_lxc_config 根据 LXC 版本加载不同的配置片段(如 config_1config_3)。
    • 处理 lxc.aa_profile 在不同版本中的命名差异。
  3. 硬件加速支持

    • 检测 dri 节点并配置 gralloc/egl,实现图形硬件加速。
    • 支持 Vulkan 驱动自动检测。
  4. 安全策略

    • 使用 AppArmor 或 Seccomp 限制容器权限。
    • 对用户提供的挂载路径进行合法性检查。

4.0 典型调用流程

  1. 初始化配置

    set_lxc_config(args)  # 生成容器基础配置
    make_base_props(args)  # 生成 Android 系统属性
    
  2. 启动容器

    start(args)  # 启动容器并等待运行
    
  3. 用户会话配置

    generate_session_lxc_config(args, session)  # 动态挂载用户目录和套接字
    
  4. 执行命令

    shell(args)  # 进入容器执行命令
    

5.0 小结

这段代码通过 LXC 容器技术实现了 Android 环境的隔离与运行,核心在于设备节点配置、硬件加速支持和用户会话集成。它充分利用 Linux 内核功能(如命名空间、cgroups)和 Android 系统特性,确保应用在容器中高效运行。对于理解 Waydroid 的技术实现和容器化 Android 方案具有重要参考价值。

6.0 helpers/images.py代码功能概述

这段代码主要用于 Waydroid 项目中 Android 系统镜像的管理、验证、替换以及根文件系统的挂载和卸载操作,同时涉及配置文件的读取和保存,下面详细分析:

  1. 镜像获取与更新:从指定的 OTA 渠道获取系统和供应商镜像,验证其完整性,下载并解压到指定路径,更新配置文件中的时间戳。
  2. 镜像验证:验证下载的镜像是否来自指定的 OTA 渠道。
  3. 镜像替换:使用指定的系统和供应商镜像替换现有镜像,并更新配置文件。
  4. 覆盖层清理:移除根文件系统的覆盖层目录。
  5. 属性文件生成:根据配置信息生成 waydroid.prop 属性文件。
  6. 根文件系统挂载与卸载:挂载系统和供应商镜像,设置覆盖层,绑定必要的目录,并生成属性文件;卸载根文件系统的所有挂载点。

7.0 代码结构与关键函数分析

7.1. 哈希计算函数 sha256sum
def sha256sum(filename):
    h = hashlib.sha256()
    b = bytearray(128*1024)
    mv = memoryview(b)
    with open(filename, 'rb', buffering=0) as f:
        for n in iter(lambda: f.readinto(mv), 0):
            h.update(mv[:n])
    return h.hexdigest()

该函数用于计算指定文件的 SHA-256 哈希值,通过分块读取文件内容并更新哈希对象,最后返回十六进制表示的哈希值。

7.2. 镜像获取函数 get
def get(args):
    cfg = tools.config.load(args)
    # 处理系统镜像
    system_ota = cfg["waydroid"]["system_ota"]
    system_request = helpers.http.retrieve(system_ota)
    if system_request[0] != 200:
        raise ValueError(
            "Failed to get system OTA channel: {}, error: {}".format(args.system_ota, system_request[0]))
    system_responses = json.loads(system_request[1].decode('utf8'))["response"]
    if len(system_responses) < 1:
        raise ValueError("No images found on system channel")
    for system_response in system_responses:
        if system_response['datetime'] > int(cfg["waydroid"]["system_datetime"]):
            images_zip = helpers.http.download(
                args, system_response['url'], system_response['filename'], cache=False)
            logging.info("Validating system image")
            if sha256sum(images_zip) != system_response['id']:
                try:
                    os.remove(images_zip)
                except:
                    pass
                raise ValueError("Downloaded system image hash doesn't match, expected: {}".format(
                    system_response['id']))
            logging.info("Extracting to " + args.images_path)
            with zipfile.ZipFile(images_zip, 'r') as zip_ref:
                zip_ref.extractall(args.images_path)
            cfg["waydroid"]["system_datetime"] = str(system_response['datetime'])
            tools.config.save(args, cfg)
            os.remove(images_zip)
            break
    # 处理供应商镜像
    vendor_ota = cfg["waydroid"]["vendor_ota"]
    vendor_request = helpers.http.retrieve(vendor_ota)
    if vendor_request[0] != 200:
        raise ValueError(
            "Failed to get vendor OTA channel: {}, error: {}".format(vendor_ota, vendor_request[0]))
    vendor_responses = json.loads(vendor_request[1].decode('utf8'))["response"]
    if len(vendor_responses) < 1:
        raise ValueError("No images found on vendor channel")
    for vendor_response in vendor_responses:
        if vendor_response['datetime'] > int(cfg["waydroid"]["vendor_datetime"]):
            images_zip = helpers.http.download(
                args, vendor_response['url'], vendor_response['filename'], cache=False)
            logging.info("Validating vendor image")
            if sha256sum(images_zip) != vendor_response['id']:
                try:
                    os.remove(images_zip)
                except:
                    pass
                raise ValueError("Downloaded vendor image hash doesn't match, expected: {}".format(
                    vendor_response['id']))
            logging.info("Extracting to " + args.images_path)
            with zipfile.ZipFile(images_zip, 'r') as zip_ref:
                zip_ref.extractall(args.images_path)
            cfg["waydroid"]["vendor_datetime"] = str(vendor_response['datetime'])
            tools.config.save(args, cfg)
            os.remove(images_zip)
            break
    remove_overlay(args)

该函数从配置文件中获取系统和供应商的 OTA 渠道信息,通过 HTTP 请求获取镜像列表,筛选出更新的镜像,下载并验证其完整性,解压到指定路径,更新配置文件中的时间戳,最后移除覆盖层。

7.3. 镜像验证函数 validate
def validate(args, channel, image_zip):
    cfg = tools.config.load(args)
    channel_url = cfg["waydroid"][channel]
    channel_request = helpers.http.retrieve(channel_url)
    if channel_request[0] != 200:
        return False
    channel_responses = json.loads(channel_request[1].decode('utf8'))["response"]
    for build in channel_responses:
        if sha256sum(image_zip) == build['id']:
            return True
    logging.warning(f"Could not verify the image {image_zip} against {channel_url}")
    return False

该函数验证指定的镜像文件是否来自指定的 OTA 渠道,通过计算镜像的 SHA-256 哈希值并与渠道中的镜像信息进行比对。

7.4. 镜像替换函数 replace
def replace(args, system_zip, system_time, vendor_zip, vendor_time):
    cfg = tools.config.load(args)
    args.images_path = cfg["waydroid"]["images_path"]
    if os.path.exists(system_zip):
        with zipfile.ZipFile(system_zip, 'r') as zip_ref:
            zip_ref.extractall(args.images_path)
        os.remove(system_zip)
        cfg["waydroid"]["system_datetime"] = str(system_time)
        tools.config.save(args, cfg)
    if os.path.exists(vendor_zip):
        with zipfile.ZipFile(vendor_zip, 'r') as zip_ref:
            zip_ref.extractall(args.images_path)
        os.remove(vendor_zip)
        cfg["waydroid"]["vendor_datetime"] = str(vendor_time)
        tools.config.save(args, cfg)
    remove_overlay(args)

该函数使用指定的系统和供应商镜像替换现有镜像,解压并更新配置文件中的时间戳,最后移除覆盖层。

7.5. 覆盖层清理函数 remove_overlay
def remove_overlay(args):
    if os.path.isdir(tools.config.defaults["overlay_rw"]):
        shutil.rmtree(tools.config.defaults["overlay_rw"])
    if os.path.isdir(tools.config.defaults["overlay_work"]):
        shutil.rmtree(tools.config.defaults["overlay_work"])

该函数移除根文件系统的读写覆盖层和工作目录。

7.6. 属性文件生成函数 make_prop
def make_prop(args, cfg, full_props_path):
    if not os.path.isfile(args.work + "/waydroid_base.prop"):
        raise RuntimeError("waydroid_base.prop Not found")
    with open(args.work + "/waydroid_base.prop") as f:
        props = f.read().splitlines()
    if not props:
        raise RuntimeError("waydroid_base.prop is broken!!?")

    def add_prop(key, cfg_key):
        value = cfg[cfg_key]
        if value != "None":
            value = value.replace("/mnt/", "/mnt_extra/")
            props.append(key + "=" + value)

    add_prop("waydroid.host.user", "user_name")
    add_prop("waydroid.host.uid", "user_id")
    add_prop("waydroid.host.gid", "group_id")
    add_prop("waydroid.host_data_path", "waydroid_data")
    add_prop("waydroid.xdg_runtime_dir", "xdg_runtime_dir")
    add_prop("waydroid.pulse_runtime_path", "pulse_runtime_path")
    add_prop("waydroid.wayland_display", "wayland_display")
    add_prop("waydroid.background_start", "background_start")
    if which("waydroid-sensord") is None:
        props.append("waydroid.stub_sensors_hal=1")
    dpi = cfg["lcd_density"]
    if dpi != "0":
        props.append("ro.sf.lcd_density=" + dpi)

    final_props = open(full_props_path, "w")
    for prop in props:
        final_props.write(prop + "\n")
    final_props.close()
    os.chmod(full_props_path, 0o644)

该函数根据配置信息生成 waydroid.prop 属性文件,从 waydroid_base.prop 文件中读取基础属性,添加额外的属性,最后将属性写入新文件并设置权限。

7.7. 根文件系统挂载函数 mount_rootfs
def mount_rootfs(args, images_dir, session):
    cfg = tools.config.load(args)
    helpers.mount.mount(args, images_dir + "/system.img",
                        tools.config.defaults["rootfs"], umount=True)
    if cfg["waydroid"]["mount_overlays"] == "True":
        try:
            helpers.mount.mount_overlay(args, [tools.config.defaults["overlay"],
                                               tools.config.defaults["rootfs"]],
                                    tools.config.defaults["rootfs"],
                                    upper_dir=tools.config.defaults["overlay_rw"] + "/system",
                                    work_dir=tools.config.defaults["overlay_work"] + "/system")
        except RuntimeError:
            cfg["waydroid"]["mount_overlays"] = "False"
            tools.config.save(args, cfg)
            logging.warning("Mounting overlays failed. The feature has been disabled.")

    helpers.mount.mount(args, images_dir + "/vendor.img",
                           tools.config.defaults["rootfs"] + "/vendor")
    if cfg["waydroid"]["mount_overlays"] == "True":
        helpers.mount.mount_overlay(args, [tools.config.defaults["overlay"] + "/vendor",
                                           tools.config.defaults["rootfs"] + "/vendor"],
                                    tools.config.defaults["rootfs"] + "/vendor",
                                    upper_dir=tools.config.defaults["overlay_rw"] + "/vendor",
                                    work_dir=tools.config.defaults["overlay_work"] + "/vendor")

    for egl_path in ["/vendor/lib/egl", "/vendor/lib64/egl"]:
        if os.path.isdir(egl_path):
            helpers.mount.bind(
                args, egl_path, tools.config.defaults["rootfs"] + egl_path)
    if helpers.mount.ismount("/odm"):
        helpers.mount.bind(
            args, "/odm", tools.config.defaults["rootfs"] + "/odm_extra")
    else:
        if os.path.isdir("/vendor/odm"):
            helpers.mount.bind(
                args, "/vendor/odm", tools.config.defaults["rootfs"] + "/odm_extra")

    make_prop(args, session, args.work + "/waydroid.prop")
    helpers.mount.bind_file(args, args.work + "/waydroid.prop",
                            tools.config.defaults["rootfs"] + "/vendor/waydroid.prop")

该函数挂载系统和供应商镜像,设置覆盖层,绑定必要的目录,并生成属性文件,将其绑定到根文件系统中。

7.8. 根文件系统卸载函数 umount_rootfs
def umount_rootfs(args):
    helpers.mount.umount_all(args, tools.config.defaults["rootfs"])

该函数卸载根文件系统的所有挂载点。

代码调用示例

import argparse
from tools import config
import logging

# 初始化参数
args = argparse.Namespace()
args.work = "/path/to/work"
args.images_path = "/path/to/images"
# 加载配置
cfg = config.load(args)

# 获取更新的镜像
try:
    get(args)
except ValueError as e:
    logging.error(e)

# 验证镜像
image_zip = "/path/to/image.zip"
channel = "system_ota"
if validate(args, channel, image_zip):
    logging.info("Image is valid.")
else:
    logging.warning("Image is invalid.")

# 替换镜像
system_zip = "/path/to/system.zip"
system_time = 1630416000
vendor_zip = "/path/to/vendor.zip"
vendor_time = 1630416000
replace(args, system_zip, system_time, vendor_zip, vendor_time)

# 挂载根文件系统
images_dir = args.images_path
session = {
    "user_name": "user",
    "user_id": "1000",
    "group_id": "1000",
    "waydroid_data": "/path/to/data",
    "xdg_runtime_dir": "/path/to/xdg_runtime",
    "pulse_runtime_path": "/path/to/pulse_runtime",
    "wayland_display": "wayland-0",
    "background_start": "False",
    "lcd_density": "320"
}
mount_rootfs(args, images_dir, session)

# 卸载根文件系统
umount_rootfs(args)

8.0 小节总结

这段代码通过一系列函数实现了 Waydroid 项目中 Android 系统镜像的管理、验证、替换以及根文件系统的挂载和卸载操作,确保了系统的正常运行和更新。同时,通过配置文件的读取和保存,方便用户进行个性化设置。代码结构清晰,每个函数的功能明确,便于维护和扩展。

lineageOS

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值