标签: 信任边界

  • 大型 ADAS 代码库怎么读:四步阅读顺序与三类边界

    结论:读一个大型 ADAS 代码库,正确顺序不是从目录名或进程启动顺序开始,而是按职责与信任边界组织:先分清运行底座、输入感知、状态估计、车辆接入、决策执行、旁路验证六大块,再沿「车况从 CAN 进来到控制从 CAN 出去」这条链逐层追。本文给出一条可复用的四步阅读顺序——从进程与消息契约出发,追车况解析、追使能状态机、追候选发送与板端安全钩子——并强调三类不能混同的边界。源代码根提交为 c8fb906,归档缺少模型权重实体,以下为静态分析,不代表构建通过或实车验证。

    按职责与信任边界看,而不是按目录名

    这个代码库的关键结构不是「哪个目录属于哪个功能」,而是各部分所处的信任层级。可以用一张源码目录地图定位:

    目录 在系统中的位置 主要产物或边界
    系统与平台目录 设备与运行平台 进程管理、相机与传感器采集、记录、设备状态与远程服务
    消息契约与传输 数据契约与传输 日志结构定义、服务频率定义、跨进程消息队列与图像缓冲
    共享运行时 共享基础 参数持久化、时间与调度、日志、设备抽象与版本信息
    视觉推理目录 视觉推理 道路与驾驶员模型输入处理与推理输出;模型权重属于缺失的实体
    定位与监控目录 状态估计 定位、标定、车辆参数学习与驾驶员注意力策略
    车辆 I/O 目录 主机侧车辆 I/O 车况标准化、候选发送帧生成、与安全板通信
    决策与控制目录 决策与执行请求 雷达融合、纵向规划、控制器与使能告警状态机
    车型协议与安全目录 车型协议与独立安全边界 DBC、车型状态与控制、safety 规则;安全 MCU 执行收发与发送限制
    开发验证目录 开发验证 回放、仿真、CAN 分析与诊断,不属于车上实时控制路径

    要特别记住一点:安全板固件、车型协议、跨进程通信与推理运行时分别位于不同的源码归档,不要把它们误认为同一个可热插拔模块。这条边界决定了后面所有替换与移植工作的可行性。

    对照这张地图时,一个实用判断是看某模块是否参与了实时控制路径:属于开发验证的部分不应成为车上的必要依赖,而安全相关部分则必须与车型、总线与固件联合版本化。

    四步阅读顺序

    公开资料给出了一条从函数追到消息的阅读顺序,可以照此执行:

    1. 从进程配置与进程管理函数找到运行进程,再对照服务频率定义与日志结构定义的主题字段;
    2. 从车况更新函数追车型接口更新、车型状态与雷达轨迹,再追模型输出、雷达状态、纵向计划的生产者;
    3. 从事件汇总与状态机更新的函数追使能 / 退出条件,再看控制器如何生成执行请求;
    4. 从车型编码函数追车型控制器的更新、候选发送帧、主机到板传输与板端安全钩子;任何可替换模块都不能跨越最后一层安全门。

    顺序的关键在最后一步:候选发送帧在到达总线之前,必须经过安全 MCU 的独立检查。也就是说,主机侧插件、车型控制器和配置项都不能替代板端规则。

    这四步的意义在于把「消息契约」放在第一位:先知道有哪些主题、频率与字段,再去看谁生产它们。否则很容易在某个子目录里困住,却不知道它在闭环中的位置。追控制链时,建议始终回到「车况进来、控制出去」这两条主线上做交叉验证。

    运行阶段与消息速查

    下表按理解闭环的顺序排列,不是全局同步调用栈。进程各自运行,消息的发布、接收与板端执行发生在不同时间,表内的「输出」也不等于车辆已经执行:

    阶段 主要输入与产出 硬件或插件边界
    设备与进程 板 / 电源 / 温度等状态形成设备启动状态,进程管理据此增减受管进程 新平台必须先可靠报告点火、热状态和电源;进程启动不代表传感器健康
    物理采集 相机图像进入模型;CAN 经车型解析形成车况与雷达轨迹 帧时间、坐标系、CAN 来源总线及采样年龄必须随适配器一起验收
    观测发布 分别发布模型输出、相机里程计、车况与雷达轨迹 同一发布周期内的多个主题也可能有不同有效条件
    融合与规划 雷达轨迹与模型输出形成雷达状态;模型更新触发纵向计划 规划插件须声明输入时间轴、有效性和输出期限;计划存在不等于控制获准
    使能与控制 事件产生使能状态;控制器综合车况、模型和计划发布控制请求 使能、横向、纵向是不同权限;控制请求的 valid 不是最终硬阻断
    车型编码 控制请求经车型转换成为候选发送帧 主分支检查控制请求存活,不统一检查其有效位
    主机到 MCU 主机按消息年龄发送候选帧,另维护板状态与心跳 消息年龄门和心跳不是逐帧安全授权
    板端安全 按安全模式、发送白名单、车型钩子及故障决定是否接受候选帧 车型 / DBC / 安全参数 / 固件必须联合版本化

    建议先用只读 CAN 与图像采集验证前四阶段,再以离线回放验证控制请求,最后独立验证网关与 MCU 的拒帧、超时及故障退化。

    三类边界:输入、能力与验证

    从源码职责推导,面向自制硬件与插件重构有三条边界,对应四类插件:

    插件类型 可定义的契约 不可交给插件自我授权的部分
    传感器 / 感知 时间戳、坐标系、健康状态和标准消息输出 无效或过期输入继续参与控制的许可
    车型适配 指纹、DBC、车况与控制帧、受控总线清单 安全模式、发送白名单、驾驶员覆盖规则
    规划 / 控制 标准模型 / 雷达 / 控制契约及回放等价性 无条件接管发送或绕过使能状态机
    记录 / UI / 远程 只读订阅、存储和远程诊断能力 将远程命令直接转为车辆控制权限

    第一条边界是物理输入与安全发送:车型、总线、线束、摄像头时间戳以及安全板配置必须版本绑定。第二条是算法与辅助能力:感知、定位、日志和 UI 可以探索可替换接口,但控制相关组件不能仅凭「插件已安装」取得发送权。第三条是验证:先只读采集、进程回放和故障注入,再做台架与受控场地测试;这类文档不是接线图或上车许可。

    常见问题

    能不能只看目录名理解这套代码?

    不建议。应优先按职责与信任边界组织:安全板固件、车型协议、跨进程通信和推理运行时分别位于不同源码归档,按目录名会被误导。可用文中的目录地图定位各部分所处层级。

    控制请求发出去了就等于车辆执行了吗?

    不等于。候选发送帧在到达总线前还要经过安全 MCU 的独立检查;主机侧的消息年龄门和心跳也不是逐帧安全授权。文中所有表中的「输出」都不等于车辆已经执行。

    使能、横向、纵向是同一个开关吗?

    不是。公开资料特别指出它们是不同权限,且控制请求的 valid 不是最终硬阻断。判断能否执行应回到使能状态机与板端安全规则,而不是单一布尔值。

    这份分析能作为上车依据吗?

    不能。归档缺少模型权重实体,本文为静态源码分析,不代表构建通过或实车验证;它不是接线图或上车许可,任何执行能力都需要台架与受控场地另行验证。

    延伸阅读

    参考资料

    • 项目 02 —— openpilot-research/ARCHITECTURE_GUIDE_CN.md(取用源码目录地图、四步阅读顺序、运行阶段与消息速查、三类边界与插件类型表)。

    说明:本文为对固定源码快照(根提交 c8fb906)的静态结构分析,未运行完整测试套件、未连接硬件、未验证目标车型的真实参数组合。文中所有入口、阶段与边界均出自源文档,标注为未验证的结论保留其原有边界。

    更新记录

    首次发布 2026-10-11 最近核对:2026-10-11

  • tinygrad PCI 两条内存路径:alloc 与 map 的所有权不对称

    结论:tinygrad 的 PCI 接口把「本地发现与独占」「内存分配与映射」和「远程 RPC 调试」三件事放在同一模块里,普通插件很容易只看到最后一句 alloc(),却忽略它前面的宿主状态改动。两条内存路径的所有权并不对称:VRAM 路径走 MemoryManager.valloc(),释放时有 vfree();而 SYS 路径单独持有 VA、系统内存和 GPU 页表,free() 只做本地 munmap(),没有显式归还 VA 或拆页表。远程服务 serve.py 监听 0.0.0.0 且在协议处理前没有认证,属于开发工具。以下为固定归档的静态源码追踪,未解绑驱动、未映射 BAR、未启动远程服务、未访问真实 PCI 设备。

    本地调用链:构造对象即改变宿主 PCI 状态

    _System.pci_scan_bus() 按 vendor/device 掩码和可选 base class 筛选设备;list_devices() 在设置 REMOTE 时改走 TCP 远程扫描并缓存结果;pci_probe_device() 应用可见设备过滤后构造设备。

    真正需要警惕的是 PCIDevice.__init__():它取得文件锁、解绑现有内核驱动、移除同 slot 的 sibling functions、选择 VFIO 或 sysfs 启用,并打开 PCI config。也就是说,构造对象本身就会改变宿主机 PCI 状态,研究时不应把它当成只读操作。PCIIfaceBase.__init__() 随后探测设备、保留 VA 区间、尝试 resize BAR 并构造具体实现。

    步骤 负责函数 副作用 / 边界
    扫描与筛选 pci_scan_bus()、pci_probe_device() 按 vendor/device 掩码与可见设备过滤。
    远程分流 list_devices() 设置 REMOTE 时改走 TCP 扫描并缓存。
    独占与启用 PCIDevice.__init__() 取文件锁、解绑驱动、移除 sibling function、选 VFIO/sysfs。
    VA 与 BAR PCIIfaceBase.__init__() 保留 VA 区间、尝试 resize BAR;resize 异常被吞掉并继续。

    alloc 与 map:所有权不对称

    alloc() 依据 host/cpu_access/BAR 选择锁页系统内存或 VRAM。VRAM 路径调用 MemoryManager.valloc();SYS 路径则取得 VA、分配 sysmem 并写 GPU 页表。map() 允许同 host 的 CPU/PYTHON/NPY 缓冲或同 peer group 的 PCI GPU 缓冲进入 GPU VA;host 路径检查页对齐与 VA 范围并锁页,P2P 路径拒绝小 BAR 与跨 peer group 映射,p2p_paddrs() 用目标 BAR 物理基址形成系统地址空间映射。

    释放时的不对称是重点。PCIIfaceBase.free() 对 PHYS 路径调用 vfree()、对本地 CPU 映射调用 munmap(),但没有对 SYS 分配调用 unmap_range() 或 va_allocator.free()。同样,map() 对 host 缓冲调用 System.lock_memory(),对应的 unmap() 只调用页表 unmap_range(),未见 munlock()。

    入口 主要职责 需要核对的结果
    alloc_sysmem()、system_paddrs() MAP_LOCKED 匿名映射并从 pagemap 取物理页 依赖权限、页固定和完整物理页列表。
    map_bar()、bar_info() resource 文件长度/地址与 BAR mmap off/size、BAR 边界与 MADV_DONTFORK 是内核契约。
    alloc() 输出 BufferStorage 加 PCIAllocationMeta PHYS 有 valloc() 所有权;SYS 单独拥有 VA、映射和页表。
    free() PHYS 走 vfree();本地 CPU 映射 munmap() SYS 路径未显式归还 VA 或拆页表。
    map()、unmap() 外部缓冲映射与页表拆除 unmap() 仅拆 GPU PTE,host mlock() 解锁责任不明确。

    此外,USBPCIDevice 通过 ASM24 桥配置 PCI bus/BAR,并用 512 KiB SRAM Bump 分配器提供 DMA 暂存。这属于特定 USB 桥路径,不应混作通用主板 PCI 内存。另有几个静态可见的失败边界:PCIDevice.reset() 使用 shell 命令但不检查退出状态;BAR resize 异常在 __init__() 被吞掉并继续初始化——因此不能把构造成功等同于 reset/resize 已成功。

    远程 RPC:服务端没有认证

    RemotePCIDevice 经 TCP 的 REMOTE_REQ/REMOTE_RESP 与服务端通信。RemoteMMIOInterface 的读取使用往返 RPC,写入是 posted 命令,同一 TCP 流上的后续请求用于观察此前的写入。服务端 handle() 暴露设备探测、config/BAR 读写、reset、系统内存映射、直接内存读写,以及 LOAD_PROG/EXEC_PROG。

    信任边界很高:server.bind() 监听 0.0.0.0,在协议处理前没有认证或授权;SYSMEM_READ/WRITE 直接以请求提供的地址构造进程内存视图;LOAD_PROG/EXEC_PROG 可装载并执行客户端给出的程序。只要服务可被访问,客户端就获得远超「推理插件数据搬运」的能力。源码本身是开发工具,不应在车载生产网络上裸露运行,应物理或网络隔离,并增加认证、命令白名单与最小权限。这一判断是接口能力分析,不表示当前车辆上已启动该服务。

    静态可见的失败边界与验收

    除了 alloc/free 的 VA 与锁页不对称,还有两处条件性风险。其一,alloc() 的 SYS 路径先取 VA,再申请系统内存和建立页表,失败中途没有本层回滚,属条件性资源累积风险,需区分底层设备复位或进程退出清理与运行中的热重配。其二,远程 scan()/_connect() 缓存 socket,而 serve.py 的已发现设备、打开设备、BAR 映射和程序列表是模块全局状态,连接关闭不会在同一进程中显式清掉,热重连与多客户端所有权需要另测。

    面向自制硬件的验收应先在无设备 mock 中覆盖 VA/SYS 分配失败回滚、host 锁页与释放、BAR 边界和 P2P peer group;再在专用台架验证驱动解绑与恢复、VFIO/IOMMU、DMA 地址、重复启动和断电回收。远程调试服务只能放在隔离开发环境,绝不能成为「万物即插件」的通用车载执行接口;任何 PCI/计算插件都不得直接获得 sendcan 或 panda safety 配置权限。

    常见问题

    alloc 之后 free 就能把资源全部还回去吗?

    不一定对称。PHYS/VRAM 路径在 free() 里走 vfree(),本地 CPU 映射走 munmap();但 SYS 路径没有显式调用 unmap_range() 或 va_allocator.free(),host 锁页的解锁责任也未在这一对函数里明确。

    构造 PCIDevice 对象是只读操作吗?

    不是。PCIDevice.__init__() 会取得文件锁、解绑现有内核驱动、移除同 slot 的 sibling functions,并选择 VFIO 或 sysfs 启用,因而会改变宿主机 PCI 状态。

    远程 serve.py 暴露了什么能力?

    它暴露设备探测、config/BAR 读写、reset、系统内存映射、直接内存读写,以及 LOAD_PROG/EXEC_PROG;server.bind() 监听 0.0.0.0 且协议处理前没有认证,SYSMEM_READ/WRITE 直接按请求地址构造进程内存视图。

    BAR resize 失败会影响后续初始化吗?

    会存在被忽略的情况。BAR resize 的异常在 PCIIfaceBase.__init__() 被吞掉并继续初始化;PCIDevice.reset() 也使用 shell 命令但不检查退出状态,因此不能把构造成功等同于 reset/resize 已成功。

    延伸阅读

    参考资料

    • 项目 02 —— openpilot-research/TINYGRAD_PCI_LOCAL_REMOTE_MEMORY_TRUST_LIFECYCLE_DEEP_DIVE.md(取用了本地调用链、alloc/map/free 所有权不对称、USB 桥路径、远程 RPC 服务端无认证与静态失败边界)。

    说明:本文为对固定源码快照的静态结构分析,未运行完整测试套件、未连接硬件、未验证目标车型的真实参数组合。文中所有阈值、行号索引与配置均出自源文档,标注为未验证的结论保留其原有边界。

    更新记录

    首次发布 2026-10-11 最近核对:2026-10-11

  • 系统时间自举:日期范围门、GNSS 校时与信任边界

    结论:openpilot 的系统时间自举建立在几个纯日期范围检查上:Python 侧的 system_time_valid() 用本地墙钟在固定下界 2025-02-21 与固定上界 2035-01-01 之间做严格开区间比较,若找到 systemd 可执行文件,还会把它的修改时间加一天并与下界取较大值。「有效」只表示当前日期落在区间内,不等于可信 UTC、证书有效或各传感器时钟已对齐:一台时钟在区间内但快慢数天的设备同样能通过。本文为固定源码快照 c8fb906 的静态阅读与有限隔离探针,未修改系统时钟、未连接 GNSS 或访问网络。

    公共日期门:范围检查而非可信时间

    MIN_DATE 与 MAX_DATE 分别为 2025-02-21 和 2035-01-01。min_date() 若发现 systemd 可执行文件,会把该文件的修改时间按本地时间加一天,再与固定下界取较大值;system_time_valid() 用本地 datetime.now() 做严格开区间比较。它没有读 NTP 同步状态、GNSS 精度、硬件 RTC 来源或 TLS 证书有效期。

    一个结构性边界是:若该文件的修改时间异常地进入未来,动态下界可能高于上界,导致整个有效区间为空。四项假路径探针确认了这一族行为:没有该文件或修改时间早于固定下界时仍用 2025-02-21;较新的修改时间会把下界抬到其本地日期加一天;恰好等于动态下界或 2035-01-01 上界均判无效;修改时间晚于上界可使整个有效区间为空。

    原生 C++ 侧还有一条不同的判断:它的固定下界是 2024-08-26,也考虑该文件修改时间加一天,但没有 2035 上界,安装器依赖这一路径。两端不能被当作同一个时间状态。

    项目 Python 侧 C++ 侧
    固定下界 2025-02-21 2024-08-26
    固定上界 2035-01-01 无
    动态下界 systemd 文件修改时间的本地时间加一天,与固定下界取大 同样考虑该文件修改时间加一天
    加一天的算术 先转本地无时区时间,再加一个日历日 直接对 epoch 秒加 86400,即固定 24 小时

    两端的「加一天」也不完全等价:在使用夏令时的本地时区且这一天跨越切换点时,两个下界可差一小时。离线以 America/New_York 的 2026-03-07 12:00 为修改时间,Python 得到次日 12:00,固定秒数得到次日 13:00;2026-10-31 12:00 则分别得到次日 12:00 与 11:00。这只验证时区算术差异,目标设备是否配置夏令时尚未验证。

    timed 的 GNSS 校时链

    定位服务按启动时的一个可用性参数在外部与内部定位主题之间选一个,timed.main() 创建一次订阅,不在主循环中动态切换。每轮先把当前墙钟写入时钟消息,并用上述范围判断设置外层有效位。只有所选主题本轮更新、消息单调时间距当前不超过两秒、定位有效且 GNSS 时间落在下界到上界之间,才调用校时函数;此路径没有检查所选定位消息的顶层有效位。偏差不足十秒直接跳过,更大的偏差通过系统命令设置,失败只记录异常。

    六项假消息与假命令探针补充了执行顺序证据:时钟发布发生在 GNSS 门控之前,即使本地墙钟无效也可用新鲜定位尝试校时;所选定位消息的外层无效不阻断调用。定位时间戳在检查「本轮更新、新鲜度、定位有效」之前即被转成日期时间,因此假消息即使标为未更新,超出可表示范围的时间戳仍会先抛异常,使本轮不进入其余门控。此外主循环在校时函数正常返回后无条件睡十秒,未根据实际校时成功与否区分退避。

    环节 实现事实 边界
    订阅选择 按启动时可用性参数在两条定位主题中选一条 主循环不动态切换
    新鲜度门 消息距当前单调时间不超过两秒 需定位有效且时间在范围内
    校时调用 偏差超过十秒才设置系统时间 不检查定位消息顶层有效位
    异常处理 设置失败只记录异常 主循环仍无条件睡十秒

    启动自举与下游行为

    时间门被多个网络与硬件路径复用:GNSS 相关服务仅在时间「有效」时给模组发送辅助时间,并在网络可用时才取辅助数据。另一方面 timed 又依赖 GNSS 首次有效定位来校准系统时间,所以自制硬件必须设计 RTC、NTP 或独立定位的冷启动路径,不能假设辅助时间与 GNSS 校时会自动互相解锁。

    下游同样依赖这个门:eSIM 的配置下载函数先检查系统时间范围,通知上传也有同一门;更新服务在时间无效时等待再重试;UI 配对与更新路径、以及取令牌的辅助函数也依赖它。部分初始设置在证书错误且本地时间无效时会尝试重启时间同步服务。这些调用说明「日期在范围内」是业务前置条件,却不能替代 HTTPS 自身的证书验证和服务端认证。

    还有两个容易混淆的语义:GNSS 时间戳被转为 UTC 后移除时区信息,再与本地时间下界比较,设备为 UTC 时一致、非 UTC 时边界会偏移;而外层有效位为真只表示当前墙钟通过了范围门,不是刚从 GNSS 成功校时。该进程也不会证明相机帧起始、IMU 中断与诊断日志时间戳已与墙钟完成共时钟标定。

    硬件与插件化要求

    时间服务应分别声明墙钟来源与可信级别、UTC 与闰秒、单调时钟域、观测时间戳、最后同步时间、估计误差、时钟跳变和上游服务状态。只有受信任的平台层可设置系统时间;GNSS 与网络插件应提交带来源与精度的候选时间,不能直接把范围检查当成「可信时间」,也不能自行运行系统校时命令。UI、更新与 eSIM 的网络门应区分「尚无可信时间、可信时间可用、时间失步」三种状态,避免把范围检查误认为完整安全检查。

    待验证边界

    尚未验证的场景包括:无 RTC 冷启动、真实镜像中该文件修改时间异常、时区非 UTC、GNSS 定位延迟或失效、NTP 与 GNSS 冲突,以及系统时间前后跳变对证书、令牌、日志与相机时序的影响。本轮未执行这些动态或实板测试。

    常见问题

    system_time_valid() 为真就代表时间可信吗?

    不代表。它只是一个纯日期范围检查,不读 NTP 同步状态、GNSS 精度、硬件 RTC 来源或 TLS 证书有效期。一台时钟在区间内但快慢数天的设备同样能通过,因此「日期合理」不等于可信 UTC。

    Python 与 C++ 的时间有效判断一致吗?

    不一致。Python 侧下界为 2025-02-21、上界为 2035-01-01;C++ 侧下界为 2024-08-26 且没有上界。两端的「加一天」算术也不同:Python 先转本地无时区时间再加一个日历日,C++ 直接对 epoch 秒加 86400,跨夏令时时可差一小时。

    只要 GNSS 有定位就会校准系统时间吗?

    不一定。只有所选主题本轮更新、消息距当前单调时间不超过两秒、定位有效且时间落在范围内才调用校时函数;偏差不足十秒会直接跳过。而且该路径不检查定位消息的顶层有效位。

    本地墙钟无效会不会阻止 GNSS 校时?

    不会。探针显示时钟发布发生在 GNSS 门控之前,即使本地墙钟范围检查不通过,也可用新鲜定位尝试校时;所选定位消息的外层无效同样不阻断调用。

    延伸阅读

    参考资料

    • 项目 02 —— openpilot-research/SYSTEM_TIME_BOOTSTRAP_TRUST_CONTRACT_DEEP_DIVE.md(取用了公共日期门、GNSS 校时链、下游消费者与硬件插件要求)。
    • 项目 02 —— openpilot-research/NATIVE_UTIL_IO_EXIT_CONTRACT_DEEP_DIVE.md(关联的原生工具专题,用于对照 C++ 与 Python 两套日期门差异,本文未展开其 I/O 实现)。

    说明:本文为对固定源码快照的静态结构分析,未运行完整测试套件、未连接硬件、未验证目标车型的真实参数组合。文中所有阈值、行号索引与配置均出自源文档,标注为未验证的结论保留其原有边界。

    更新记录

    首次发布 2026-10-11 最近核对:2026-10-11