Linux DRM - PRIME과 dma-buf로 버퍼 공유하기

How Linux DRM shares buffers between processes and devices with PRIME and dma-buf: handle to fd export, fd to handle import, the dma_buf_ops table, and a dumb buffer plus PRIME example.
October 30, 2026

Linux DRM - PRIME과 dma-buf로 버퍼 공유하기

Reqruies
  • Kernel: Linux v6.19 소스 기준
  • Library: libdrm 2.4.129
  • 선행 글: Linux DRM 4편 GEM

Introduction

편주제
1편구조와 전체 그림
2편코어 초기화와 드라이버 등록
3편open()과 ioctl() 코드 흐름
4편GEM: handle, dumb buffer, mmap
4편에서 본 GEM handle은 drm_file 안에서만 의미가 있습니다. 화면 서버(compositor)가 앱이 그린 버퍼를 받으려면, 또는 GPU가 만든 버퍼를 카메라나 비디오 디코더가 읽으려면 fd로 전달할 수 있는 공유 수단이 필요합니다. 이 글은 그 수단인 dma-buf와, GEM과 dma-buf를 오가는 DRM의 기능 PRIME을 따라갑니다.

PRIME: Sharing Buffers Between Processes and Devices

GEM handle은 drm_file 안에서만 의미가 있어서 다른 프로세스에 넘길 수 없습니다. 화면 서버(compositor)가 앱이 그린 버퍼를 받으려면, 또는 GPU가 만든 버퍼를 카메라/비디오 디코더가 읽으려면 fd로 전달 가능한 공유 수단이 필요합니다. 리눅스의 답은 dma-buf이고, DRM이 GEM과 dma-buf를 오가는 기능을 PRIME이라고 부릅니다.
dma-buf: exporter · importer · attachment
Mermaid
flowchart TB subgraph EXP["Exporter (예: GPU 드라이버 A)"] OBJ["drm_gem_object"] --- DB["struct dma_buf<br/>ops = drm_gem_prime_dmabuf_ops"] end DB --> FD["dma-buf fd<br/>SCM_RIGHTS 로 다른 프로세스에 전달 가능"] FD -->|"dma_buf_attach<br/>dma_buf_map_attachment"| ATT subgraph IMP["Importer (다른 드라이버 또는 같은 드라이버)"] ATT["dma_buf_attachment"] --> SGT["sg_table<br/>(물리 페이지 목록)"] --> OBJ2["drm_gem_object (import)"] end
  • exporter가 자기 버퍼를 struct dma_buf로 감싸 fd로 내놓습니다.
  • importer는 그 fd로 dma_buf_attach()한 뒤 dma_buf_map_attachment()로 sg_table(물리 페이지 목록)을 받아 자기 버퍼 객체를 만듭니다.
  • 데이터는 복사되지 않습니다. 두 쪽이 같은 물리 페이지를 봅니다.

export: handle → dma-buf fd

유저는 DRM_IOCTL_PRIME_HANDLE_TO_FD를 부릅니다. 이 ioctl은 flags에 DRM_RENDER_ALLOW가 있어서 render node에서도 됩니다(3편의 ioctl 표).
drivers/gpu/drm/drm_prime.c - drm_prime_handle_to_fd_ioctl() (L533-549)
c
drivers/gpu/drm/drm_prime.c - drm_gem_prime_handle_to_fd() (L510-530)
c
fd를 하나 예약하고, dma-buf를 얻어서, fd_install()로 그 fd에 연결합니다. dma-buf를 얻는 핵심 함수는 drm_gem_prime_handle_to_dmabuf()입니다.
drivers/gpu/drm/drm_prime.c - drm_gem_prime_handle_to_dmabuf() (L431-494)
c
이 함수의 분기가 이 절의 핵심입니다.
  1. drm_gem_object_lookup(): handle로 객체를 찾습니다.
  2. 이미 이 파일에서 내보낸 적이 있으면(drm_prime_lookup_buf_by_handle) 그 dma-buf를 그대로 돌려줍니다.
  3. 가져온(import) 객체면 원래의 dma-buf를 돌려줍니다(obj->import_attach->dmabuf).
  4. 이미 obj->dma_buf가 있으면 재사용합니다.
  5. 아무것도 없을 때만 새로 만듭니다(export_and_register_object).
