标签: 内存所有权

  • 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

  • tinygrad NV 原生后端:RM/UVM、PCI 与映射所有权

    结论:tinygrad 的 NV 原生后端把设备发现、内存管理与队列提交分成三层接口:NVKIface 走内核驱动与 RM/UVM,PCIIface 走用户态 PCI 与 GSP,MOCKIface 用于模拟。块内存由 NVAllocator 经所选接口分配,NVKIface.alloc() 依次创建 VA、RM 物理句柄、CPU 映射、UVM range 与 DMA/GPU 映射。它的 VA 分配使用不回退的 BumpAllocator,且中途失败未见配对的逐阶段回滚,因此长时间运行、模型热替换或多插件加载必须专门设计可回收地址分配。本文为固定源码快照的静态阅读,openpilot 的 load_oob() 默认并不使用 NV,本文不代表车端已部署行为。

    接口选择与设备启动

    NVKIface.__init__() 先打开控制与 UVM 节点,按驱动主版本切换常量表,初始化 UVM 并按可见设备规则筛选 GPU,再打开指定设备描述符并读取 GPU instance 与 minor。root、全局描述符和 VA 分配器都是类级状态,因此同一进程内多次初始化会共享这些资源。

    setup_usermode()、setup_vm() 与 setup_gpfifo_vm() 查询可用硬件 class、映射门铃 MMIO,并注册 GPU 与虚拟地址空间及队列,已打开的 NV 设备之间还会尝试启用 P2P。类支持情况与同主机上的初始化顺序会影响启动结果。NVDevice.__init__() 再选择接口、创建设备树、vaspace 与 channel group、查询 SM 能力,并懒建计算与复制相关的 GPFIFO 对象。

    两条用户态路径互斥:PCIIface.__init__() 启动时会拒绝已初始化 NVKIface 的进程,原因是固定地址 mmap 可能覆盖 UVM 分配。因此不能在同一进程里随意从一个接口切到另一个。

    接口 底层路径 关键边界
    NVKIface 内核驱动 + RM/UVM 类级 root/描述符/VA 分配器;与 PCI 路径互斥
    PCIIface 用户态 PCI/GSP 拒绝已初始化 NVKIface 的进程;不能套用 UVM 生命周期
    MOCKIface 模拟器 用于无设备环境,不代表真实驱动行为

    RM/UVM 内存与跨设备映射

    底层 nv_iowr()、rm_alloc()、rm_control() 与 uvm() 只检查系统调用返回,RM 与 UVM 的包内状态分别再检查,显存不足会被映射为 MemoryError;因此不能以 ioctl 成功替代对象创建成功。NVKIface.alloc() 按 host、uncached 与大小选择页粒度,分配 VA 与 RM 物理对象,再创建 UVM external range、DMA 地址映射与 GPU 可见映射,cpu_access 会把内存映射到 CPU 地址。

    NVKIface.map() 与 _alloc_gpu_vaddr() 要求 CPU/PYTHON/NPY 缓冲 4 KiB 对齐;首次创建 host range,已存在 NV 映射时复用物理对象,NV 到 NV 则在既有 VA 上添加 GPU 映射。低、高地址池都是 wrap=False 的 BumpAllocator。

    位置 作用与边界
    nv_iowr() / rm_alloc() / uvm() ioctl 只检查系统调用返回,RM/UVM 包内状态另行检查
    NVKIface.alloc() / _gpu_uvm_map() 分配 VA、RM 物理对象、UVM range、DMA 与 GPU 映射
    NVKIface.free() / unmap() 按句柄条件 RM_FREE,再 UVM_FREE,必要时 munmap
    NVKIface.map() / _alloc_gpu_vaddr() 要求 4 KiB 对齐,复用物理对象或在既有 VA 上加映射

    所有权矩阵

    NVAllocator 把通用 BufferSpec 的 host、cpu_access、zero 转给选定接口,释放前先 synchronize(),跨设备映射与注销委托接口;external_ptr 由通用 Allocator.do_free() 跳过底层释放。Compiled.finalize() 在进程结束时先尝试同步,再在有 device_fini() 时调用它,它不是每次 Buffer.free() 的通用设备重启或销毁 API。

    对象 创建/拥有者 释放或完成门 待验证点
    普通 NV BufferStorage NVAllocator 经 iface 分配 先同步,再由 NVKIface.free() 释放 RM/UVM/CPU 映射 异常顺序下 RM 句柄与 UVM range 能否全部回滚
    BufferSpec.external_ptr 提供地址的上层 do_free() 不调用底层释放 提供方是否保持地址有效到最后一次 GPU 访问
    CPU 缓冲的 NV 映射 原缓冲持有宿主地址 宿主缓冲释放时触发 _unmap() 多卡重复映射与 owns_range=False 的引用回收
    VA 地址池 NVKIface 类级 BumpAllocator UVM_FREE 释放映射,但 bump 指针不后退 大量创建释放后 VA 是否耗尽
    设备树与 GPFIFO NVDevice 与 iface 进程结束时同步并调用可选 device_fini() NVKIface 分支没有逐对象显式销毁路径

    静态风险与边界

    NVKIface.alloc() 依次分配 VA、RM 物理句柄、CPU mmap、UVM range、DMA 映射与 GPU 映射;若中途失败,本函数未看到配对的逐阶段 try/finally 回滚,仅靠 Buffer 成功返回后的析构无法处理此前的半成品状态。NVKIface.free() 的 RM_FREE 条件按句柄与主机对象枚举比较,主机对象与显存对象释放走不同路径,因此不能把 UVM_FREE 等同为每一层资源都已归还。

    更结构性的问题是 VA 池:高低地址池使用 wrap=False 且没有对应 free,即使单个缓冲的 UVM 映射已释放,进程内地址预留仍单调增长。对长时间运行、模型热替换或多插件加载,必须设计可回收的地址分配或代际隔离,并测最坏地址消耗;这是代码结构上的上界问题,不等于当前可达负载已触发。

    需要区分的是,PCI/GSP 路径使用不同的基类,不能直接套用 NVKIface 的 UVM 生命周期推论。本机没有 NVIDIA 目标设备,也没有执行设备 ioctl 或映射操作,上述影响须在隔离设备上做故障注入与句柄、映射计数验证。

    硬件插件要求

    车载硬件插件必须显式声明控制面(驱动与 PCI)、地址空间、分配与映射与释放的权责、P2P 资格和故障回滚;推理插件不得拥有 CAN 发送或更改 panda 安全门的权限。若借鉴该后端,应把设备发现、内存所有权、队列提交与资源释放拆成彼此独立的契约,再逐项验收。

    常见问题

    ioctl 成功是不是就代表 RM/UVM 对象创建成功?

    不代表。nv_iowr() 等只检查系统调用返回,RM 与 UVM 的包内状态会分别再检查,显存不足被映射为 MemoryError。不能以 ioctl 成功替代对象创建成功。

    NVKIface 和 PCIIface 能同时用吗?

    不能随意混用。PCIIface.__init__() 会拒绝已初始化 NVKIface 的进程,原因是固定地址 mmap 可能覆盖 UVM 分配。两条路径使用不同的基类与生命周期。

    VA 地址池会随创建释放一起回收吗?

    不会。低、高地址池使用 wrap=False 的 BumpAllocator,没有对应的 free;即使单个缓冲的 UVM 映射已释放,进程内地址预留仍单调增长。长时间运行或多插件加载需要额外的回收或隔离设计。

    NV 后端是 openpilot 车端的默认推理路径吗?

    不是。openpilot 的 load_oob() 默认不使用 NV,因此本专题描述的是可用路径而非已部署行为,车端实际推理路径另有选择。

    延伸阅读

    参考资料

    • 项目 02 —— openpilot-research/TINYGRAD_NV_RM_UVM_PCI_MEMORY_OWNERSHIP_DEEP_DIVE.md(取用了接口互斥、RM/UVM 分配与映射、所有权矩阵、VA 池与失败回滚边界)。
    • 项目 02 —— openpilot-research/TINYGRAD_NV_QMD_GPFIFO_PROGRAM_CACHE_CONTRACT_DEEP_DIVE.md(关联的 NV 队列与程序镜像专题,用于区分内存所有权与队列提交,本文未展开其实现)。

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

    更新记录

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