1.0 helpers/lxc.py代码功能概述
这段代码是 Waydroid 项目的一部分,主要负责配置和管理 LXC 容器,以实现 Android 环境在 Linux 系统上的运行。它包含以下核心功能:
- LXC 容器配置:生成容器所需的设备节点、挂载点和系统属性。
- 容器生命周期管理:启动、停止、冻结和解冻容器。
- 硬件访问支持:配置图形设备、Binder 驱动和其他硬件节点。
- 用户会话集成:动态挂载用户目录和通信套接字。
- Android 环境模拟:设置系统属性和环境变量,模拟 Android 运行环境。
2.0 代码结构与关键模块分析
2.1. LXC 容器配置生成
-
generate_nodes_lxc_config
:- 功能:生成容器所需的设备节点和挂载点配置。
- 关键逻辑:
- 挂载
/dev
下的基本设备节点(如zero
、null
、ashmem
)。 - 配置图形设备节点(如
kgsl-3d0
、mali0
、dri
节点)。 - 处理 Binder 驱动节点(
binder
、vndbinder
、hwbinder
)。 - 挂载用户目录和系统权限文件。
- 挂载
- 示例:
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 库(如
gralloc
、egl
)。 - 配置 Vulkan 和 OpenGL ES 版本。
- 模拟设备指纹和厂商信息。
- 检测硬件 HAL 库(如
- 示例:
props.append("ro.hardware.gralloc=gbm") # 图形内存分配器 props.append("ro.build.fingerprint=vendor/model") # 设备指纹
- 功能:生成 Android 系统属性文件(
2.4. 容器生命周期管理
start
/stop
/freeze
/unfreeze
:- 功能:控制容器的启动、停止和状态。
- 关键逻辑:
- 使用
lxc-start
启动容器并等待状态为RUNNING
。 - 处理容器权限和日志文件权限。
- 使用
- 示例:
command = ["lxc-start", "-F", "-n", "waydroid", "--", "/init"] # 启动容器
2.5. 容器交互
shell
:- 功能:进入容器执行命令。
- 关键逻辑:
- 设置 Android 环境变量(如
PATH
、ANDROID_ROOT
)。 - 支持用户权限和 SELinux 上下文。
- 设置 Android 环境变量(如
- 示例:
command = ["lxc-attach", "--clear-env", "--set-var", "ANDROID_ROOT=/system"] # 附加到容器
3.0技术细节与设计亮点
-
设备节点管理:
- 通过
generate_nodes_lxc_config
动态挂载必要的设备节点,确保容器能访问硬件资源(如 GPU、Binder 驱动)。 - 使用
optional 0 0
标记可选挂载,提高兼容性。
- 通过
-
多版本兼容性:
set_lxc_config
根据 LXC 版本加载不同的配置片段(如config_1
、config_3
)。- 处理
lxc.aa_profile
在不同版本中的命名差异。
-
硬件加速支持:
- 检测
dri
节点并配置gralloc
/egl
,实现图形硬件加速。 - 支持 Vulkan 驱动自动检测。
- 检测
-
安全策略:
- 使用 AppArmor 或 Seccomp 限制容器权限。
- 对用户提供的挂载路径进行合法性检查。
4.0 典型调用流程
-
初始化配置:
set_lxc_config(args) # 生成容器基础配置 make_base_props(args) # 生成 Android 系统属性
-
启动容器:
start(args) # 启动容器并等待运行
-
用户会话配置:
generate_session_lxc_config(args, session) # 动态挂载用户目录和套接字
-
执行命令:
shell(args) # 进入容器执行命令
5.0 小结
这段代码通过 LXC 容器技术实现了 Android 环境的隔离与运行,核心在于设备节点配置、硬件加速支持和用户会话集成。它充分利用 Linux 内核功能(如命名空间、cgroups)和 Android 系统特性,确保应用在容器中高效运行。对于理解 Waydroid 的技术实现和容器化 Android 方案具有重要参考价值。
6.0 helpers/images.py代码功能概述
这段代码主要用于 Waydroid 项目中 Android 系统镜像的管理、验证、替换以及根文件系统的挂载和卸载操作,同时涉及配置文件的读取和保存,下面详细分析:
- 镜像获取与更新:从指定的 OTA 渠道获取系统和供应商镜像,验证其完整性,下载并解压到指定路径,更新配置文件中的时间戳。
- 镜像验证:验证下载的镜像是否来自指定的 OTA 渠道。
- 镜像替换:使用指定的系统和供应商镜像替换现有镜像,并更新配置文件。
- 覆盖层清理:移除根文件系统的覆盖层目录。
- 属性文件生成:根据配置信息生成
waydroid.prop
属性文件。 - 根文件系统挂载与卸载:挂载系统和供应商镜像,设置覆盖层,绑定必要的目录,并生成属性文件;卸载根文件系统的所有挂载点。
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 系统镜像的管理、验证、替换以及根文件系统的挂载和卸载操作,确保了系统的正常运行和更新。同时,通过配置文件的读取和保存,方便用户进行个性化设置。代码结构清晰,每个函数的功能明确,便于维护和扩展。