그래서 같은 handle을 몇 번 내보내도 같은 dma-buf가 나옵니다. 아래 예제의 export #1, export #2가 같은 inode를 가리키는 이유입니다.
drivers/gpu/drm/drm_prime.c - export_and_register_object() (L375-407)
c
obj->funcs->export가 있으면 백엔드의 것을, 없으면 코어의 기본 drm_gem_prime_export()를 씁니다.
drivers/gpu/drm/drm_prime.c - drm_gem_prime_export() (L916-931)
c
dma_buf_export_info에 ops(dma-buf 연산 표)와 priv = obj(GEM 객체)를 담습니다. dma-buf가 GEM 객체를 가리키는 연결고리가 여기입니다. 연산 표는 다음과 같습니다.
drivers/gpu/drm/drm_prime.c - drm_gem_prime_dmabuf_ops (L827-836)
c
map_dma_buf는 importer가 dma_buf_map_attachment()를 부를 때 실행됩니다. GEM 객체의 funcs->get_sg_table()로 페이지 목록을 얻어 importer 장치용으로 DMA 매핑합니다.
drivers/gpu/drm/drm_prime.c - drm_gem_map_dma_buf() (L666-692)
c
drm_gem_prime_export()는 drm_gem_dmabuf_export()를 부르고, 이 함수가 dma-buf 코어의 dma_buf_export()를 부른 뒤 장치와 GEM 객체의 참조를 하나씩 더합니다(dma-buf가 살아 있는 동안 장치와 객체가 사라지지 않게 하려는 것입니다).
drivers/gpu/drm/drm_prime.c - drm_gem_dmabuf_export() (L239-254)
c
그 dma-buf 코어는 다음과 같습니다.
drivers/dma-buf/dma-buf.c - dma_buf_export() (L683-759)
c
drivers/dma-buf/dma-buf.c - dma_buf_getfile() (L602-632)
c
dma-buf의 정체가 여기서 드러납니다. dma-buf = 익명 inode 위에 만든 가짜(pseudo) 파일 + 연산 표 + 사설 데이터입니다. 그래서 fd로 다룰 수 있고, SCM_RIGHTS로 다른 프로세스에 보낼 수 있으며, close()하면 참조가 내려갑니다. 이 파일의 이름이 "dmabuf"로 붙기 때문에 /proc/<pid>/fd/에서 다음과 같이 보입니다.
drivers/dma-buf/dma-buf.c - dmabuffs_dname() (L126-140)
c
이름 붙이기 전의 dma-buf는 /proc/self/fd/N이 /dmabuf:로 읽힙니다.

import: dma-buf fd → handle

받는 쪽은 DRM_IOCTL_PRIME_FD_TO_HANDLE을 부릅니다.
drivers/gpu/drm/drm_prime.c - drm_gem_prime_fd_to_handle() (L292-359)
c
dma_buf_get(fd)로 fd에서 dma-buf를 얻고, 이미 아는 dma-buf면 기존 handle을 돌려줍니다. 처음이면 drm_gem_prime_import()로 GEM 객체를 만들고 drm_gem_handle_create_tail()로 이 파일의 handle 표에 올립니다. 객체를 만드는 쪽에는 두 가지 경우가 있습니다.
drivers/gpu/drm/drm_prime.c - drm_gem_prime_import_dev() (L968-1020)
c
  • 같은 DRM 장치가 내보낸 dma-buf이면(drm_gem_is_prime_exported_dma_buf) 새 객체를 만들지 않고 원래 객체(dma_buf->priv)에 참조만 더합니다. 같은 장치끼리의 공유는 비용이 거의 없습니다.
  • 다른 장치가 내보낸 것이면 dma_buf_attach() → dma_buf_map_attachment_unlocked()로 sg_table을 받고, 드라이버 콜백 gem_prime_import_sg_table()이 그 페이지들로 자기 GEM 객체를 만듭니다. obj->import_attach가 채워지므로, 이후 이 객체는 "import된 객체"로 취급됩니다(예: 앞의 drm_gem_dumb_map_offset()는 import된 객체에 -EINVAL을 돌려줍니다).

