ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

Rockchip工位机黑屏真相:scrcpy引发的DMA-BUF内存泄漏

2026/9/12 15:19:39 拓冰建站 浏览量
Rockchip工位机黑屏真相:scrcpy引发的DMA-BUF内存泄漏 1. 黑屏卡死不是App锅一个被误判两周的工位机故障真相刚接手这台Rockchip平台的Android工位机时我下意识以为又是一次典型的“应用崩溃导致系统僵死”——毕竟产线同事反复强调“只要打开那个定制监控App10分钟内必黑屏adb shell完全无响应连reboot都得硬按电源键。”我们照例抓了ANR日志、dumpsys meminfo、/proc/meminfo快照甚至重刷了整套固件。App侧代码review三轮JNI层内存泄漏检查两遍连SurfaceView的onSurfaceDestroyed回调都加了log——结果全无异常。直到第17次复现时我注意到一个反常细节黑屏前3秒设备USB端口的LED灯会高频闪烁一次且仅在scrcpy连接状态下触发。这个微小现象像一根针扎破了“App是元凶”的思维定式。后来证明真正吃掉系统最后一点内存的既不是Java层的Bitmap缓存也不是Native层的OpenGL纹理而是scrcpy与Rockchip视频编码器之间一场静默的DMA-BUF资源争夺战。它不报错、不打log、不触发OOM Killer只在物理内存耗尽时让整个系统瞬间凝固。这种故障模式在RK3399/RK3566平台上尤为典型因为它们的VPUVideo Processing Unit依赖Linux内核的DMA-BUF子系统进行零拷贝视频帧传输而scrcpy的默认配置恰恰踩中了Rockchip驱动里一个未公开的资源释放边界条件。如果你的工位机也用Rockchip芯片、跑scrcpy做远程调试、且黑屏前有USB通信异常那这篇排查实录就是为你写的——它不讲理论只还原我们如何从“怀疑App”一步步推翻所有假设最终定位到内核级DMA-BUF泄漏的完整链路。2. scrcpy与Rockchip编码器的协作机制为什么这里会埋雷要理解泄漏根源必须先拆解scrcpy和Rockchip VPU的真实协作流程。很多人以为scrcpy只是把屏幕数据“抓出来再推过去”实际上在Rockchip平台上它走的是硬件编码加速路径scrcpy通过ADB调用screenrecord命令而screenrecord在RK平台会自动启用VPU的H.264编码器而非软件编码器如libx264。这个过程涉及三层关键交互2.1 DMA-BUF在视频流水线中的真实角色DMA-BUF不是普通内存块它是Linux内核为跨设备共享内存设计的“通行证”。在Rockchip VPU编码流程中它的流转路径如下Step 1SurfaceFlinger将合成后的帧写入GraphicBuffer该Buffer底层由ION分配本质是一块DMA-BUFStep 2screenrecord进程通过gralloc接口获取该Buffer的fd文件描述符并将其传递给VPU驱动Step 3VPU硬件直接通过DMA引擎读取该DMA-BUF的物理地址完成编码输出H.264 NALUStep 4编码完成后VPU驱动本应调用dma_buf_unmap_attachment()释放对Buffer的引用但Rockchip某版本驱动v4.4.194-rk3399在此处存在条件竞争当scrcpy频繁启停如网络抖动导致重连驱动可能漏掉一次unmap调用。提示这个泄漏点不会出现在dmesg里因为驱动认为“引用计数仍大于0无需释放”。它只在/sys/kernel/debug/dma_buf/目录下留下痕迹——那里会堆积数百个名为rockchip-vpu-enc的DMA-BUF条目每个占用2MB显存RK3399的VPU Buffer默认大小。2.2 scrcpy的默认行为如何加剧问题scrcpy v1.24默认启用--video-codech264且在连接断开时执行adb shell am force-stop com.genymobile.scrcpy。问题在于force-stop不等于优雅退出。它会杀死scrcpy进程但screenrecord子进程可能仍在运行尤其当编码队列有积压时。此时VPU驱动收到的release信号来自已销毁的进程上下文导致DMA-BUF引用计数无法正确归零。我们实测发现每强制断开一次scrcpy连接/sys/kernel/debug/dma_buf/中就新增3~5个未释放Buffer连续10次后系统剩余可用内存跌破100MBSurfaceFlinger因无法分配新GraphicBuffer而停止刷新屏幕冻结。2.3 为什么App背了黑锅监控App被冤枉是因为它恰好是触发scrcpy使用的“导火索”。产线操作员习惯在启动App后立即用scrcpy投屏查看日志而App本身会持续请求高帧率屏幕捕获用于实时状态渲染。这导致scrcpy连接频次激增——平均每2分钟一次重连。App只是“高频使用者”而非“泄漏源”。真正的分水岭测试很简单关闭App仅用scrcpy连接空闲桌面同样5小时后黑屏反之开启App但禁用scrcpy设备可稳定运行72小时以上。这个对照实验直接否定了App侧责任。3. 排查链路全记录从USB闪烁到DMA-BUF泄漏确认故障复现本身不难难的是在无log、无crash、无明显告警的情况下锁定根因。我们的排查不是线性推进而是多线程并行验证最终交叉印证指向DMA-BUF。以下是完整时间线与关键证据链3.1 第一阶段排除用户态干扰耗时1.5天我们首先隔离所有可能的用户态嫌疑App侧用adb shell dumpsys activity top确认App无ANR用adb shell procrank | grep app-pid观察其RSS稳定在80MB无增长scrcpy侧升级至v2.1.1当时最新版禁用--tunnel-forward参数排除ADB隧道问题系统服务adb shell dumpsys window显示InputManager正常dumpsys power显示WakeLock无异常持有关键动作在黑屏前3秒执行adb shell cat /proc/meminfo | grep MemAvailable发现MemAvailable从120MB骤降至18MB——这是内存耗尽的铁证但dumpsys meminfo却显示各进程RSS总和仅占40%内存说明有“幽灵内存”被占用。注意此时/proc/meminfo中的Shmem字段异常升高达200MB而Cached反而偏低。Shmem通常关联tmpfs或匿名映射但Rockchip平台的VPU驱动恰好将DMA-BUF映射到shmem区域——这个线索成为后续突破点。3.2 第二阶段内核级内存追踪耗时2天当用户态无异常时矛头转向内核。我们采用三步法启用slab内存追踪adb shell su -c echo slabinfo /proc/sysrq-trigger抓取/proc/slabinfo发现rockchip_vpu_enc相关slab缓存如rk_vpu_enc_ctx数量随scrcpy连接次数线性增长且active_objs远高于num_objs表明对象未被回收检查DMA-BUF状态adb shell su -c ls -l /sys/kernel/debug/dma_buf/输出显示327个rockchip-vpu-enc-*条目每次scrcpy连接新增约3个每个链接指向/dev/ion设备adb shell su -c cat /sys/kernel/debug/dma_buf/rockchip-vpu-enc-0001显示size: 2097152即2MBexporter: rockchip-vpuusers: 1——但对应进程PID已不存在验证内存归属adb shell su -c echo m /proc/sysrq-trigger触发内核内存信息dump解析/proc/kmsg发现rockchip-vpu ff3a0000.vpu: dma-buf leak detected: 327 buffers, total 671MB——这是驱动自检日志需在内核编译时启用CONFIG_ROCKCHIP_VPU_DEBUG才可见。3.3 第三阶段复现与注入验证耗时0.5天为100%确认我们设计了最小复现脚本#!/system/bin/sh # leak_reproduce.sh for i in $(seq 1 50); do # 启动scrcpy编码模拟产线操作 adb shell screenrecord --time-limit 1 --output-formath264 /data/local/tmp/test.h264 PID$! sleep 0.5 # 强制杀死制造泄漏条件 adb shell kill $PID # 检查DMA-BUF数量 COUNT$(adb shell su -c ls /sys/kernel/debug/dma_buf/rockchip-vpu-enc-* 2/dev/null | wc -l) echo Loop $i: DMA-BUF count $COUNT sleep 0.3 done执行后COUNT从0飙升至152且/proc/meminfo中Shmem同步增长152×2MB304MB。此时手动执行adb shell su -c echo 1 /sys/module/rockchip_vpu/parameters/force_release驱动提供的强制清理接口Shmem立即回落设备恢复响应——根因闭环确认。4. 修复方案与生产环境落地细节确认根因后修复不是简单“升级驱动”而是需要兼顾稳定性、兼容性与产线部署成本的组合策略。我们最终采用三级防御体系已在200台工位机上线6个月0复发。4.1 根治方案Rockchip驱动补丁推荐给OEM厂商我们向Rockchip提交了补丁已合入v4.4.205内核核心修改在drivers/media/platform/rockchip/vpu/rk_vpu_enc.c// 原代码仅在进程exit时释放 static void rk_vpu_enc_release(struct drm_gem_object *obj) { struct rk_vpu_enc_ctx *ctx to_rk_vpu_enc_ctx(obj); if (ctx-vpu_dev ctx-vpu_dev-vpu_enc) { // 缺少引用计数校验直接释放 rk_vpu_enc_stop(ctx); } } // 补丁后增加引用计数安全检查 static void rk_vpu_enc_release(struct drm_gem_object *obj) { struct rk_vpu_enc_ctx *ctx to_rk_vpu_enc_ctx(obj); if (!ctx || !ctx-vpu_dev || !ctx-vpu_dev-vpu_enc) return; // 关键检查当前DMA-BUF是否被其他进程引用 if (atomic_read(obj-dma_buf-file-f_count) 1) { dev_warn(ctx-vpu_dev-dev, DMA-BUF %s still referenced by %d processes, skip release, obj-name, atomic_read(obj-dma_buf-file-f_count)); return; // 防止误释放 } rk_vpu_enc_stop(ctx); }实测效果补丁后即使scrcpy强制断开DMA-BUF泄漏率降至0.02%偶发单次泄漏由内核GC自动回收内存占用曲线平稳。4.2 过渡方案scrcpy配置优化立即生效适配现有设备对无法升级内核的设备我们改造scrcpy启动逻辑禁用硬件编码scrcpy --video-codecnone --bit-rate2M强制使用软件编码CPU负载增加15%但内存安全优雅退出脚本替换原scrcpy.bat添加预退出清理echo off rem 先通知VPU释放资源 adb shell killall screenrecord 2/dev/null timeout /t 1 /nobreak nul rem 再启动scrcpy scrcpy.exe %*内存监控守护在工位机上部署轻量守护进程每5分钟检查/sys/kernel/debug/dma_buf/数量超50个则自动adb shell su -c echo 1 /sys/module/rockchip_vpu/parameters/force_release。4.3 生产环境部署 checklist落地时我们发现单纯修复技术问题不够还需解决产线实际约束固件签名问题Rockchip驱动补丁需重新签名我们协调OEM提供update.img增量包避免整包刷机scrcpy版本兼容性v2.1.1与RK3399固件存在ABI不匹配降级至v1.25.1后稳定性提升40%USB供电影响部分工位机USB口供电不足400mA导致scrcpy连接时VPU供电波动加剧DMA-BUF管理异常统一更换为带外置供电的USB集线器监控App适配要求App开发团队改用MediaProjectionAPI替代screenrecord调用从源头减少VPU占用频次。5. Rockchip平台DMA-BUF泄漏的通用识别与预防手册这次排查的价值不止于解决单个故障更沉淀出一套Rockchip Android设备的DMA-BUF健康诊断方法论。以下是我们总结的“三查一守”原则适用于所有RK平台开发者5.1 查DMA-BUF状态的黄金指标不要等黑屏才检查日常巡检只需一条命令# 快速统计VPU相关DMA-BUF adb shell su -c ls /sys/kernel/debug/dma_buf/rockchip-vpu-* 2/dev/null | wc -l # 安全阈值空闲设备应≤5个高负载设备≤30个 # 超过50个即预警需立即执行force_release更深度的诊断可结合adb shell su -c cat /sys/kernel/debug/dma_buf/rockchip-vpu-enc-0001查看单个Buffer详情adb shell su -c dmesg | grep -i dma-buf\|vpu检索内核级警告。5.2 查内存分布的异常信号/proc/meminfo中以下字段组合异常大概率指向DMA-BUF问题字段正常值泄漏征兆原因Shmem50MB200MBDMA-BUF映射到shmem区域MemAvailable300MB50MB物理内存被DMA-BUF占满Cached占MemTotal 30%~50%10%DMA-BUF不计入Cached导致缓存空间被挤压经验当Shmem增长速率与scrcpy连接频次正相关且Cached同步萎缩基本可锁定DMA-BUF泄漏。5.3 查VPU驱动的版本陷阱Rockchip不同内核版本的VPU驱动存在已知DMA-BUF问题关键版本节点RK3399平台内核4.4.194及之前版本含主流Android 8.1固件存在泄漏RK3566平台内核4.19.231及之前版本Android 11固件存在类似问题规避方案优先选用内核4.4.205/4.19.232固件或确认OEM已集成对应补丁。5.4 守产线设备的长期健康守护在工厂环境中预防比修复更重要。我们部署了自动化守护方案启动时自检设备开机后执行check_dma_buf.sh若发现残留Buffer则自动清理连接时防护修改/system/etc/init.d/99scrcpy在scrcpy启动前注入ulimit -v 524288限制进程虚拟内存512MB防止单次泄漏失控日志聚合将/sys/kernel/debug/dma_buf/状态每日上报至中央日志系统建立泄漏趋势图谱提前预测设备寿命。最后分享一个血泪教训我们曾以为“升级scrcpy就能解决”结果v2.0.0因引入新的VPU控制协议反而在RK3399上触发更严重的泄漏单次连接泄漏12个Buffer。这提醒我们——在Rockchip平台上任何涉及VPU的工具链变更都必须搭配DMA-BUF压力测试。现在我们的标准流程是新版本scrcpy/ADB/固件上线前必须跑满72小时DMA-BUF泄漏测试每分钟连接断开循环达标才放行。技术没有银弹只有敬畏细节的耐心。