Linux DRM - 구조와 전체 그림

An overview of the Linux Direct Rendering Manager (DRM): why it exists, the user and kernel stack, the /dev/dri device nodes, and the core structures drm_driver, drm_device and drm_file in Linux v6.19.
October 26, 2026

Linux DRM - 구조와 전체 그림

Reqruies
  • Kernel: Linux v6.19 소스 기준
  • Library: libdrm 2.4.129

Introduction

화면에 무언가를 그리는 일은 리눅스에서 DRM(Direct Rendering Manager)이라는 커널 서브시스템을 거칩니다. 데스크톱의 Wayland나 Xorg, 안드로이드의 SurfaceFlinger, 라즈베리 파이의 콘솔 화면까지 모두 같은 DRM 위에서 동작합니다.
이름이 비슷한 Digital Rights Management(저작권 보호)와는 전혀 다른 이야기이고, 이 글은 리눅스 커널의 그래픽 서브시스템만 다룹니다.
이 글에서는 DRM이 무엇이고 어떤 모양으로 커널에 붙어 있는지를 봅니다. 코드는 다음부터 본격적으로 따라갑니다.

Why DRM Was Needed

DRM이 생기기 전에는 두 가지 방식이 있었습니다.
  • fbdev (/dev/fb0): 화면 메모리 하나를 mmap해서 그냥 픽셀을 쓰는 단순한 방식입니다. 3D 가속이나 여러 화면, 해상도 변경 같은 기능이 없습니다.
  • X 서버가 하드웨어를 직접 제어: X 서버가 root 권한으로 그래픽 카드 레지스터에 접근해서 해상도(모드)도 설정하고 3D 명령도 직접 보냈습니다.
두 번째 방식은 다음과 같은 문제가 있었습니다.
  • 3D 앱이 GPU를 직접 쓰려면 커널이 GPU 접근을 중재해야 하는데, 그 장치가 없었습니다(여러 프로세스가 GPU를 동시에 쓰면 서로 망가뜨립니다).
  • 해상도 설정(mode setting)이 유저 공간에 있어서, 콘솔과 X를 오갈 때 화면이 깜빡이고 부팅 초기 화면을 제어할 수 없었습니다.
  • X 서버가 root여야 해서 보안이 취약했습니다.
DRM은 이 문제를 "GPU와 디스플레이를 커널이 관리하고, 유저 공간은 안전한 인터페이스로만 접근한다"는 방향으로 풀었습니다. 큰 흐름은 대략 다음과 같습니다.
기능하는 일대략의 시기
DRI / DRM앱이 GPU에 직접 명령을 보내되 커널이 중재1990년대 후반 ~ 2000년경
GEMGPU 버퍼(메모리)를 커널이 관리Linux 2.6.28 무렵
KMS해상도/화면 설정을 커널로 이동Linux 2.6.29 무렵
dma-buf (PRIME)버퍼를 프로세스·장치 사이에서 공유Linux 3.3 무렵
render node화면 제어 없이 GPU 계산만 쓰는 전용 노드Linux 3.12 무렵
atomic modeset화면 설정 변경을 한 번에 검증·적용Linux 3.19 (커널 문서 Documentation/gpu/todo.rst)
이 글에서 다루는 구조는 모두 이 역사의 결과입니다. 렌더링(GPU로 그리기)과 디스플레이(화면에 내보내기)를 한 서브시스템이 함께 맡는다는 점을 기억해 두세요.

The Full Stack

