ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

第22章:用户态DMA:通过V4L2、ALSA或UDMABUF等接口,在用户态直接操作DMA缓冲区,减少内核态切换开销。

第22章:用户态DMA:通过V4L2、ALSA或UDMABUF等接口,在用户态直接操作DMA缓冲区,减少内核态切换开销。 用户态DMA。说实话做多媒体驱动这么多年我踩过最大的坑就是——内核态和用户态之间的数据拷贝。你想想看摄像头一帧数据进来DMA先把数据搬到内核缓冲区然后应用程序再通过read/ioctl把数据拷到用户空间。这一来一回CPU被占得死死的带宽也浪费了。我当年在MT6765上调Camera预览发现帧率死活上不去。查了半天原来是每帧数据在内核态和用户态之间拷贝了三次三次啊兄弟们。后来改成用户态DMA直接操作缓冲区帧率直接翻倍。所以这一章咱们就专门讲讲怎么在用户态直接操作DMA缓冲区把内核态切换的开销降到最低。22.1 为什么需要用户态DMA先看一个典型的数据流硬件设备 → DMA → 内核缓冲区 → copy_to_user() → 用户缓冲区 → 应用程序处理这里面有两个问题上下文切换每次系统调用都要切到内核态再切回来。一次切换几十微秒帧率一高就扛不住。数据拷贝DMA写完内核缓冲区还得再拷一份到用户空间。对于4K视频一帧就几MB拷贝开销非常大。用户态DMA的思路很简单让DMA直接把数据写到用户空间的缓冲区里。应用程序零拷贝直接操作。核心思想DMA引擎直接访问用户空间的物理内存应用程序通过mmap映射后直接读写。整个过程不需要经过内核。22.2 V4L2的用户态DMADMABUF机制V4L2Video for Linux 2是Linux下视频设备的标准接口。在MTK平台上Camera和ISP基本都是走V4L2。V4L2支持三种缓冲区模式Read/Write模式最传统数据在内核和用户之间拷贝MMAP模式内核分配缓冲区用户通过mmap映射DMABUF模式用户分配缓冲区DMA直接写入我个人最推荐的就是DMABUF模式。为什么因为缓冲区完全由用户态管理DMA直接写进去零拷贝。来看一个实际例子// 1. 用户态分配DMA缓冲区 int dma_fd dma_buf_alloc(size); void *buf mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, dma_fd, 0); // 2. 把DMABUF传给V4L2驱动 struct v4l2_buffer vbuf {0}; struct v4l2_plane planes[1]; struct v4l2_exportbuffer expbuf {0}; // 先申请V4L2缓冲区 vbuf.type V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; vbuf.memory V4L2_MEMORY_DMABUF; vbuf.m.planes planes; vbuf.m.planes[0].m.fd dma_fd; // 传入用户态的DMABUF fd vbuf.m.planes[0].length size; // 3. 入队DMA直接写入用户缓冲区 ioctl(fd, VIDIOC_QBUF, vbuf); // 4. 出队数据已经在用户空间了 ioctl(fd, VIDIOC_DQBUF, vbuf); // 5. 直接处理buf中的数据无需拷贝 process_frame(buf);我的经验在MTK平台上DMABUF的分配建议用ion_alloc或者dma_heap_alloc。MTK的Ion驱动对连续物理内存支持很好适合Camera这种需要大块连续DMA缓冲区的场景。22.3 ALSA的用户态DMA直接内存访问音频这边ALSAAdvanced Linux Sound Architecture也支持用户态DMA。不过音频的数据量比视频小得多所以很多人觉得没必要。但我要说——低延迟场景下用户态DMA是刚需。我记得有一次做车载语音助手要求端到端延迟小于20ms。用默认的ALSA读写模式光内核拷贝就占了5ms。改成用户态DMA后延迟直接降到12ms。ALSA的用户态DMA主要通过snd_pcm_mmap_begin和snd_pcm_mmap_commit实现// 1. 设置MMAP模式 snd_pcm_hw_params_set_access(pcm, hw, SND_PCM_ACCESS_MMAP_INTERLEAVED); // 2. 获取可写的DMA缓冲区位置 const snd_pcm_channel_area_t *areas; snd_pcm_uframes_t offset, frames 1024; snd_pcm_mmap_begin(pcm, areas, offset, frames); // 3. 直接往DMA缓冲区写数据 short *buf (short *)areas[0].addr; for (int i 0; i frames * channels; i) { buf[i] generate_sample(i); } // 4. 提交数据DMA直接发送 snd_pcm_mmap_commit(pcm, offset, frames);注意ALSA的MMAP模式要求硬件支持非连贯DMA。MTK的音频DMA控制器是支持的但有些低端平台可能不行。建议先查一下/proc/asound/card0/pcm0p/sub0/hw_params看看是否支持MMAP。22.4 UDMABUF通用DMA缓冲区共享UDMABUF是Linux 5.6引入的通用DMA缓冲区框架。说白了它把DMABUF的概念标准化了不局限于V4L2或ALSA。在MTK平台上UDMABUF特别适合跨设备共享DMA缓冲区的场景。比如Camera采集的数据直接送给GPU做图像处理或者送给Display做预览显示。使用UDMABUF的典型流程// 1. 分配UDMABUF int udmabuf_fd open(/dev/udmabuf, O_RDWR); struct udmabuf_create create { .size 4 * 1024 * 1024, // 4MB .flags UDMABUF_FLAGS_CLOEXEC, }; int dma_fd ioctl(udmabuf_fd, UDMABUF_CREATE, create); // 2. 映射到用户空间 void *buf mmap(NULL, 4 * 1024 * 1024, PROT_READ | PROT_WRITE, MAP_SHARED, dma_fd, 0); // 3. 传给V4L2驱动Camera struct v4l2_buffer vbuf; vbuf.memory V4L2_MEMORY_DMABUF; vbuf.m.planes[0].m.fd dma_fd; ioctl(cam_fd, VIDIOC_QBUF, vbuf); // 4. 传给DRM驱动Display struct drm_mode_fb_cmd2 fb {0}; fb.fb_id 0; fb.width 1920; fb.height 1080; fb.pixel_format DRM_FORMAT_NV12; fb.handles[0] dma_fd; // 同一个DMABUF ioctl(drm_fd, DRM_IOCTL_MODE_ADDFB2, fb);关键点同一个DMABUF fd可以传给多个驱动。Camera写数据Display读数据GPU处理数据——全部零拷贝。这在MTK的多媒体框架里非常实用。22.5 避坑指南用户态DMA的常见问题做了这么多年MTK驱动用户态DMA的坑我基本都踩过。挑几个典型的说说Cache一致性DMA直接写用户缓冲区CPU的Cache可能还是旧数据。我遇到过画面撕裂就是因为没做Cache刷新。解决方案用dma_buf_begin_cpu_access和dma_buf_end_cpu_access来管理Cache。内存碎片用户态分配大块连续物理内存时间长了容易碎片。我曾经在MT6789上跑了一个星期的Camera突然分配失败。后来改用预分配池提前申请好一批缓冲区循环使用。权限问题用户态直接操作DMA缓冲区安全性是个隐患。建议用seccomp或者namespace做隔离别让恶意进程乱搞。我的习惯在MTK平台上我一般用ion分配DMA缓冲区然后用dma_buf接口导出fd。这样既兼容V4L2也兼容DRM和ALSA。一套代码到处用。22.6 性能对比用户态DMA vs 传统模式最后给个数据让大家直观感受一下差距。这是我在MT6877天玑900上测的Camera预览性能模式每帧延迟CPU占用帧率传统Read/Write12.3 ms35%30 fpsMMAP模式8.1 ms22%45 fps用户态DMADMABUF4.2 ms12%60 fps看到了吧用户态DMA的延迟只有传统模式的1/3CPU占用也降了2/3。说白了省下来的CPU时间你可以拿去做更复杂的图像处理算法。
返回列表