结论:蜂窝链在 modem.lock 仲裁下启动 pppd CID 1,用 metric 1000 默认路由、策略表 table 1000 与 resolvectl DNS 接管默认网络,但 connected 字段只等于「ppp0 接口有 IP」,既不保证 peer 存在,也不保证路由/DNS 成功。这些全局名字(table 1000、pppd)不是独占资源,多链路并存时会互相破坏。本文为固定源码快照 c8fb906 的静态分析,蜂窝链只服务网络与遥测,不是车辆控制执行链。
AT 控制口:锁与空响应的歧义
Modem._at() 非阻塞持有 /dev/shm/modem.lock,打开 AT 控制口,清输入,逐行等 OK;锁忙、超时、ERROR 或串口 I/O 异常返回 []。六项原方法假串口探针确认:正常路径将短写剩余字节继续写出,忽略空行,收集 OK 前的响应;锁争用时关闭锁 fd 并返回 [],不会打开控制串口。因此空响应不区分「共享端口被占用」「模组离线」与「指令被拒」三种原因。
还有两个不折叠为空响应的边界:锁文件本身的 os.open() 位于捕获块之前,若 /dev/shm 不可用异常会直接传播;finally 先 flock(LOCK_UN) 再 os.close(fd),探针注入解锁 OSError 后正常响应被该异常覆盖,且关闭语句未执行。这是条件性清理缺口,不表示目标内核的正常解锁会失败。
PPP 数据口与路由/DNS 安装
PPPSession.start() 以 sudo pppd 启动 CID 1;kill() 停止时执行 sudo killall -9 pppd,没有按 self._proc.pid 限定。新硬件若另有 PPP 会话,也会进入停止目标集合。maybe_install_routes() 仅 peer 非空且不同于 _peer 才校验 IPv4、清旧路由、依次安装默认路由、表 1000 默认路由与源地址规则;失败则清理并 kill PPP。
六项原 PPPSession/假 subprocess.run 探针确认:相同 peer 即使本地 IP 从 10.0.0.2 变为 10.0.0.3,也不执行任何路由命令;无效 IPv4 在清路由前就调用全局 killall -9 pppd。新 peer 成功路径先清 ppp0 默认路由、flush 表 1000、删除相关规则,再依次安装三条配置;第二条 add 失败时再次执行全局清理并杀 PPP,_peer 不更新。DNS 侧,maybe_install_dns() 有 DNS 时设置 ppp0 DNS 与 resolved 默认路由,命令失败 kill PPP;resolvectl default-route 失败时只立即杀 PPP,该方法内没有同步调用 cleanup_routes()。
关键交错是:同 peer 不重装路由,导致 DNS 安装也被跳过——_poll_iface() 仅当 maybe_install_routes() 返回真才调用 maybe_install_dns()。若第一次 DNS 安装失败会退出 PPP 并重拨重装;但若 resolved 配置后来单独丢失、PPP 仍在且 peer 不变,当前轮询路径没有恢复机制。此结论来自调用条件,不等于实机已复现。
| 函数 | 源码可确认行为 | 不能据此证明 |
|---|---|---|
Modem._at() |
非阻塞持有 modem.lock,锁忙/超时/ERROR/IO 异常返回 []。 |
空响应不区分端口占用、模组离线与指令被拒。 |
_do_searching() |
同时要求 CREG、CGREG 为 home/roaming;漫游禁用只对 CREG == roaming 单独阻断。 |
CREG=home 而 CGREG=roaming 仍满足连接门。 |
PPPSession.kill() |
执行 sudo killall -9 pppd,未按 self._proc.pid 限定。 |
其他 PPP 会话也会进入停止目标集合。 |
maybe_install_routes() |
仅 peer 变化时安装默认路由、表 1000 默认路由与源地址规则;失败清理并 kill PPP。 | 同 peer 下外部删除路由或源 IP 改变不自愈。 |
cleanup_routes() |
删除 ppp0 默认路由、flush 表 1000、删所有 table 1000 规则并 resolvectl revert ppp0。 |
table 1000 与 pppd 是全局名字,代码未证明其独占。 |
_poll_iface() |
解析到 inet 地址就返回 connected=True。 |
不保证 peer、路由/DNS 成功,更不是互联网探活。 |
状态发布与字段年龄
_do_connecting() 启动 pppd 后立刻进入枚举名 CONNECTED,但此时共享 JSON 的 connected 仍可为 False;只有 _poll_iface() 看到 ppp0 地址才设真。因此状态机阶段和接口 IP 观测是两个证据层级。_do_disconnecting() 先发布初始快照,再全局杀 PPP、清路由、复位数据口;run() 异常只将本地 state 改为 DISCONNECTING 而不发布,下一轮断开 handler 返回 INITIALIZING,因此远端可能完全看不到该阶段。
状态写入先创建临时 JSON,chmod 0o644 后 os.replace 到共享路径;seconds_since_boot 是最近任一字段发布时间,不是每个温度/信号/IP 的采样时间。四项原 _publish_state()/_poll() 临时文件探针确认:增量发布 connected 时旧 signal_quality 和 iccid 仍保留,但全局时间更新为本次发布时刻;七个采样函数都返回空字典时 _poll() 不发布。HardwareComma.get_modem_state() 读取该 JSON,hw_state_thread() 约十秒刷新一次再写入 deviceState,这条链没有逐字段新鲜度。
| 证据层级 | 来源 | 能证明 | 不能证明 |
|---|---|---|---|
| 状态机阶段 | 枚举名 CONNECTING/CONNECTED |
代码走到哪一步。 | 接口是否已有 IP。 |
| 接口观测 | _poll_iface() 读 ppp0 |
接口是否解析出 IPv4 地址。 | peer、路由、DNS 是否就绪。 |
| 共享状态文件 | _publish_state() 原子 JSON |
本轮采样的字段增量。 | 每个字段是否同样新鲜。 |
| 设备状态汇总 | hw_state_thread() |
约十秒一次的快照。 | 逐字段时间戳。 |
新硬件与插件重构要求
源码给出的方向是:CellularTransport 插件应只管理自己启动的进程和专属路由表/规则,提供资源所有者 ID;不能通过 killall pppd 或无所有者的 table 1000 清理与其他链路争用。状态应拆分 modem_seen、sim_ready、registered、ppp_running、ip_assigned、route_installed、dns_installed、internet_reachable,每项附采样时间、错误码和恢复动作。
对 AT 锁忙、超时、设备消失、指令拒绝和空成功响应应给出不同结果;APN/漫游配置提交必须读回确认。台架故障注入需覆盖:第二个 PPP 会话、其他消费者占用表 1000、路由/DNS 被外部删除、同 peer 换 IP、AT 被 LPA 占锁、SIM 热插拔、解析异常、无 DNS 与 resolved 重启。路由/DNS、接口采样、共享状态和 AT 局部控制流共有二十三项原方法隔离探针,其余状态转移仍主要为静态追踪。
常见问题
connected=True 就代表蜂窝网络可用吗?
不代表。_poll_iface() 只要解析到 inet 地址就返回 connected=True,即使 peer 字段缺失或路由安装方法返回 False 也成立;它既不保证 peer 存在,也不保证路由/DNS 成功,更不是互联网探活。
为什么换 IP 后路由不重建?
因为 maybe_install_routes() 只在 peer 非空且不同于 _peer 时才安装;探针确认相同 peer 下本地 IP 从 10.0.0.2 变为 10.0.0.3 也不执行任何路由命令,DNS 安装也随之被跳过。
AT 返回空响应到底是哪种故障?
无法从空响应区分。锁忙、超时、ERROR 与串口 I/O 异常都返回 [];插件化后应对这些情况给出不同结果与可恢复动作。
为什么十秒刷新一次还不够新鲜?
因为 seconds_since_boot 是最近任一字段的发布时间,不是每个字段各自的采样时间;单项采样可能刷新全局时间,旧的 signal_quality 或 network_type 可能与当前 ppp0 IP 并列出现。
延伸阅读
- 模组总览:蜂窝状态机的各个阶段与故障交错 —— PPP 链上游的状态机全貌。
- 设备状态是怎么汇总的:从各子系统到 deviceState 的一条链 —— 共享 JSON 如何进入设备状态。
- eSIM LPA 下载链路:120 字节分片与第 257 片序号回绕 —— 同样抢
modem.lock的 eSIM 交易。 - GNSS 二进制解析:另一条共享 AT/串口的遥测链路 —— 与蜂窝共享串口的定位数据链。
参考资料
- 项目 02 ——
openpilot-research/MODEM_PPP_ROUTE_DNS_OWNERSHIP_CONTRACT_DEEP_DIVE.md(PPP 进程、路由/DNS 所有权与状态可信度)。 - 项目 02 —— 同目录探针记录:六项 AT 锁/应答、六项 PPP 命令、七项接口/DNS、四项共享状态隔离探针结论。
说明:本文为对固定源码快照的静态结构分析,未运行完整测试套件、未连接硬件、未验证目标车型的真实参数组合。文中所有阈值、行号索引与配置均出自源文档,标注为未验证的结论保留其原有边界。
更新记录
首次发布 2026-10-11 最近核对:2026-10-11