-
Notifications
You must be signed in to change notification settings - Fork 2
Terminal Runtime
📑 本页目录
platform:terminal在手机上供给并运行一个真实的 Ubuntu 24.04 用户态 Linux: 官方 rootfs + PRoot 用户态沙箱 + 原生 C++ PTY + 自研 VT100 模拟器 + Ubuntu 生命周期编排 +apt包管理。
| 层 | 实现 | 要点 |
|---|---|---|
| rootfs 供给 |
RootfsDownloader/Extractor/Configurator、BundledRootfsSource、RootfsProvisionerImpl、UbuntuBootstrapManager
|
官方 Ubuntu 24.04.4 归档(sha256 锁定)、断点续装、sources.list 配置、基础包档案;v1.2.0 起支持内置 rootfs 随 APK 分发(见 Ubuntu rootfs 供给) |
| 执行后端 |
LinuxPRootBackend + ProotExecutor + SystemBindProfile + NativeLibraryPRootBinaryProvider
|
PRoot 用户态 ptrace 沙箱(无需 root)、预编译 so 随包分发(指纹校验防篡改)、Fake 后端供测试 |
| PTY | C++ forkpty(pty_engine.cpp / pty_session.cpp / jni_bridge.cpp + headers) |
原生伪终端、argv 编组、进程组信号、会话隔离;CMake 产出 apex_terminal,C++17 |
| 终端模拟 |
:terminal-emulator 模块 |
自研 VT100/ANSI:转义序列解析、滚动区、24 位色、UTF-8 解码(见 VT100/ANSI 模拟器) |
| 背压 IO |
PtyOutputPump + BackpressureConfig
|
有界输出泵、EOF 语义、丢帧保护 |
| 观察引擎 |
ObservationEngine2 + SemanticStateReducer + InputWaitingDetector
|
把 ANSI 噪音降维成语义状态(等待输入 / 运行中 / 完成 / 错误),识别提示符 |
| 包管理 |
UbuntuAptPackageManager + PackageOperationLock
|
设备上 apt install,并发锁防交错 |
| 环境自适应 |
AdaptiveProvisionLoop + DiagnosticRules + ResolverCache
|
执行观察 → 诊断规则 → 自动修复 |
| 会话持久化 |
SessionMetadataStore + RuntimeRecoveryService、SessionManager
|
重启恢复会话元数据 |
sequenceDiagram
participant UI as TerminalScreen
participant SM as SessionManager
participant B as LinuxPRootBackend
participant P as ProotExecutor
participant PTY as C++ forkpty (JNI)
participant G as guest bash/apt
participant VTE as terminal-emulator
participant OBS as ObservationEngine2
UI->>SM: create(backend=proot, workspace)
SM->>B: ensureRootfs() → BundledRootfsSource / RootfsDownloader
B->>P: proot -r <rootfs> -b /sdcard … /bin/bash
P->>PTY: forkpty execv argv[]
PTY-->>G: 真·伪终端会话
UI->>SM: write("apt install -y figlet\n")
G-->>PTY: 输出字节流(含 ANSI)
PTY-->>SM: PtyOutputPump(有界、丢帧保护)
SM->>VTE: feed(bytes) → 屏幕模型(Glyphs / 光标 / 滚动区)
SM->>OBS: 降维 → SemanticState(RUNNING / WAITING_INPUT / DONE / ERROR)
OBS-->>UI: 语义状态(输入法桥据此切换、UI 据此显示"等待输入")
Warning
这是硬红线,不是忘记升级。
从 Android 10(API 29)开始,系统禁止应用从可写私有目录执行二进制 —— SELinux 把应用数据文件
标记为 app_data_file,而 untrusted_app 域对这个标签没有 execute 权限。
Ubuntu rootfs 恰恰依赖:
-
execve()guest ELF(rootfs 里的/bin/bash、/usr/bin/apt); -
ptrace()(PRoot 的实现机制)。
一旦 targetSdk ≥ 29,哪怕设备已 Root、哪怕 Binary 已被解压到 /data/data/.../lib,
都会触发 permission denied / Operation not permitted,整个终端链路失效。
相关的 lint(ExpiredTargetSdkVersion)在 app/build.gradle.kts 中被显式关闭并写了理由注释。
配套:
-
useLegacyPackaging = true:让.so(含伪装成.so的 rootfs 归档)被解压到原生库目录,而不是保持压缩状态; -
keepDebugSymbols += "**/libubuntu-rootfs.so":跳过llvm-strip,否则归档会被"瘦身"损坏。
| 能做 | 不能做 / 要注意 |
|---|---|
完整用户态 Linux:apt、bash 脚本、python、gcc/clang(小项目) |
无内核模块、无 systemd(用 proot-services 之类替代或直接跑前台进程) |
绑定 /sdcard 等宿主目录(SystemBindProfile) |
无 Root 时不能 mount 物理分区、不能改系统属性 |
多会话隔离(SessionManager + 独立 PTY) |
性能:ptrace 会带来显著开销,编译大项目很慢 |
| 网络访问(走宿主网络栈) |
ping/raw socket 等需要特权的操作受宿主限制 |
把"终端显示器上出现了什么"翻译给 LLM 是没意义的(全是 ANSI 噪音),所以有:
| 组件 | 输出 |
|---|---|
ObservationEngine2 |
观察窗口内的原始输出 + 时间戳 + 窗口大小 |
SemanticStateReducer |
RUNNING / WAITING_INPUT / DONE / ERROR + 关键行摘要 |
InputWaitingDetector |
识别 shell 提示符、Password:、[Y/n] 等(决定是否需要继续喂输入) |
这让 Agent 可以"用.Terminal 像人一样操作":观察 → 判断状态 → 写输入 → 再观察,而不是 sleep 等死。
terminal.create terminal.run terminal.write terminal.observe
terminal.snapshot terminal.wait terminal.resize terminal.signal
terminal.close terminal.workspaces terminal.backends terminal.linux_bootstrap
terminal.linux_status terminal.linux_packages terminal.linux_network terminal.ubuntu_install
terminal.ubuntu.ensure(T82) terminal.ubuntu.status(T82)
其中 ubuntu.ensure / ubuntu.status 是一键生命周期编排入口(见 UbuntuLifecycleCoordinator)。
另有 tools/v2/*(22 个实现)与 tools/legacy/*(7 个旧实现)在模块内部演进。
Note
终端工具遵循 SDK 边界约束::terminal-emulator 不允许依赖 :platform:terminal,
详见 VT100/ANSI 模拟器。
terminal.create(workspace="default", backend="proot")
terminal.ubuntu.ensure() # 保证 Ubuntu 就绪(内置版免下载)
terminal.run("apt-get update && apt-get install -y figlet")
terminal.write("figlet HELLO\n")
terminal.observe() # 语义状态 + 关键输出
terminal.wait(until="DONE", timeoutMs=30000)
terminal.close()
| 现象 | 原因与处理 |
|---|---|
execve: permission denied |
几乎一定是 targetSdk ≥ 29 或 rootfs 未解压(见 §3);确认装的是带内置 rootfs 的版本 |
| 卡在 "rootfs 供给中" | 下载大文件 + 解压较慢;内置 APK 版本应完全离线。检查 RootfsHealthCheck 日志 |
apt 报无法解析域名 |
rootfs 内 DNS:检查 RootfsConfigurator 写入的 resolv.conf;宿主能否联网 |
| 输出乱码 / 方框字 | Termux 风格字体未安装(需支持 box-drawing 的等宽字体);确认渲染层 UTF-8 解码 |
| 会话断开后无法恢复 |
SessionMetadataStore 恢复的是元数据而非进程;进程级恢复不支持(Linux 语义) |
| 编译特别慢 | PRoot ptrace 开销 + 手机 CPU 决定,属预期;大工程建议 在移动端用 shell 工具替代 |
- Ubuntu rootfs 供给 —— rootfs 从哪来、怎么打包、怎么校验
- VT100/ANSI 模拟器 —— 屏幕是怎么画出来的
- 工具系统 —— 终端工具在全量工具里的位置
- 测试体系 —— 含真 Ubuntu E2E 测试
-
MCP 生态总览
新 - (沙箱 MCP · 官方 Hub · 逆向 Host · 门控语义)
- 终端运行时
- 终端 API 契约
- SDK 边界
- Termux 能力矩阵
- Ubuntu rootfs 供给
- Ubuntu 生命周期
- PRoot 二进制溯源
- VT100/ANSI 模拟器
- 终端性能
- 终端迁移
- 原生层 C++/JNI