尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

15、显示缓冲区管理:ION/DMA-BUF框架、GraphicBuffer分配、Fence同步机制、BufferQueue

15、显示缓冲区管理:ION/DMA-BUF框架、GraphicBuffer分配、Fence同步机制、BufferQueue 15.1 ION/DMA-BUF框架内存分配的基石先讲ION。这是Android系统里统一的内存分配器专门给多媒体、显示、相机这些模块用的。为什么需要ION因为显示驱动需要的内存不是普通的内存。它需要连续物理内存或者至少是IOMMU映射好的内存还要支持跨进程共享。你想想看应用层画了一帧画面要传给SurfaceFlinger合成再送到显示硬件——这中间要跨多少道进程ION的核心概念就是「Heap」。每个Heap代表一种内存类型Heap类型用途特点ION_HEAP_TYPE_SYSTEM普通系统内存非连续速度一般ION_HEAP_TYPE_SYSTEM_CONTIG连续物理内存用于显示控制器直接访问ION_HEAP_TYPE_CARVEOUT预留内存固定大小不参与页回收ION_HEAP_TYPE_CHUNK分块内存MTK平台常用兼顾连续性和灵活性我在MTK8678上调试时遇到过一个问题用SYSTEM_HEAP分配显示缓冲区结果画面偶尔出现条纹。查了半天发现是内存不连续导致DMA传输出错。后来换成SYSTEM_CONTIG_HEAP问题就解决了。关键点显示缓冲区必须使用物理连续内存或者通过IOMMU映射成连续的虚拟地址空间。DMA-BUF是ION的底层机制。它本质上是一个文件描述符代表一块可以跨设备、跨进程共享的内存。ION分配的内存最终都会导出为一个DMA-BUF fd。// ION分配内存示例 struct ion_allocation_data alloc_data { .len buffer_size, .heap_id_mask 1 ION_HEAP_TYPE_SYSTEM_CONTIG, .flags ION_FLAG_CACHED, }; int ret ioctl(ion_fd, ION_IOC_ALLOC, alloc_data); // 获取DMA-BUF fd int dma_buf_fd alloc_data.fd;嗯这里要注意ION的API在较新内核中已经被DMA-BUF Heaps替代了。但MTK平台为了兼容性内部还是保留了ION的接口封装。你看到代码里还有ION相关的调用别奇怪。15.2 GraphicBuffer分配从应用到驱动的桥梁GraphicBuffer是Android上层对ION/DMA-BUF的封装。应用层画图时不会直接调ION接口而是通过GraphicBufferAllocator来申请。它的分配流程是这样的应用调用ANativeWindow::dequeueBuffer()SurfaceFlinger通过BufferQueue向GraphicBufferAllocator请求bufferGraphicBufferAllocator调用ION分配内存ION返回DMA-BUF fd封装成GraphicBuffer对象GraphicBuffer通过Binder跨进程传递给应用我个人习惯在调试时先确认GraphicBuffer的格式和尺寸是否正确。MTK8678支持多种像素格式比如RGBA_8888、RGBX_8888、NV12等。如果格式不匹配显示硬件会直接罢工。调试技巧在dumpsys SurfaceFlinger的输出中可以看到每个Layer的buffer信息。重点关注buffer的width、height、format和usage flags。GraphicBuffer还有一个重要属性叫usage flags。它告诉ION这块内存要用来做什么GRALLOC_USAGE_HW_RENDER给GPU渲染用GRALLOC_USAGE_HW_COMPOSER给显示合成器用GRALLOC_USAGE_HW_VIDEO_ENCODER给视频编码器用GRALLOC_USAGE_SW_READ_OFTENCPU频繁读取这些flags会影响内存的cache策略和物理位置。比如HW_COMPOSER通常要求uncached内存因为显示控制器不经过CPU cache。15.3 Fence同步机制别让画面撕裂多屏显示最头疼的问题是什么同步。你想想看GPU在渲染第N帧显示控制器在读取第N-1帧如果两者同时操作同一块buffer画面就会撕裂。Fence就是用来解决这个问题的。它本质上是一个内核态的同步原语用文件描述符表示。Fence有两种状态signaled操作已完成可以安全使用bufferunsignaled操作还在进行中别碰buffer典型的同步流程是这样的GPU开始渲染buffer A创建一个release fenceGPU渲染完成后signal这个fence显示控制器等待这个fence signaled后才开始读取buffer A显示控制器读取完成后创建另一个release fenceGPU等待这个fence才能把新数据写入buffer A我曾经在MTK8678上遇到一个诡异的问题双屏显示时副屏偶尔卡住不动。查了三天发现是Fence超时导致的。主屏的release fence一直没signal副屏的acquire fence等不到就卡死了。避坑指南我曾经因为Fence超时时间设置太短导致正常操作也被误判为超时。建议把超时时间设为5秒以上调试阶段甚至可以设到10秒。在代码层面Fence的操作主要通过sync_file接口// 创建fence int fence_fd sync_file_create(gpu_render_fence); // 等待fence struct sync_fence_info_data *info; int ret ioctl(fence_fd, SYNC_IOC_FENCE_INFO, info); // 合并多个fence int merged_fd sync_file_merge(merged_fence, fence1_fd, fence2_fd);15.4 BufferQueue生产者-消费者模型BufferQueue是Android显示系统的核心数据结构。它实现了生产者-消费者模型让应用生产者和SurfaceFlinger消费者能高效地交换buffer。BufferQueue的核心成员成员作用mSlots[]存储所有buffer的数组默认64个slotmQueue已填充好的buffer队列等待消费者处理mFreeSlots空闲slot列表mActiveBuffers当前正在被生产或消费的buffer它的工作流程说白了就是三个操作dequeueBuffer生产者申请一个空闲bufferqueueBuffer生产者把填充好的buffer放入队列acquireBuffer消费者从队列取出bufferreleaseBuffer消费者用完buffer后归还多屏场景下每个显示设备都有自己的BufferQueue。MTK8678支持三个屏幕同时显示那就需要三个独立的BufferQueue实例。这里有个坑BufferQueue的深度即mQueue中最多能放几个buffer会影响延迟。深度太小生产者容易阻塞深度太大画面延迟会增加。我一般建议设成2或3兼顾流畅度和延迟。核心要点BufferQueue Fence ION这三者配合构成了Android显示系统的完整数据通路。ION负责「在哪里放数据」Fence负责「什么时候可以动数据」BufferQueue负责「数据怎么流转」。15.5 实战经验多屏显示的内存优化最后分享一个我在MTK8678项目中的优化经验。当时客户要求三屏同时显示1080p视频每个屏幕60fps。算一下1920×1080×4字节×3屏×60fps ≈ 1.5GB/s的带宽。这还没算GPU和CPU的访问。内存带宽很快就成了瓶颈。我做了几件事使用AFBC压缩ARM的帧缓冲压缩技术可以减少30%-50%的带宽调整ION heap分配策略把显示buffer放在专用的carveout内存里避免和系统内存争抢优化Fence等待逻辑减少不必要的同步等待让流水线更紧凑BufferQueue深度调优从默认的1改成2让GPU和显示控制器能并行工作优化后带宽占用降到了800MB/s左右三屏播放流畅无卡顿。嗯这一章的内容就到这里。缓冲区管理是显示驱动的核心也是问题最多的地方。建议你在实际调试时先从ION分配开始排查再看Fence同步最后检查BufferQueue的状态。按这个顺序来能省不少时间。
返回列表