Linux 그래픽 스택에서 DRM의 위치
Mermaid
flowchart TB subgraph US["User space"] APP["3D 앱 / 게임<br/>OpenGL · Vulkan"] COMP["디스플레이 서버<br/>Wayland compositor · Xorg"] MESA["Mesa<br/>user-mode GPU driver"] LIBDRM["libdrm"] end subgraph KS["Kernel space"] NR["/dev/dri/renderD128<br/>render node"] NC["/dev/dri/card0<br/>primary node"] CORE["DRM core<br/>drm_drv.c · drm_ioctl.c · drm_gem.c ..."] HELP["DRM helpers<br/>atomic · gem_shmem · gpu scheduler"] DRV["GPU / display driver<br/>i915 · amdgpu · v3d · vkms ..."] end HW["GPU + display controller + 모니터"] APP --> MESA --> LIBDRM COMP --> LIBDRM LIBDRM -->|"ioctl · mmap"| NR LIBDRM -->|"ioctl · read"| NC NR --> CORE NC --> CORE CORE --> HELP --> DRV CORE --> DRV DRV --> HW
  • 앱은 OpenGL/Vulkan으로 그립니다. 이 API를 구현하는 Mesa가 GPU 전용 명령(셰이더, 상태 설정)을 만들어 줍니다. 이 부분을 user-mode driver(UMD)라고 부릅니다.
  • libdrm은 ioctl()을 감싼 얇은 C 라이브러리입니다. 즉 유저 공간이 DRM과 대화하는 방법은 결국 /dev/dri/* 파일을 열고 ioctl, mmap, read 하는 것뿐입니다.
  • 커널 쪽은 공통 코드인 DRM core, 여러 드라이버가 공유하는 helper, 그리고 하드웨어마다 다른 드라이버로 나뉩니다.
그림에서 두 갈래가 보입니다. 3D 앱은 render node로 GPU만 쓰고, 화면 설정은 디스플레이 서버(compositor)가 primary node(card0)로 합니다. 이 분리가 DRM 설계의 핵심입니다.

Two Jobs of DRM

렌더링 (Rendering)디스플레이 (Display)
목적GPU로 이미지를 만든다만든 이미지를 화면에 내보낸다
커널 기능메모리 관리(GEM), 공유(dma-buf), 동기화(fence), 작업 제출(scheduler)KMS (framebuffer, plane, CRTC, encoder, connector)
접근하는 노드render node(renderD128)로도 가능primary node(card0)만 가능

Device Nodes: /dev/dri and sysfs

먼저 실제 PC에서 확인해 보겠습니다(필자 PC: 커널 7.0.0, Intel i915 드라이버).
DRM 장치 노드 확인
shell
  • 주 번호(major)는 226이고 이름이 drm입니다. 이것은 커널이 부팅 중에 등록한 값입니다(아래 코드에서 확인합니다).
  • card0은 부 번호(minor) 0, renderD128은 128입니다. 부 번호 범위가 노드 종류마다 다르다는 뜻입니다.
  • card0-DP-1, card0-HDMI-A-1처럼 connector도 sysfs에 나타납니다.
이 규칙은 커널의 drm_drv.c에 있습니다.
drivers/gpu/drm/drm_drv.c - #define DRM_MINOR_LIMIT (L136-139)
c
include/drm/drm_file.h - enum drm_minor_type (L61-66)
c
소스 include/drm/drm_file.h L61-L66 (Linux v6.19)
  • primary(DRM_MINOR_PRIMARY = 0)는 부 번호 0~63, render(DRM_MINOR_RENDER = 2)는 64 * 2 = 128부터 시작합니다. 위 ls 결과의 226, 128이 정확히 이 계산입니다.
  • 이름은 drm_sysfs_minor_alloc()에서 정해집니다.
drivers/gpu/drm/drm_sysfs.c - drm_sysfs_minor_alloc() (L551-591)
c
"renderD%d", "card%d" 이름과 MKDEV(DRM_MAJOR, minor->index)가 보입니다. 이 struct device가 나중에 device_add()로 등록되면 udev와 devtmpfs가 /dev/dri/card0, /dev/dri/renderD128을 만들어 줍니다.

How card0 Differs from renderD128

primary node(card0)는 모든 기능(KMS 포함)을 쓸 수 있고, 화면 제어 권한인 DRM master를 가질 수 있습니다. render node(renderD128)는 화면 설정이 불가능한 대신 권한 문제가 없습니다. 누구든 파일 권한만 있으면 열어서 GPU 계산을 할 수 있습니다.
이 차이는 ioctl 하나하나에 플래그로 새겨져 있고, drm_ioctl_permit()이 강제합니다.
include/drm/drm_ioctl.h - enum drm_ioctl_flags (L80-122)
c
drivers/gpu/drm/drm_ioctl.c - drm_ioctl_permit() (L599-621)
c
읽어 보면 규칙이 명확합니다.
  • DRM_MASTER가 붙은 ioctl은 현재 master만 호출할 수 있습니다. 대표적으로 DRM_IOCTL_MODE_ATOMIC이 그렇습니다.
  • render node(drm_is_render_client)는 DRM_RENDER_ALLOW가 명시된 ioctl만 쓸 수 있습니다. 그래서 DRM_IOCTL_VERSION, DRM_IOCTL_PRIME_HANDLE_TO_FD는 render node에서 되지만, DRM_IOCTL_MODE_CREATE_DUMB는 flags가 0이라 render node에서 거부됩니다.
그럼 master는 누가 될까요? 첫 번째로 열린 primary 파일이 자동으로 master가 됩니다.
drivers/gpu/drm/drm_auth.c - drm_master_open() (L317-335)
c
그래서 데스크톱 환경이 이미 card0을 열어 master를 쥐고 있으면, 다른 프로그램은 같은 card0에서 화면 설정(atomic commit)을 하려다 -EACCES를 받게 됩니다.

Struct Relationships Inside DRM Core

DRM의 핵심 구조체는 세 가지입니다. drm_driver(드라이버가 정의하는 상수 템플릿), drm_device(장치 하나), drm_file(open() 하나)입니다.
drm_driver · drm_device · drm_minor · drm_file 의 관계
Mermaid
flowchart TB DRIVER["struct drm_driver<br/>드라이버가 정의하는 const 템플릿<br/>name · driver_features · fops · ioctls · dumb_create ..."] DEV["struct drm_device<br/>물리 장치 1개의 인스턴스<br/>filelist · mode_config · vblank ..."] PRI["drm_minor : primary<br/>card0 · 226:0"] REN["drm_minor : render<br/>renderD128 · 226:128"] F1["drm_file<br/>프로세스 A의 open()"] F2["drm_file<br/>프로세스 B의 open()"] PF["open마다 따로 가지는 것<br/>object_idr : GEM handle 표 · fbs : 만든 framebuffer<br/>event_list : 이벤트 큐 · master · atomic 설정"] DRIVER -->|"dev->driver"| DEV DEV --> PRI DEV --> REN PRI --> F1 REN --> F2 F1 --- PF

drm_driver: The Template Drivers Fill In

드라이버는 struct drm_driver 하나를 const로 정의합니다. 이 안에 기능 플래그, 파일 연산, 드라이버 전용 ioctl 표, 몇 가지 콜백이 들어 있습니다.
include/drm/drm_drv.h - struct drm_driver (L181-434)
c
소스 include/drm/drm_drv.h L181-L434 (Linux v6.19)
  • driver_features: DRIVER_GEM, DRIVER_MODESET, DRIVER_RENDER, DRIVER_ATOMIC 같은 비트 플래그입니다. DRM core는 이 값을 보고 어떤 기능을 켤지 정합니다(예: DRIVER_RENDER가 있어야 render node를 만듭니다).
  • fops: /dev/dri/cardN에 대한 file_operations입니다. 대부분의 드라이버가 매크로 하나를 씁니다(아래에서 확인합니다).
  • ioctls, num_ioctls: 드라이버만의 ioctl 표입니다. 번호 범위는 0x40~0xA0입니다(DRM_COMMAND_BASE, DRM_COMMAND_END).
  • dumb_create, gem_prime_import_sg_table 등: 코어가 부르는 드라이버 콜백입니다. 코어는 공통 일을 하고, 하드웨어에 따라 다른 부분은 이 콜백에 위임합니다. 이 패턴이 DRM 전체를 관통합니다.
include/drm/drm_drv.h - enum drm_driver_feature (L57-170)
c
소스 include/drm/drm_drv.h L57-L170 (Linux v6.19)

drm_file: One per open()

/dev/dri/card0를 open()할 때마다 struct drm_file이 하나 생깁니다. 프로세스(또는 fd)별 상태가 전부 여기에 있습니다.
include/drm/drm_file.h - struct drm_file (L164-420)
c
  • object_idr: 유저에게 보이는 GEM handle을 커널 객체로 바꾸는 표입니다. handle이 fd(drm_file)마다 따로라는 뜻입니다.
  • event_list, pending_event_list: page flip 완료 같은 이벤트를 쌓아 두는 큐입니다. 유저는 이 fd를 read()해서 꺼냅니다.
  • atomic, universal_planes: drmSetClientCap()으로 켜는 클라이언트 기능입니다. atomic ioctl은 이게 켜져 있어야 동작합니다.
  • master, is_master: 이 파일이 화면 제어 권한을 가졌는지 나타냅니다.

Source Tree Map

DRM 코드를 읽을 때 가장 먼저 필요한 것은 "어디에 무엇이 있는가"입니다.
위치내용
include/uapi/drm/유저 공간과 커널이 공유하는 ABI (drm.h, drm_mode.h, 드라이버별 *_drm.h)
include/drm/커널 내부 헤더 (drm_device.h, drm_drv.h, drm_gem.h ...)
drivers/gpu/drm/drm_drv.c, drm_file.c, drm_ioctl.c장치/파일/ioctl 등 코어의 뼈대
drivers/gpu/drm/drm_gem*.c, drm_prime.c, drm_syncobj.c버퍼 관리와 공유, 동기화
drivers/gpu/drm/drm_atomic*.c, drm_crtc.c, drm_plane.c, drm_vblank.cKMS와 atomic
drivers/gpu/drm/scheduler/GPU 작업 스케줄러
drivers/dma-buf/dma-buf, dma-fence
drivers/gpu/drm/<드라이버>/i915, amdgpu, v3d, vkms 등 개별 드라이버
Documentation/gpu/공식 문서 (drm-kms.rst, drm-mm.rst, drm-uapi.rst, drm-internals.rst)
이 시리즈에서는 드라이버 구조를 보여 줄 때 vkms(Virtual Kernel Mode Setting)를 주로 씁니다. 하드웨어가 없는 가상 드라이버라서 코드가 작고(drivers/gpu/drm/vkms/, 약 6천 줄), DRM이 요구하는 최소 골격이 그대로 드러나기 때문입니다.

Code Locations

단계함수위치 (Linux v6.19)
구조체: 드라이버 템플릿struct drm_driverinclude/drm/drm_drv.h
구조체: open() 하나의 상태struct drm_fileinclude/drm/drm_file.h

Summary

이 Post 에서 확인한 것은 다음과 같습니다.
  • DRM은 렌더링(GPU)과 디스플레이(KMS)를 커널이 중재하는 서브시스템이고, 유저 공간은 /dev/dri/*의 ioctl, mmap, read로만 접근합니다.
  • 장치 노드는 주 번호 226에 primary(card)와 render(renderD)가 있고, 권한 차이는 ioctl 플래그(DRM_MASTER, DRM_RENDER_ALLOW)로 강제됩니다.
  • 구조는 drm_driver(템플릿) → drm_device(장치) → drm_minor(노드) → drm_file(open)이며, 코어가 공통 일을 하고 하드웨어 전용 부분은 드라이버 콜백에 위임합니다.
다음에는 이 구조가 실제로 만들어지는 과정, 즉 코어 초기화(226 등록)와 드라이버 등록(drm_dev_register)을 vkms 드라이버의 코드로 따라가 보겠습니다.
Jooojub
System S/W engineer
Explore Tags
Series
    Recent Post
    © 2026. jooojub. All right reserved.