Linux DRM - GEM: handle, dumb buffer, mmap

How Linux DRM manages GPU buffers with GEM: drm_gem_object, per-file handles, dumb buffer creation and the mmap fake offset path with page faults in Linux v6.19.
October 29, 2026

Linux DRM - GEM: handle, dumb buffer, mmap

Reqruies
  • Kernel: Linux v6.19 소스 기준
  • 선행 글: Linux DRM 2편, 3편 open()과 ioctl()

Introduction

편주제
1편구조와 전체 그림
2편코어 초기화와 드라이버 등록
3편open()과 ioctl() 코드 흐름
1~3편에서는 DRM의 구조와 open/ioctl 흐름을 봤고, struct drm_file 안의 object_idr이 GEM handle 표라고 했습니다. 이 글은 거기서 시작합니다.
질문이 글에서 보는 코드
GPU가 쓰는 버퍼는 누가 만들고 어떻게 가리키나?GEM (drm_gem_object, handle)
가장 단순한 버퍼는 어떻게 만들어지나?dumb buffer 생성 (DRM_IOCTL_MODE_CREATE_DUMB)
그 버퍼를 CPU에서 어떻게 읽고 쓰나?mmap, fake offset, page fault

Why Is Memory Management DRM's Job

GPU는 CPU와 별개의 프로세서라서, 그림을 그리려면 CPU와 GPU가 같은 메모리를 함께 다뤄야 합니다. 텍스처, 프레임 버퍼, 명령 버퍼가 모두 그런 메모리입니다. 여기에는 일반 malloc으로 풀 수 없는 문제가 있습니다.
  • GPU가 접근하려면 메모리가 물리 페이지로 고정(pin)되어 있어야 하고, GPU용 주소(IOMMU나 GPU 자체 페이지 테이블)로 매핑되어야 합니다.
  • 프로세스가 죽으면 그 프로세스가 쓰던 GPU 메모리는 자동으로 회수되어야 합니다.
  • 화면 서버와 앱처럼 다른 프로세스가 같은 버퍼를 봐야 합니다.
  • GPU가 쓰는 중에 CPU가 건드리지 않도록 동기화가 필요합니다.
DRM은 이 문제를 GEM(Graphics Execution Manager)이라는 인터페이스로 풉니다. 모든 GPU 버퍼를 struct drm_gem_object 하나로 표현하고, 실제 메모리를 어디서 가져올지는 백엔드(helper)가 정합니다. 메인 메모리의 shmem을 쓰는 drm_gem_shmem_helper.c, 연속 메모리를 쓰는 drm_gem_dma_helper.c, GPU 전용 메모리(VRAM)까지 관리하는 TTM 기반 drm_gem_ttm_helper.c가 대표적입니다. 이 글의 코드는 가장 단순한 shmem 백엔드(vkms 등이 사용)로 따라가겠습니다.

The Core of GEM: drm_gem_object and Handles

The Object: struct drm_gem_object

include/drm/drm_gem.h - struct drm_gem_object (L280-455)
c
소스 include/drm/drm_gem.h L280-L455 (Linux v6.19)
  • funcs: 백엔드가 구현하는 함수 표(free, mmap, vmap, export, get_sg_table ...)입니다. 코어는 공통 일을 하고 메모리 종류에 따라 다른 일은 여기에 위임합니다.
  • filp: shmem 백엔드에서 객체의 실제 메모리를 담은 shmem 파일입니다.
  • vma_node: mmap할 때 객체를 찾기 위한 "가짜 오프셋"을 담습니다(아래 mmap 절).
  • dma_buf, import_attach: 다른 장치와 공유할 때 쓰는 dma-buf 연결입니다(PRIME 절).
  • resv: 이 버퍼에 걸린 fence를 모아 두는 dma_resv입니다(동기화 절).
include/drm/drm_gem.h - struct drm_gem_object_funcs (L72-235)
c
소스 include/drm/drm_gem.h L72-L235 (Linux v6.19)

The Name Tag: Handles Are per fd

