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 마다 따로 — 같은 객체를 다른 번호로 가리킬 수 있다Mermaidflowchart 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
소스 drivers/gpu/drm/drm_gem.c L1027-L1032 (Linux v6.19)
handle을 만드는 함수는
drm_gem_handle_create()이고, 실제 일은 tail 함수가 합니다.drivers/gpu/drm/drm_gem.c - drm_gem_handle_create_tail() (L437-495)c
소스 drivers/gpu/drm/drm_gem.c L437-L495 (Linux v6.19)
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 백엔드)MermaidsequenceDiagram 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
소스 drivers/gpu/drm/drm_dumb_buffers.c L191-L228 (Linux v6.19)
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
소스 include/drm/drm_gem_shmem_helper.h L302-L304 (Linux v6.19)
drivers/gpu/drm/drm_gem_shmem_helper.c - drm_gem_shmem_dumb_create() (L558-568)c
소스 drivers/gpu/drm/drm_gem_shmem_helper.c L558-L568 (Linux v6.19)
drivers/gpu/drm/drm_gem_shmem_helper.c - drm_gem_shmem_create_with_handle() (L470-491)c
소스 drivers/gpu/drm/drm_gem_shmem_helper.c L470-L491 (Linux v6.19)
순서를 보면 다음과 같습니다.
drm_gem_shmem_create()로 객체를 만들고, 참조 카운트를 1 갖습니다.drm_gem_handle_create()로 handle을 만듭니다. 안에서drm_gem_object_handle_get()이handle_count를 올리는데, 첫 번째 handle일 때만 객체 참조를 하나 잡습니다(if (obj->handle_count++ == 0) drm_gem_object_get(obj)). 이후 handle들은 개수만 올립니다.- 마지막에
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
소스 drivers/gpu/drm/drm_gem.c L1043-L1049 (Linux v6.19)
프로세스가 비정상 종료해도 커널이 fd를 닫으면서 이 함수가 불리기 때문에, GPU 메모리가 새지 않습니다. 이제 객체를 만드는
__drm_gem_shmem_init()로 돌아가겠습니다. 이 함수가 중요한 두 가지를 합니다.drivers/gpu/drm/drm_gem_shmem_helper.c - __drm_gem_shmem_init() (L52-94)c
소스 drivers/gpu/drm/drm_gem_shmem_helper.c L52-L94 (Linux v6.19)
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_shmem_helper.c L230-L262 (Linux v6.19)
drivers/gpu/drm/drm_gem.c - drm_gem_get_pages() (L623-688)c
소스 drivers/gpu/drm/drm_gem.c L623-L688 (Linux v6.19)
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 얻기 → mmapMermaidsequenceDiagram 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
소스 drivers/gpu/drm/drm_gem.c L395-L420 (Linux v6.19)
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
소스 drivers/gpu/drm/drm_gem.c L557-L564 (Linux v6.19)
Step 2: mmap()
mmap()은 3편에서 본 DRM_GEM_FOPS의 .mmap = drm_gem_mmap으로 들어옵니다.drivers/gpu/drm/drm_gem.c - drm_gem_mmap() (L1205-1251)c
소스 drivers/gpu/drm/drm_gem.c L1205-L1251 (Linux v6.19)
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
소스 drivers/gpu/drm/drm_gem.c L1146-L1187 (Linux v6.19)
객체의
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
소스 drivers/gpu/drm/drm_gem_shmem_helper.c L653-L691 (Linux v6.19)
이제 유저가 매핑된 주소를 처음 읽거나 쓰면 page fault가 나고, 객체의 fault 핸들러가 페이지를 하나씩 연결합니다.
drivers/gpu/drm/drm_gem_shmem_helper.c - drm_gem_shmem_fault() (L571-599)c
소스 drivers/gpu/drm/drm_gem_shmem_helper.c L571-L599 (Linux v6.19)
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 fault | drm_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를 봅니다.