DMA-Buf explained: zero-copy buffer sharing between Linux drivers

Every Linux GPU-adjacent API has its own buffer objects — V4L2 buffers, VA-API surfaces, Vulkan VkDeviceMemory, GBM gbm_bo. Copying frames between them wastes bandwidth and CPU. DMA-Buf is the kernel-side mechanism that makes the same physical memory visible to all of them: a buffer allocated by one driver is exported as a plain file descriptor, and any other driver can mmap or import it.

The model

dmabuf-flow.txt
producer driver                      consumer driver
(V4L2 / VA-API / GPU)                (Vulkan / OpenCL / other GPU)
      │                                    │
      ▼                                    ▼
 allocates buffer ──export──► dma-buf fd ──import──► same pages,
                                                     zero copies

A dma-buf fd is not the memory — it’s a reference to a kernel struct dma_buf that owns the backing pages plus bookkeeping (the exporting driver, an ops table for map/sync, and an fd is the handle, so all the usual fd rules apply: dup(), SCM_RIGHTS passing over unix sockets, FD_CLOEXEC on exec, etc.).

Why an fd and not a pointer

File descriptors are the kernel’s existing capability mechanism:

  • dup() shares the same dma-buf across processes
  • SCM_RIGHTS over unix(7) sockets hands it to an unrelated process
  • poll() on the fd signals buffer readiness (fence)
  • the kernel refcount pins the pages until the last fd closes

The exporter keeps a DMA reservation object (dma_resv) inside — fences attached to it let consumers wait for producer writes to complete.

The modifier problem — the #1 gotcha

A bare VkDeviceMemory-sized blob isn’t enough: GPUs don’t store images linearly. They tile and compress them, and each vendor’s tiling is different. That’s why dma-buf pairs with DRM format modifiers (drm_fourcc.h, DRM_FORMAT_MOD_*):

modifier.hint
DRM_FORMAT_MOD_LINEAR            = 0              — plain linear
I915_FORMAT_MOD_X_TILED          = 0x100000000000001
I915_FORMAT_MOD_Y_TILED          = 0x100000000000002  (Intel)
DRM_FORMAT_MOD_AMD_GFX9_GFX10    = vendor-tiled   (AMD)

The modifier is a 64-bit value saying how the bytes are laid out. Both sides must agree on it — Vulkan queries support via VK_EXT_image_drm_format_modifier, and the exporter must be told the consumer’s modifier list.

The failure mode is silent. If the modifier doesn’t match, the import may succeed — the driver creates a VkImage happily — and the sampled pixels are garbage (tiled bytes read as linear). There is no error, only wrong colors. Always intersect the exporter’s modifiers with vkGetPhysicalDeviceImageFormatProperties2 + VkPhysicalDeviceImageDrmFormatModifierInfoEXT results before committing.

The Vulkan side

VK_EXT_external_memory_dma_buf imports the fd:

import.cpp
// 1. Query which modifiers the GPU accepts for this format:
vk::PhysicalDeviceImageFormatInfo2 fmtInfo{};
fmtInfo.setFormat(vk::Format::eR8Unorm)
       .setType(vk::ImageType::e2D)
       .setTiling(vk::ImageTiling::eDrmFormatModifierEXT)
       .setUsage(vk::ImageUsageFlagBits::eSampled);
// … DrmFormatModifier list in pNext → enumerate supported modifiers

// 2. Create the image with the negotiated modifier:
vk::ImageDrmFormatModifierExplicitCreateInfoEXT modInfo{};
modInfo.setDrmFormatModifier(chosenModifier)
       .setPlaneLayouts(planeLayouts);

vk::ImageCreateInfo ici{};
ici.setPNext(&modInfo)
   .setTiling(vk::ImageTiling::eDrmFormatModifierEXT)
   .setExternalMemoryHandleTypeFlags(
        vk::ExternalMemoryHandleTypeFlagBits::eDmaBufEXT);
// …

// 3. Import the fd:
vk::ImportMemoryFdInfoKHR import{};
import.setHandleType(vk::ExternalMemoryHandleTypeFlagBits::eDmaBufEXT)
      .setFd(dmaBufFd)
      .setMemory(memory.get());
device.bindImageMemory2(bindInfos); // or vkBindImageMemory + ImportInfo

vkImportMemoryFdKHR consumes the fd on success — the caller must not close() it afterwards (double-close recycles the number into a later import → driver samples freed BOs → GPUVM fault).

Where it shows up

  • V4L2 → Vulkan: VIDIOC_EXPBUF exports capture buffers; import as textures — zero-copy camera ingest.
  • VA-API → Vulkan: av_hwframe_map() produces an AVDRMFrameDescriptor (the dma-buf fds + fourcc + modifier) that imports per-plane as VkImage.
  • Vulkan → Vulkan across processes: export VkDeviceMemory via vkGetMemoryFdKHR — but the fd is a dma-buf, so the importer needs eDmaBufEXT, not eOpaqueFd.

Working examples for each path:

Example: VulkanExamples/p48_v4l2

Example: VulkanExamples/p49_vaapi

Example: VulkanExamples/p70_ipc

Practical rules

  • Always negotiate modifiers — never assume DRM_FORMAT_MOD_LINEAR.
  • The fd is consumable — track ownership carefully (-1 after successful import).
  • FD_CLOEXEC bites when exec’ing a child that should import the fd — clear the flag before fork.
  • The kernel serializes access via dma_resv fences — use DMA_BUF_IOCTL_SYNC or the equivalent in-driver sync to respect producer fences.

Check out similar posts by category: Vulkan Linux