유저는 객체의 포인터를 알 수 없습니다. 대신 정수 handle을 받습니다. 이 handle은 1편에서 본 drm_file.object_idr의 인덱스입니다. 즉 handle은 open()한 파일(fd)마다 독립적인 번호 공간이고, 같은 객체여도 프로세스마다 handle이 다릅니다.
handle 은 drm_file 마다 따로 — 같은 객체를 다른 번호로 가리킬 수 있다
Mermaid
flowchart LR subgraph PA["프로세스 A 의 drm_file"] IA["object_idr<br/>1 → obj X<br/>2 → obj Y"] end subgraph PB["프로세스 B 의 drm_file"] IB["object_idr<br/>1 → obj Y<br/>2 → obj Z"] end X["drm_gem_object X"] Y["drm_gem_object Y<br/>handle_count = 2"] Z["drm_gem_object Z"] IA -->|"handle 1"| X IA -->|"handle 2"| Y IB -->|"handle 1"| Y IB -->|"handle 2"| Z
이 표는 open() 때 초기화됩니다(3편의 drm_file_alloc). 번호가 1부터 시작하는 것도 여기서 정해집니다.
drivers/gpu/drm/drm_gem.c - drm_gem_open() (L1027-1032)
c
handle을 만드는 함수는 drm_gem_handle_create()이고, 실제 일은 tail 함수가 합니다.
drivers/gpu/drm/drm_gem.c - drm_gem_handle_create_tail() (L437-495)
c
  • idr_alloc(&file_priv->object_idr, NULL, 1, 0, ...): 이 파일의 표에서 1 이상의 빈 번호를 하나 잡습니다. 처음에는 객체 포인터 대신 NULL을 넣어 둡니다.
  • drm_vma_node_allow(): 이 파일이 이 객체를 mmap할 수 있도록 허락합니다(아래에서 drm_vma_node_is_allowed로 검사합니다).
  • obj->funcs->open(): 백엔드 콜백입니다.
  • 마지막에 idr_replace()로 NULL을 실제 객체로 바꿉니다. 중간에 다른 스레드가 반쯤 만들어진 handle을 조회하지 못하게 하려는 순서입니다.

Code Flow 1: Creating a Dumb Buffer

가장 단순한 버퍼 생성 경로인 dumb buffer로 GEM을 따라가 보겠습니다. 화면에 소프트웨어로 그릴 때 쓰는 가장 기본적인 버퍼로, GPU 가속 기능은 없고 "CPU가 칠할 수 있는 평평한 메모리"입니다. 유저는 DRM_IOCTL_MODE_CREATE_DUMB 하나로 만듭니다.
DRM_IOCTL_MODE_CREATE_DUMB 처리 흐름 (shmem 백엔드)
Mermaid
sequenceDiagram participant U as 유저 participant D as drm_mode_create_dumb participant S as shmem helper participant G as GEM core U->>D: CREATE_DUMB<br/>(width, height, bpp) D->>D: 크기 검사<br/>(overflow, PAGE_ALIGN) D->>S: driver->dumb_create() S->>S: shmem 파일 생성<br/>mmap offset 예약 S->>G: drm_gem_handle_create() G->>G: idr_alloc → handle G-->>U: handle · pitch · size
유저가 넘긴 width, height, bpp로 크기를 계산하고 검사하는 것은 코어의 일입니다.
drivers/gpu/drm/drm_dumb_buffers.c - drm_mode_create_dumb() (L191-228)
c
dev->driver->dumb_create가 드라이버 콜백입니다. 2편에서 vkms의 drm_driver에 있던 DRM_GEM_SHMEM_DRIVER_OPS 매크로가 이 콜백을 drm_gem_shmem_dumb_create로 채워 줍니다.
include/drm/drm_gem_shmem_helper.h - #define DRM_GEM_SHMEM_DRIVER_OPS (L302-304)
c
drivers/gpu/drm/drm_gem_shmem_helper.c - drm_gem_shmem_dumb_create() (L558-568)
c
drivers/gpu/drm/drm_gem_shmem_helper.c - drm_gem_shmem_create_with_handle() (L470-491)
c
순서를 보면 다음과 같습니다.
  1. drm_gem_shmem_create()로 객체를 만들고, 참조 카운트를 1 갖습니다.
  2. drm_gem_handle_create()로 handle을 만듭니다. 안에서 drm_gem_object_handle_get()이 handle_count를 올리는데, 첫 번째 handle일 때만 객체 참조를 하나 잡습니다(if (obj->handle_count++ == 0) drm_gem_object_get(obj)). 이후 handle들은 개수만 올립니다.
  3. 마지막에 drm_gem_object_put()으로 처음의 참조를 내려놓습니다(소스 주석: "handle holds it now"). 이제 객체의 수명은 handle이 쥐고 있습니다. 유저가 handle을 닫거나, 파일이 닫힐 때 아래의 drm_gem_release()가 이 파일의 object_idr에 남은 handle을 모두 풀어서 객체가 정리됩니다.
