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 · attachmentMermaidflowchart 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 L533-L549 (Linux v6.19)
drivers/gpu/drm/drm_prime.c - drm_gem_prime_handle_to_fd() (L510-530)c
소스 drivers/gpu/drm/drm_prime.c L510-L530 (Linux v6.19)
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
소스 drivers/gpu/drm/drm_prime.c L431-L494 (Linux v6.19)
이 함수의 분기가 이 절의 핵심입니다.
drm_gem_object_lookup(): handle로 객체를 찾습니다.- 이미 이 파일에서 내보낸 적이 있으면(
drm_prime_lookup_buf_by_handle) 그 dma-buf를 그대로 돌려줍니다. - 가져온(import) 객체면 원래의 dma-buf를 돌려줍니다(
obj->import_attach->dmabuf). - 이미
obj->dma_buf가 있으면 재사용합니다. - 아무것도 없을 때만 새로 만듭니다(
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
소스 drivers/gpu/drm/drm_prime.c L375-L407 (Linux v6.19)
obj->funcs->export가 있으면 백엔드의 것을, 없으면 코어의 기본 drm_gem_prime_export()를 씁니다.drivers/gpu/drm/drm_prime.c - drm_gem_prime_export() (L916-931)c
소스 drivers/gpu/drm/drm_prime.c L916-L931 (Linux v6.19)
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
소스 drivers/gpu/drm/drm_prime.c L827-L836 (Linux v6.19)
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
소스 drivers/gpu/drm/drm_prime.c L666-L692 (Linux v6.19)
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
소스 drivers/gpu/drm/drm_prime.c L239-L254 (Linux v6.19)
그 dma-buf 코어는 다음과 같습니다.
drivers/dma-buf/dma-buf.c - dma_buf_export() (L683-759)c
소스 drivers/dma-buf/dma-buf.c L683-L759 (Linux v6.19)
drivers/dma-buf/dma-buf.c - dma_buf_getfile() (L602-632)c
소스 drivers/dma-buf/dma-buf.c L602-L632 (Linux v6.19)
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
소스 drivers/dma-buf/dma-buf.c L126-L140 (Linux v6.19)
이름 붙이기 전의 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
소스 drivers/gpu/drm/drm_prime.c L292-L359 (Linux v6.19)
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
소스 drivers/gpu/drm/drm_prime.c L968-L1020 (Linux v6.19)
- 같은 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 와 PRIMEc
각 줄이 위에서 본 커널 코드의 어디로 이어지는지 정리하면 다음과 같습니다.
| 유저 코드 | 커널에서 일어나는 일 |
|---|---|
DRM_IOCTL_MODE_CREATE_DUMB | drm_mode_create_dumb() → dumb_create → drm_gem_handle_create(). 새 drm_file의 첫 handle은 보통 1 (표가 1부터 시작) |
DRM_IOCTL_MODE_MAP_DUMB | drm_gem_dumb_map_offset() → vma_node 주소가 offset |
mmap(..., fd, offset) | drm_gem_mmap() → drm_gem_mmap_obj() → page fault 때 페이지 연결 |
drmPrimeHandleToFD ×2 | drm_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-buf | drm_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_ops | drivers/gpu/drm/drm_prime.c |
| PRIME: dma-buf 만들기 (코어) | dma_buf_export() | drivers/dma-buf/dma-buf.c |
| PRIME: fd → handle | drm_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를 봅니다.