Hands-on: Dumb Buffers and PRIME

앞의 흐름을 한 프로그램에 담았습니다. dumb buffer를 만들고 칠한 뒤 PRIME으로 내보내고, 다른 open()(다른 drm_file)에서 가져와 같은 픽셀을 읽습니다.
drm_dumb_prime.c - dumb buffer 와 PRIME
c
각 줄이 위에서 본 커널 코드의 어디로 이어지는지 정리하면 다음과 같습니다.
유저 코드커널에서 일어나는 일
DRM_IOCTL_MODE_CREATE_DUMBdrm_mode_create_dumb() → dumb_create → drm_gem_handle_create(). 새 drm_file의 첫 handle은 보통 1 (표가 1부터 시작)
DRM_IOCTL_MODE_MAP_DUMBdrm_gem_dumb_map_offset() → vma_node 주소가 offset
mmap(..., fd, offset)drm_gem_mmap() → drm_gem_mmap_obj() → page fault 때 페이지 연결
drmPrimeHandleToFD ×2drm_gem_prime_handle_to_dmabuf()가 두 번째는 캐시된 같은 dma-buf를 반환 (그래서 같은 inode)
readlink("/proc/self/fd/N")dma-buf 파일의 이름 → dmabuffs_dname() → /dmabuf:
drmPrimeFDToHandle(fd2, ...)drm_gem_prime_import_dev()의 같은 장치 분기 → 새 handle이 fd2의 표에 만들어짐
이 예제는 libdrm 2.4.129로 컴파일되는 것을 확인했습니다. 글을 쓰던 환경에서는 /dev/dri 접근 권한이 없어 실행 결과는 싣지 않았습니다. 직접 실행해 보시면 두 export가 같은 inode로 나오고, 가져온 쪽에서도 같은 픽셀 값이 읽히는 것을 볼 수 있습니다. (참고: 이 PC의 i915 같은 드라이버는 shmem helper 대신 자체 GEM 구현을 쓰지만, 이 글의 코어 부분 — handle 표, PRIME 분기 — 은 똑같이 거칩니다.)

Code Locations

단계함수위치 (Linux v6.19)
PRIME: handle → dma-bufdrm_gem_prime_handle_to_dmabuf()drivers/gpu/drm/drm_prime.c
PRIME: 새 dma-buf 만들기export_and_register_object()drivers/gpu/drm/drm_prime.c
PRIME: dma-buf 연산 표drm_gem_prime_dmabuf_opsdrivers/gpu/drm/drm_prime.c
PRIME: dma-buf 만들기 (코어)dma_buf_export()drivers/dma-buf/dma-buf.c
PRIME: fd → handledrm_gem_prime_fd_to_handle()drivers/gpu/drm/drm_prime.c
PRIME: 객체 가져오기drm_gem_prime_import_dev()drivers/gpu/drm/drm_prime.c

Summary

이 Post 에서 확인한 것은 다음과 같습니다.
  • dma-buf = 익명 inode 위의 pseudo 파일 + 연산 표 + 사설 데이터입니다. fd이므로 SCM_RIGHTS로 다른 프로세스에 보낼 수 있습니다.
  • export(drm_gem_prime_handle_to_dmabuf)는 먼저 캐시를 찾고, 없을 때만 새로 만듭니다. 같은 handle을 여러 번 내보내도 같은 dma-buf가 나옵니다.
  • import(drm_gem_prime_import_dev)는 같은 장치가 내보낸 것이면 원래 객체를 그대로 쓰고, 다른 장치 것이면 attach → map_attachment로 sg_table을 받아 드라이버 콜백으로 객체를 만듭니다. 데이터는 복사되지 않습니다.
버퍼를 공유하면 "누가 언제 쓰고 있는지"를 맞춰야 합니다. 다음에는 이 동기화를 맡는 dma_fence와, GPU 작업을 줄 세우는 drm_sched를 봅니다.
Jooojub
System S/W engineer
Explore Tags
Series
    Recent Post
    © 2026. jooojub. All right reserved.