파일이 닫힐 때의 정리는 이렇게 단순합니다.
drivers/gpu/drm/drm_gem.c - drm_gem_release() (L1043-1049)
c
프로세스가 비정상 종료해도 커널이 fd를 닫으면서 이 함수가 불리기 때문에, GPU 메모리가 새지 않습니다. 이제 객체를 만드는 __drm_gem_shmem_init()로 돌아가겠습니다. 이 함수가 중요한 두 가지를 합니다.
drivers/gpu/drm/drm_gem_shmem_helper.c - __drm_gem_shmem_init() (L52-94)
c
  • drm_gem_object_init_with_mnt()는 shmem_file_setup("drm mm object", size, ...)로 shmem 파일을 만듭니다. 객체 하나가 사실상 크기가 정해진 익명 메모리 파일 하나인 셈입니다.
  • drm_gem_create_mmap_offset()로 이 객체의 mmap용 가짜 오프셋을 미리 예약합니다(아래 mmap 절).
여기서는 아직 물리 페이지를 할당하지 않았습니다. 페이지는 필요해질 때 drm_gem_shmem_get_pages_locked()에서 잡습니다.
drivers/gpu/drm/drm_gem_shmem_helper.c - drm_gem_shmem_get_pages_locked() (L230-262)
c
drivers/gpu/drm/drm_gem.c - drm_gem_get_pages() (L623-688)
c
shmem_read_folio_gfp()로 shmem 파일의 페이지를 읽어(없으면 새로 할당해) 모으고, mapping_set_unevictable()로 스왑 아웃되지 않게 고정합니다. 앞서 말한 "GPU가 쓰려면 물리 페이지로 고정되어야 한다"가 코드로 보이는 부분입니다.

Code Flow 2: Painting from the CPU with mmap

버퍼는 만들었지만 CPU에서 아직 접근할 수 없습니다. /dev/dri/card0 파일 하나에 여러 버퍼가 있으니, mmap(fd, ...)만으로는 어느 버퍼인지 알 수 없습니다. DRM은 이를 fake offset으로 풉니다. 파일의 오프셋(mmap의 마지막 인자)을 객체 식별자로 쓰는 것입니다.
mmap 의 두 단계 : offset 얻기 → mmap
Mermaid
sequenceDiagram participant U as 유저 participant K as DRM core participant V as VM (page fault) U->>K: ioctl(MAP_DUMB, {handle}) K->>K: handle → drm_gem_object (object_idr) K-->>U: offset (obj->vma_node 의 주소) U->>K: mmap(NULL, size, ..., fd, offset) K->>K: drm_gem_mmap(): offset → vma_node → obj<br/>drm_vma_node_is_allowed() 검사 K->>K: drm_gem_mmap_obj(): vm_ops 설정, funcs->mmap() U->>V: 첫 접근 V->>K: fault 핸들러 drm_gem_shmem_fault() K-->>V: shmem 페이지를 PTE에 연결

Step 1: Getting the Offset (MAP_DUMB)

drivers/gpu/drm/drm_gem.c - drm_gem_dumb_map_offset() (L395-420)
c
drm_gem_object_lookup()이 handle로 객체를 찾고(object_idr 조회), 객체에 예약된 vma_node의 주소를 offset으로 돌려줍니다. 이 offset은 실제 파일 위치가 아니라 drm_vma_offset_manager가 장치마다 관리하는 가짜 주소입니다.
drivers/gpu/drm/drm_gem.c - drm_gem_create_mmap_offset_size() (L557-564)
c

Step 2: mmap()

mmap()은 3편에서 본 DRM_GEM_FOPS의 .mmap = drm_gem_mmap으로 들어옵니다.
drivers/gpu/drm/drm_gem.c - drm_gem_mmap() (L1205-1251)
c
  • vma->vm_pgoff(= offset을 페이지로 나눈 값)로 vma_node를 찾고, container_of로 drm_gem_object를 얻습니다.
  • drm_vma_node_is_allowed(node, priv): 앞서 drm_vma_node_allow()로 허락받은 파일만 통과합니다. 다른 프로세스가 offset 숫자만 알아서 남의 버퍼를 매핑하는 것을 막는 장치입니다.
drivers/gpu/drm/drm_gem.c - drm_gem_mmap_obj() (L1146-1187)
c
객체의 funcs->mmap과 vm_ops를 연결합니다. shmem 백엔드는 이 mmap에서 실제로 페이지를 확보(get_pages)하고 VM_PFNMAP 매핑으로 설정합니다.
drivers/gpu/drm/drm_gem_shmem_helper.c - drm_gem_shmem_mmap() (L653-691)
c
이제 유저가 매핑된 주소를 처음 읽거나 쓰면 page fault가 나고, 객체의 fault 핸들러가 페이지를 하나씩 연결합니다.
drivers/gpu/drm/drm_gem_shmem_helper.c - drm_gem_shmem_fault() (L571-599)
c
shmem->pages[page_offset]가 앞서 drm_gem_get_pages()가 모은 배열이고, vmf_insert_pfn()이 그 페이지를 유저의 가상 주소에 연결합니다. 이로써 유저의 *(uint32_t *)(px + ...) = color 한 줄이 GEM 객체의 shmem 페이지를 직접 쓰게 됩니다.

Code Locations

단계함수위치 (Linux v6.19)
GEM: handle 표 초기화drm_gem_open()drivers/gpu/drm/drm_gem.c
GEM: handle 만들기 (idr_alloc)drm_gem_handle_create_tail()drivers/gpu/drm/drm_gem.c
GEM: 객체 조회drm_gem_object_lookup()drivers/gpu/drm/drm_gem.c
dumb: 크기 검사와 dumb_create 호출drm_mode_create_dumb()drivers/gpu/drm/drm_dumb_buffers.c
dumb: shmem 백엔드의 생성drm_gem_shmem_dumb_create()drivers/gpu/drm/drm_gem_shmem_helper.c
dumb: 생성과 handle 연결drm_gem_shmem_create_with_handle()drivers/gpu/drm/drm_gem_shmem_helper.c
dumb: shmem 파일 생성, mmap offset 예약__drm_gem_shmem_init()drivers/gpu/drm/drm_gem_shmem_helper.c
dumb: 페이지 확보drm_gem_get_pages()drivers/gpu/drm/drm_gem.c
mmap: offset 돌려주기drm_gem_dumb_map_offset()drivers/gpu/drm/drm_gem.c
mmap: 진입drm_gem_mmap()drivers/gpu/drm/drm_gem.c
mmap: vm_ops 연결drm_gem_mmap_obj()drivers/gpu/drm/drm_gem.c
mmap: page faultdrm_gem_shmem_fault()drivers/gpu/drm/drm_gem_shmem_helper.c

Summary

이 Post 에서 확인한 것은 다음과 같습니다.
  • GPU 버퍼는 GEM 객체 하나로 표현되고, 유저는 fd(drm_file)마다 독립적인 handle로 가리킵니다. handle의 수명이 객체의 수명을 쥡니다.
  • dumb buffer는 shmem 파일 + mmap fake offset으로 구현되고, 페이지는 처음 필요할 때 확보됩니다.
  • mmap은 offset으로 객체를 찾아 page fault 때 페이지를 하나씩 연결합니다. 남의 버퍼를 offset만 알아서 매핑하지 못하게 drm_vma_node_is_allowed()가 막습니다.
handle은 이 drm_file 안에서만 의미가 있습니다. 다음에는 이 버퍼를 다른 프로세스나 다른 장치와 공유하는 방법, 즉 PRIME과 dma-buf를 봅니다.
Jooojub
System S/W engineer
Explore Tags
Series
    Recent Post
    © 2026. jooojub. All right reserved.