GPU 서브시스템 (DRM/KMS)
Linux GPU 서브시스템(DRM/KMS)을 디스플레이 파이프라인과 연산 가속 경로를 함께 보는 관점에서 심층 분석합니다. DRM core와 KMS 객체 모델(CRTC/plane/encoder/connector), GEM/TTM 메모리 관리자, DMA-BUF 기반 장치 간 버퍼 공유, atomic modesetting과 vblank 동기화, fence 기반 GPU 작업 순서 제어, 전원·클럭·열 관리, userspace(Mesa/Wayland)와의 ioctl 계약, debugfs/tracepoint로 프레임 드롭과 hang을 진단하는 방법까지 GPU 드라이버 개발 핵심을 다룹니다.
핵심 요약
- 노드 분리 — 화면 제어는 primary node, 비특권 렌더링은 render node, 비그래픽 연산은 accel node가 담당합니다.
- 상태 모델 — KMS는 CRTC/plane/connector를 atomic state로 묶어 한 번에 검증하고 적용합니다.
- 버퍼 수명주기 — BO 생성, 핸들 배정, mmap, DMA-BUF 공유, fence 동기화가 한 세트로 움직입니다.
- 메모리 계층 — 단순 SoC GPU는 GEM shmem, 전용 VRAM GPU는 TTM/VRAM helper/자체 GPUVM을 주로 사용합니다.
- 복구 설계 — hang 감지, fence 타임아웃, 엔진 리셋, 전원 재기동이 운영 안정성을 좌우합니다.
단계별 이해
- 어느 노드를 여는지부터 구분
compositor는 primary node에서 KMS를 제어하고, 일반 앱은 render node에서 커맨드 제출을 시작합니다. - 버퍼를 어떤 백엔드에 둘지 결정
scanout 전용이면 dumb buffer, 일반 렌더링이면 드라이버 전용 BO, 장치 간 공유면 DMA-BUF까지 함께 봅니다. - atomic 상태를 조립
plane/CRTC/connector 속성을 새 상태 객체에 채운 뒤atomic_check로 하드웨어 제약을 검증합니다. - 렌더링과 표시를 동기화
dma_resv,syncobj,IN_FENCE_FD/OUT_FENCE_PTR로 렌더 완료 시점을 맞춥니다. - 운영 중 고장 경로를 준비
GPU hang, hotplug, runtime suspend, reset recovery를 debugfs·tracepoint·drm_sched 타임아웃으로 추적합니다.
DRM (Direct Rendering Manager) 개요
DRM은 Linux 커널의 GPU 접근을 관리하는 서브시스템입니다. 원래 3D 그래픽 가속을 위해 도입되었으나, 현재는 디스플레이 출력(KMS), GPU 메모리 관리(GEM/TTM), GPU 작업 스케줄링까지 포괄하는 핵심 프레임워크로 발전했습니다.
| 구성 요소 | 역할 | 커널 코드 |
|---|---|---|
| DRM Core | 드라이버 등록, ioctl 디스패치, 파일 오퍼레이션 | drivers/gpu/drm/drm_*.c |
| KMS (Kernel Mode Setting) | 디스플레이 모드 설정, CRTC/Encoder/Connector/Plane | drivers/gpu/drm/drm_atomic*.c |
| GEM (Graphics Execution Manager) | GPU 메모리 버퍼 할당/관리, mmap | drivers/gpu/drm/drm_gem*.c |
| TTM (Translation Table Manager) | VRAM/시스템 메모리 간 버퍼 이동, 페이징 | drivers/gpu/drm/ttm/ |
| DMA-BUF | 디바이스 간 버퍼 공유 (GPU↔카메라↔디스플레이) | drivers/dma-buf/ |
| GPU Scheduler | GPU 작업 큐 관리, 우선순위, 타임아웃 처리 | drivers/gpu/drm/scheduler/ |
drivers/video/fbdev/)는 단순 프레임버퍼만 제공하는 레거시 인터페이스입니다.
DRM/KMS는 하드웨어 가속, 다중 디스플레이, vsync, overlay plane 등을 지원하며, fbdev는 DRM으로 대체되는 추세입니다.
메인라인 그래픽 드라이버 개발에서는 DRM/KMS 사용이 권장되며 사실상 표준 경로입니다.
디바이스 노드와 권한 모델
현대 DRM UAPI는 GPU 하나를 단일 문자 디바이스로만 노출하지 않습니다.
같은 하드웨어라도 화면 제어, 비특권 렌더링, 비그래픽 연산을 서로 다른 노드로 나누어
권한 경계와 사용자 공간 스택을 분리합니다. 이 구분을 이해하지 못하면 drm_file의 인증 상태, client capability,
버퍼 공유 규칙, compositor와 compute runtime의 역할이 한꺼번에 흐려집니다.
| 노드 종류 | 대표 경로 | 주 용도 | 권한/제약 |
|---|---|---|---|
| Primary | /dev/dri/card0 |
KMS modeset, connector/plane/lease 제어, 레거시 ioctl | DRM master 개념이 존재합니다. 보통 compositor, Xorg, display manager가 소유합니다. |
| Control | /dev/dri/controlD64 |
과거 KMS 제어용으로 설계됨 | 메인라인 문서 기준으로 현재는 사실상 미사용입니다. |
| Render | /dev/dri/renderD128 |
OpenGL/Vulkan/VA-API/OpenCL 등 비특권 렌더링과 GPGPU | modeset 및 privileged ioctl 불가. DRM master 없이 열 수 있으며 PRIME 기반 공유가 중심입니다. |
| Accel | /dev/accel/accel0 |
NPU/AI/신호 처리 같은 비그래픽 compute | DRIVER_COMPUTE_ACCEL 전용 네임스페이스입니다. graphics 스택과 분리된 별도 major를 사용합니다. |
/* 유저 공간은 node 종류와 client capability를 먼저 협상한다 */
int fd = open("/dev/dri/card0", O_RDWR | O_CLOEXEC);
drmSetClientCap(fd, DRM_CLIENT_CAP_UNIVERSAL_PLANES, 1);
drmSetClientCap(fd, DRM_CLIENT_CAP_ATOMIC, 1);
drmSetClientCap(fd, DRM_CLIENT_CAP_WRITEBACK_CONNECTORS, 1);
/* para-virtual GPU에서는 커서 plane hotspot capability가 추가로 필요할 수 있다 */
drmSetClientCap(fd, DRM_CLIENT_CAP_CURSOR_PLANE_HOTSPOT, 1);
drm_file.atomic과 drm_file.universal_planes가 함께 의미를 가지며,
writeback connector는 atomic 지원 위에 추가로 opt-in 해야 노출됩니다.
DRM 아키텍처
유저 공간 그래픽 스택
DRM/KMS 위에서 동작하는 유저 공간 소프트웨어 스택의 구조입니다. 각 계층이 DRM을 어떻게 활용하는지 이해하면 전체 그래픽 파이프라인을 파악할 수 있습니다.
| 계층 | 구성 요소 | DRM 사용 방식 |
|---|---|---|
| 애플리케이션 | 게임, 브라우저, 3D 뷰어 | OpenGL/Vulkan API 호출 (직접 DRM 접근 안 함) |
| 윈도우 시스템 | Wayland compositor (Mutter, KWin, Weston) | Primary node에서 KMS atomic commit 수행. 렌더링은 GBM + render node 또는 드라이버 전용 UAPI를 함께 사용 |
| GL/VK 드라이버 | Mesa (radeonsi, iris, panfrost, radv, anv) | Render node로 GPU 커맨드 제출. 드라이버 고유 ioctl 사용 |
| libdrm | DRM ioctl 래퍼 라이브러리 | DRM ioctl을 C 함수로 래핑 (drmModeAtomicCommit() 등) |
| GBM (Generic Buffer Manager) | Mesa 제공, compositor용 버퍼 할당 | gbm_bo_create()가 드라이버 backend를 통해 scanout/texture/render target용 BO를 생성. dumb buffer는 이보다 훨씬 제한적인 최소 경로 |
| EGL / WSI | GL 컨텍스트 관리 / Vulkan Window System Integration | DMA-BUF로 렌더 결과를 compositor에 전달 |
- Intel: GL=
iris(Gen8+)/crocus(Gen4~7), Vulkan=anv - AMD: GL=
radeonsi(GCN+), Vulkan=radv - ARM Mali: GL=
panfrost(Midgard/Bifrost)/lima(Utgard), Vulkan=panvk - Qualcomm: GL/Vulkan=
freedreno/turnip - Broadcom: GL=
v3d(Pi4/5)/vc4(Pi0~3), Vulkan=v3dv
DRM 핵심 구조체
drm_device & drm_driver
drm_device는 하나의 GPU 디바이스를 나타내고, drm_driver는 해당 GPU 유형의 오퍼레이션을 정의합니다.
#include <drm/drm_drv.h>
#include <drm/drm_gem.h>
#include <drm/drm_ioctl.h>
/* DRM 드라이버 정의 — 드라이버 전체에 하나 */
static const struct drm_driver my_gpu_driver = {
.driver_features = DRIVER_GEM | DRIVER_MODESET | DRIVER_ATOMIC |
DRIVER_RENDER,
/* GEM 오퍼레이션 */
.gem_prime_import = drm_gem_prime_import,
.gem_prime_import_sg_table = drm_gem_shmem_prime_import_sg_table,
/* DRM ioctl 테이블 (드라이버 고유 ioctl) */
.ioctls = my_gpu_ioctls,
.num_ioctls = ARRAY_SIZE(my_gpu_ioctls),
/* /sys, /dev 노드 이름 */
.name = "my-gpu",
.desc = "My GPU DRM driver",
.date = "20260101",
.major = 1,
.minor = 0,
.fops = &my_gpu_fops,
};
drm_file (파일 프라이빗)
유저 프로세스가 DRM/accel 노드를 open하면 drm_file 구조체가 생성됩니다.
이 구조체는 “프로세스별 GPU 컨텍스트”라기보다 파일 디스크립터별 세션 상태에 가깝고,
인증 여부, master 여부, client capability, 핸들 테이블, 이벤트 큐를 함께 들고 있습니다.
/* drm_file 핵심 필드 발췌 (include/drm/drm_file.h) */
struct drm_file {
bool authenticated; /* 레거시 primary 노드 렌더 권한 */
bool universal_planes;
bool atomic;
bool writeback_connectors;
bool is_master;
bool supports_virtualized_cursor_plane;
struct drm_minor *minor; /* primary/render/accel 중 어떤 노드를 열었는지 */
struct idr object_idr; /* GEM 핸들 → drm_gem_object 매핑 */
struct idr syncobj_idr; /* drm_syncobj 핸들 테이블 */
struct list_head pending_event_list;
struct list_head event_list; /* vblank, page flip, out-fence 이벤트 */
void *driver_priv; /* 드라이버별 프라이빗 데이터 */
};
authenticated 해석 주의:
- primary node에서는 master/authentication이 여전히 의미가 있습니다. KMS 관련 많은 ioctl은 현재 master를 전제로 합니다.
- render node는 modeset/global ioctl을 막고 DRM master 개념도 버립니다. 파일 권한만 맞으면 즉시 GPU 접근이 가능합니다.
- accel node는 graphics stack과 구분된 compute 전용 경로이므로, display 관련 상태를 노출하지 않는 것이 설계 목표입니다.
DRIVER_RENDER를 설정한 드라이버만 render node를 만들고,
DRIVER_COMPUTE_ACCEL는 별도 accel 네임스페이스를 사용합니다.
KMS (Kernel Mode Setting)
KMS는 커널에서 디스플레이 모드를 설정하는 프레임워크입니다. 기존의 유저 공간 모드 설정(UMS)과 달리, 커널이 직접 해상도/주사율/디스플레이 파이프라인을 제어하여 콘솔↔그래픽 전환 시 깜박임이 없고 다중 모니터를 안정적으로 관리합니다.
KMS 핵심 오브젝트
| KMS 오브젝트 | 구조체 | 주요 콜백 | 역할 |
|---|---|---|---|
| CRTC | drm_crtc |
atomic_check, atomic_flush, atomic_enable |
스캔아웃 엔진. 해상도/주사율 타이밍 생성, Plane 합성 |
| Encoder | drm_encoder |
atomic_check, atomic_enable, atomic_disable |
내부 디지털 신호를 외부 프로토콜(HDMI/DP)로 변환 |
| Connector | drm_connector |
detect, get_modes, atomic_check |
물리적 출력 포트. EDID 파싱, 핫플러그 감지 |
| Plane | drm_plane |
atomic_check, atomic_update, atomic_disable |
프레임버퍼를 화면에 배치. 스케일링, 회전, 블렌딩 |
| Framebuffer | drm_framebuffer |
create_handle, destroy |
GEM 객체 래퍼. 픽셀 포맷, stride, modifier 정보 |
Vblank 처리와 페이지 플립
Vblank(수직 귀선 기간)은 디스플레이가 한 프레임을 마치고 다음 프레임을 시작하기 직전의 시간 구간입니다.
이 시점에 프레임버퍼를 교체(page flip)해야 화면 찢어짐(tearing)이 발생하지 않습니다.
DRM 코어는 drm_vblank 인프라로 정확한 vblank 타이밍과 시퀀스 번호를 관리합니다.
/* Vblank 관련 드라이버 콜백 */
static const struct drm_crtc_funcs my_crtc_funcs = {
/* vblank 카운터/타임스탬프 제공 */
.get_vblank_counter = drm_crtc_vblank_count,
.enable_vblank = my_enable_vblank, /* vblank IRQ 활성화 */
.disable_vblank = my_disable_vblank, /* vblank IRQ 비활성화 */
.get_vblank_timestamp = drm_crtc_vblank_helper_get_vblank_timestamp,
};
/* 드라이버 vblank IRQ 핸들러 */
static irqreturn_t my_irq_handler(int irq, void *data)
{
struct drm_device *dev = data;
u32 status = readl(regs + IRQ_STATUS);
if (status & VBLANK_IRQ) {
drm_crtc_handle_vblank(&my_crtc); /* vblank 카운터 증가 + 이벤트 전달 */
}
return IRQ_HANDLED;
}
/* 페이지 플립 완료 알림 (vblank IRQ에서 호출) */
if (page_flip_pending) {
drm_crtc_send_vblank_event(&my_crtc, event);
drm_crtc_vblank_put(&my_crtc);
page_flip_pending = false;
}
| 함수 | 역할 | 호출 위치 |
|---|---|---|
drm_crtc_vblank_on() | CRTC 활성화 시 vblank 추적 시작 | CRTC enable 콜백 |
drm_crtc_vblank_off() | CRTC 비활성화 시 vblank 추적 중단 | CRTC disable 콜백 |
drm_crtc_vblank_get() | vblank IRQ 참조 획득 (카운팅) | 페이지 플립 요청 시 |
drm_crtc_vblank_put() | vblank IRQ 참조 해제 | 플립 완료 후 |
drm_crtc_handle_vblank() | vblank 카운터 증가, 대기 중인 이벤트 처리 | vblank IRQ 핸들러 |
drm_crtc_send_vblank_event() | 유저에게 페이지 플립 완료 이벤트 전달 | 플립 완료 시점 |
drm_crtc_vblank_helper_get_vblank_timestamp()는
하드웨어 스캔라인 카운터를 읽어 vblank의 정확한 시각을 계산합니다.
이 타임스탬프는 DRM_EVENT_FLIP_COMPLETE 이벤트에 포함되어 유저 공간(Wayland compositor 등)이
프레임 타이밍을 정밀하게 제어하는 데 사용됩니다. 고정밀 프레임 스케줄링의 핵심 인프라입니다.
Atomic Modesetting
Atomic modesetting은 KMS의 현대적 커밋 모델입니다. 여러 디스플레이 속성 변경을 하나의 원자적 트랜잭션으로 묶어 중간 상태 없이 적용하거나 전체를 롤백합니다.
Atomic 상태 객체 그래프
실제 atomic UAPI의 핵심은 “property를 바로 레지스터에 쓰는 것”이 아니라,
drm_atomic_state 아래에 CRTC/plane/connector/private object의
새 상태를 모아 둔 뒤 한 번에 검증하고 커밋하는 것입니다.
driver의 atomic_check는 이 그래프 전체를 보고 대역폭, scaler 수, watermark, 링크 훈련 가능 여부,
color LUT 크기, writeback 출력 버퍼 충돌까지 판단합니다.
| 상태 객체 | 대표 구조체 | 주요 내용 |
|---|---|---|
| 트랜잭션 컨테이너 | drm_atomic_state |
이번 커밋에서 바뀌는 모든 object의 old/new state 포인터와 acquire context를 묶습니다. |
| CRTC 상태 | drm_crtc_state |
mode, active 여부, color pipeline, vrr, out-fence, bandwidth 결과를 담습니다. |
| Plane 상태 | drm_plane_state |
framebuffer, source/destination rectangle, rotation, zpos, in-fence를 담습니다. |
| Connector 상태 | drm_connector_state |
연결된 CRTC, link status, colorspace, writeback job, content protection 상태를 담습니다. |
| 드라이버 private 상태 | drm_private_obj 계열 |
watermark, shared DPLL, DSC slice allocator처럼 여러 오브젝트가 공유하는 HW 자원을 모델링합니다. |
/* Atomic 커밋 흐름 (커널 내부) */
/* 1. 유저가 DRM_IOCTL_MODE_ATOMIC 호출 */
/* → drm_mode_atomic_ioctl() 진입 */
/* 2. 상태 복제 */
struct drm_atomic_state *state = drm_atomic_state_alloc(dev);
/* 각 오브젝트의 현재 상태를 복제하여 new_state 생성 */
/* 3. 속성 적용 (유저가 요청한 변경사항) */
drm_atomic_set_crtc_for_connector(new_conn_state, crtc);
new_crtc_state->mode_blob = mode;
new_plane_state->fb = framebuffer;
new_plane_state->crtc = crtc;
/* 4. 검증 (atomic_check) — 하드웨어 제약 확인 */
ret = drm_atomic_check_only(state);
/* 각 CRTC/Plane/Connector의 atomic_check 콜백 호출 */
/* 실패 시 -EINVAL 반환, 유저에게 거부 통보 */
/* 5. TEST_ONLY 플래그면 여기서 종료 (검증만 수행) */
if (flags & DRM_MODE_ATOMIC_TEST_ONLY)
return ret;
/* 6. 커밋 (atomic_commit) — 실제 하드웨어 적용 */
ret = drm_atomic_commit(state);
/* NONBLOCK 플래그: 비동기 커밋 (vsync 대기 안 함) */
/* PAGE_FLIP_EVENT: 완료 시 유저에게 이벤트 전달 */
DRM_MODE_ATOMIC_TEST_ONLY— 검증만 수행, 실제 적용 안 함. 유저 공간에서 구성 유효성을 미리 확인DRM_MODE_ATOMIC_NONBLOCK— 비동기 커밋. vsync을 기다리지 않고 즉시 반환DRM_MODE_PAGE_FLIP_EVENT— 플립 완료 시DRM_EVENT_FLIP_COMPLETE이벤트를 유저에게 전달DRM_MODE_ATOMIC_ALLOW_MODESET— 해상도/주사율 변경이 포함된 full modeset 허용
DRM Properties 시스템
KMS 오브젝트(CRTC, Plane, Connector)의 모든 설정 가능한 속성은 DRM Property로 표현됩니다. Atomic modesetting에서는 property ID와 값을 쌍으로 전달하여 디스플레이 상태를 변경합니다.
Property 타입
| 타입 | 커널 생성 함수 | 값 형태 | 예시 |
|---|---|---|---|
| Range | drm_property_create_range() |
min~max 정수 | alpha (0~0xFFFF), rotation angle |
| Enum | drm_property_create_enum() |
이름↔값 열거 | DPMS (On/Standby/Suspend/Off) |
| Bitmask | drm_property_create_bitmask() |
비트 조합 | rotation (ROTATE_0 | REFLECT_X) |
| Blob | drm_property_create_blob() |
임의 바이너리 데이터 | MODE_ID (drm_mode_modeinfo), EDID |
| Object | drm_property_create_object() |
다른 KMS 오브젝트 ID | CRTC_ID, FB_ID, IN_FENCE_FD |
| Signed Range | drm_property_create_signed_range() |
부호 있는 정수 | SRC_X, SRC_Y (16.16 고정소수점) |
표준 KMS Property
/* === CRTC Properties === */
"ACTIVE" /* bool: CRTC 활성화 (atomic에서 DPMS 대체) */
"MODE_ID" /* blob: 디스플레이 모드 (해상도/주사율) */
"OUT_FENCE_PTR" /* ptr: 커밋 완료 fence fd 반환 위치 */
"VRR_ENABLED" /* bool: 가변 주사율 활성화 */
"DEGAMMA_LUT" /* blob: 디감마 LUT (Color Management) */
"CTM" /* blob: 색 변환 매트릭스 (3x3, S31.32 고정소수) */
"GAMMA_LUT" /* blob: 감마 LUT */
/* === Plane Properties === */
"FB_ID" /* object: 표시할 프레임버퍼 */
"CRTC_ID" /* object: 연결할 CRTC */
"SRC_X/Y/W/H" /* range: 소스 영역 (16.16 고정소수점) */
"CRTC_X/Y/W/H" /* range: 화면 위 위치/크기 (스케일링) */
"rotation" /* bitmask: ROTATE_0/90/180/270 | REFLECT_X/Y */
"alpha" /* range: 투명도 (0=투명, 0xFFFF=불투명) */
"pixel blend mode" /* enum: None/Pre-multiplied/Coverage */
"zpos" /* range: 레이어 순서 (높을수록 위) */
"IN_FENCE_FD" /* range: explicit sync 입력 fence fd */
"COLOR_ENCODING" /* enum: YCbCr BT.601/709/2020 */
"COLOR_RANGE" /* enum: YCbCr Limited/Full Range */
/* === Connector Properties === */
"CRTC_ID" /* object: 연결할 CRTC */
"DPMS" /* enum: On/Standby/Suspend/Off (레거시) */
"link-status" /* enum: Good/Bad — 링크 학습 실패 시 Bad */
"EDID" /* blob: 모니터 EDID 바이너리 (읽기 전용) */
"content type" /* enum: No Data/Graphics/Photo/Cinema/Game */
"max bpc" /* range: 최대 색 깊이 (bits per component) */
"HDR_OUTPUT_METADATA" /* blob: HDR 정적/동적 메타데이터 */
"vrr_capable" /* range: 모니터 VRR 지원 여부 (읽기 전용) */
/* 드라이버에서 커스텀 property 등록 */
struct drm_property *prop;
/* Enum property 생성 */
static const struct drm_prop_enum_list my_scaling_modes[] = {
{ 0, "None" },
{ 1, "Full" },
{ 2, "Center" },
{ 3, "Full aspect" },
};
prop = drm_property_create_enum(dev, 0, "scaling mode",
my_scaling_modes, ARRAY_SIZE(my_scaling_modes));
/* CRTC에 property 연결 */
drm_object_attach_property(&crtc->base, prop, 0);
/* atomic_check에서 property 값 읽기 */
static int my_crtc_atomic_check(struct drm_crtc *crtc,
struct drm_atomic_state *state)
{
struct drm_crtc_state *crtc_state =
drm_atomic_get_new_crtc_state(state, crtc);
/* crtc_state에서 property 값에 접근 */
...
}
modetest -M <driver> -p로 모든 KMS 오브젝트의 property와 현재 값을 확인할 수 있습니다.
유저 공간에서는 DRM_IOCTL_MODE_GETPROPERTY로 property 메타데이터를, DRM_IOCTL_MODE_OBJ_GETPROPERTIES로 오브젝트별 값을 조회합니다.
Color Management 파이프라인
KMS는 CRTC 레벨에서 3단계 색상 처리 파이프라인을 제공합니다. 이 파이프라인으로 HDR 톤매핑, 색 공간 변환, 감마 보정 등을 하드웨어 가속으로 수행합니다.
/* Color Management 관련 구조체 */
/* 감마/디감마 LUT 엔트리 (유저 공간에서 설정) */
struct drm_color_lut {
__u16 red; /* 0~0xFFFF */
__u16 green;
__u16 blue;
__u16 reserved;
};
/* 색 변환 매트릭스 (CTM) — S31.32 고정소수점 */
struct drm_color_ctm {
__u64 matrix[9]; /* 3x3, 부호 비트 + 31.32 고정소수 */
};
/* 유저 공간에서 감마 LUT 설정 예시 (libdrm) */
struct drm_color_lut lut[256];
for (int i = 0; i < 256; i++) {
/* sRGB 감마 커브: 선형 → sRGB 비선형 */
double v = (double)i / 255.0;
double srgb = (v <= 0.0031308) ?
v * 12.92 : 1.055 * pow(v, 1.0/2.4) - 0.055;
__u16 val = (__u16)(srgb * 0xFFFF);
lut[i].red = lut[i].green = lut[i].blue = val;
}
/* blob property로 설정 */
drmModeCreatePropertyBlob(fd, lut, sizeof(lut), &blob_id);
/* atomic 커밋에 GAMMA_LUT=blob_id 추가 */
HDR_OUTPUT_METADATA connector property로 SMPTE ST 2086 마스터링 디스플레이 정보와
MaxCLL/MaxFALL 값을 설정합니다. PQ(Perceptual Quantizer) EOTF는 Degamma LUT로 처리하며,
BT.2020 색 공간 변환은 CTM으로 수행합니다. 하드웨어가 충분한 LUT 정밀도(최소 1024 엔트리)를 제공해야 정확한 HDR 재현이 가능합니다.
VRR (Variable Refresh Rate) / Adaptive Sync
VRR은 GPU 렌더링 속도에 맞춰 디스플레이 주사율을 동적으로 조절하는 기술입니다. 화면 찢어짐(tearing)과 스터터링(stuttering) 없이 부드러운 프레임 전달을 가능하게 합니다.
| 기술 | 표준 | DRM 지원 | 설명 |
|---|---|---|---|
| Adaptive-Sync | VESA DP Adaptive-Sync | DisplayPort connector | DP 표준의 VRR. FreeSync 모니터가 주로 사용 |
| HDMI VRR | HDMI 2.1 VRR | HDMI connector | HDMI Forum VRR. QFT (Quick Frame Transport) 포함 |
| Panel Self Refresh | eDP PSR/PSR2 | eDP connector | 정적 화면에서 패널이 자체 리프레시 → GPU 절전 |
/* VRR 활성화 흐름 (커널 내부) */
/* 1. Connector가 VRR 지원하는지 확인 */
/* 모니터 EDID의 Adaptive-Sync range 파싱 */
connector->vrr_capable = true; /* 드라이버가 EDID 기반으로 설정 */
/* 2. 유저 공간에서 CRTC의 VRR_ENABLED property 설정 */
/* → atomic commit에 포함 */
new_crtc_state->vrr_enabled = true;
/* 3. 드라이버 atomic_check에서 VRR 설정 검증 */
static int my_crtc_atomic_check(struct drm_crtc *crtc,
struct drm_atomic_state *state)
{
struct drm_crtc_state *crtc_state =
drm_atomic_get_new_crtc_state(state, crtc);
if (crtc_state->vrr_enabled) {
/* VRR 범위 확인 (min_vfreq ~ max_vfreq) */
struct drm_connector *conn = get_connector(crtc_state);
if (!conn->display_info.monitor_range.max_vfreq)
return -EINVAL;
/* 하드웨어 VRR 타이밍 설정 */
my_state->vmin = conn->display_info.monitor_range.min_vfreq;
my_state->vmax = conn->display_info.monitor_range.max_vfreq;
}
return 0;
}
/* 4. 페이지 플립 시 VRR 타이밍 적용 */
/* GPU가 프레임을 완료하면 즉시 vsync 발생 */
/* → 가변 vblank 주기 */
modetest -M amdgpu -p에서vrr_capableproperty 확인cat /sys/class/drm/card0-DP-1/vrr_capable— sysfs에서 직접 조회- Wayland에서는
wp_tearing_control_v1프로토콜로 VRR/tearing 제어 - X11에서는
xrandr --prop으로 VRR 속성 확인
Format Modifier (타일링/압축)
GPU는 메모리 접근 효율을 위해 픽셀 데이터를 단순 행(linear) 방식이 아닌 타일(tile) 형태로 배치합니다.
Format Modifier는 프레임버퍼의 메모리 레이아웃을 기술하는 64비트 토큰입니다.
/* Format Modifier 구조 (include/uapi/drm/drm_fourcc.h) */
/*
* [63:56] = vendor (DRM_FORMAT_MOD_VENDOR_*)
* [55:0] = vendor별 modifier 값
*
* 0 = DRM_FORMAT_MOD_LINEAR (행 순차 배치, 모든 디바이스 공통)
*/
#define DRM_FORMAT_MOD_LINEAR 0
/* Intel 타일링 modifier 예시 */
#define I915_FORMAT_MOD_X_TILED /* X-tiling (레거시) */
#define I915_FORMAT_MOD_Y_TILED /* Y-tiling (Gen9+) */
#define I915_FORMAT_MOD_4_TILED /* Tile-4 (Gen12.5+, DG2/MTL) */
#define I915_FORMAT_MOD_Y_TILED_CCS /* Y-tiling + CCS 압축 */
/* AMD modifier 예시 */
#define AMD_FMT_MOD /* 타일 버전, pipe_xor_bits, DCC 등 인코딩 */
/* ARM AFBC (Arm Frame Buffer Compression) */
#define DRM_FORMAT_MOD_ARM_AFBC /* Mali GPU 프레임버퍼 무손실 압축 */
/* Broadcom */
#define DRM_FORMAT_MOD_BROADCOM_VC4_T_TILED /* VideoCore T-tiling */
/* NVIDIA */
#define DRM_FORMAT_MOD_NVIDIA_BLOCK_LINEAR_2D /* GOB 기반 타일링 */
| 레이아웃 | 설명 | 장점 | 단점 |
|---|---|---|---|
| Linear | 행 순차 배치 (pitch × height) | 모든 디바이스 호환, 단순 | GPU 캐시 효율 낮음 |
| Tiled | 정사각 또는 직사각 타일 단위 배치 | 2D 공간 지역성 향상, GPU 캐시 효율 | 디바이스별 포맷 비호환 |
| Compressed | 타일링 + 무손실 압축 (CCS, DCC, AFBC) | 메모리 대역폭 절감 30~50% | 디바이스 간 공유 시 압축 해제 필요 |
/* 프레임버퍼 생성 시 modifier 지정 (ADDFB2) */
struct drm_mode_fb_cmd2 cmd = {
.width = 1920,
.height = 1080,
.pixel_format = DRM_FORMAT_XRGB8888,
.handles[0] = gem_handle,
.pitches[0] = pitch,
.modifier[0] = I915_FORMAT_MOD_Y_TILED, /* modifier 지정 */
.flags = DRM_MODE_FB_MODIFIERS, /* modifier 사용 플래그 */
};
drmIoctl(fd, DRM_IOCTL_MODE_ADDFB2, &cmd);
/* Plane이 지원하는 modifier 목록 조회 */
/* DRM_IOCTL_MODE_GETPLANE2 또는 IN_FORMATS blob property */
/* → (format, modifier) 쌍의 목록 반환 */
/* 드라이버: Plane에 지원 modifier 등록 */
static const uint64_t my_modifiers[] = {
DRM_FORMAT_MOD_LINEAR,
I915_FORMAT_MOD_Y_TILED,
I915_FORMAT_MOD_Y_TILED_CCS,
DRM_FORMAT_MOD_INVALID /* 종료 마커 */
};
drm_universal_plane_init(dev, plane, ...,
formats, num_formats, my_modifiers, ...);
DRM_FORMAT_MOD_LINEAR를 사용하거나, 양쪽 디바이스가 모두 지원하는 modifier를 협상해야 합니다.
Wayland에서는 zwp_linux_dmabuf_v1 프로토콜로 compositor와 클라이언트 간 modifier를 협상합니다.
GEM (Graphics Execution Manager) 메모리 관리
GEM은 GPU 메모리 버퍼 객체를 관리하는 프레임워크입니다. 유저 공간에서는 정수 핸들로 버퍼를 참조하고,
커널은 내부적으로 drm_gem_object로 관리합니다.
/* drm_gem_object 핵심 필드 발췌 (include/drm/drm_gem.h) */
struct drm_gem_object {
struct kref refcount; /* 참조 카운팅 */
unsigned handle_count; /* 유저 핸들 수 */
struct drm_device *dev; /* 소속 디바이스 */
struct file *filp; /* shmem backing store 또는 NULL */
struct drm_vma_offset_node vma_node; /* mmap 오프셋 관리 */
size_t size; /* 버퍼 크기 */
int name; /* flink 이름 (레거시) */
struct dma_buf *dma_buf; /* PRIME export/import 연결점 */
struct dma_buf_attachment *import_attach;
struct dma_resv *resv; /* 공유 fence 컨테이너 */
const struct drm_gem_object_funcs *funcs;
};
/* GEM 오퍼레이션 콜백 */
struct drm_gem_object_funcs {
void (*free)(struct drm_gem_object *obj);
int (*open)(struct drm_gem_object *obj, struct drm_file *file);
void (*close)(struct drm_gem_object *obj, struct drm_file *file);
int (*pin)(struct drm_gem_object *obj);
int (*vmap)(struct drm_gem_object *obj, struct iosys_map *map);
struct sg_table *(*get_sg_table)(struct drm_gem_object *obj);
};
DRM_IOCTL_MODE_CREATE_DUMB는 CPU mmap과 scanout을 위한 최소 공통 경로입니다.
반면 GBM이나 vendor UAPI가 만드는 렌더 타깃 BO는 tiling/modifier, 압축 메타데이터, 엔진별 배치 제약,
수입된 DMA-BUF attachment까지 함께 고려합니다.
GEM 버퍼 생명주기
| 단계 | ioctl / 함수 | 설명 |
|---|---|---|
| 생성 | DRM_IOCTL_MODE_CREATE_DUMB |
비가속 dumb 버퍼 생성 (KMS 전용). 드라이버별 ioctl로 가속 버퍼 생성 |
| 매핑 | DRM_IOCTL_MODE_MAP_DUMB → mmap() |
가짜 오프셋 반환 후 mmap으로 유저 공간에 매핑 |
| 공유 | DRM_IOCTL_PRIME_HANDLE_TO_FD |
GEM 핸들을 DMA-BUF fd로 변환하여 다른 디바이스와 공유 |
| 임포트 | DRM_IOCTL_PRIME_FD_TO_HANDLE |
외부 DMA-BUF fd를 로컬 GEM 핸들로 변환 |
| 해제 | DRM_IOCTL_GEM_CLOSE |
핸들 해제. 참조 카운트가 0이 되면 실제 메모리 반환 |
GEM SHMEM 헬퍼
대부분의 임베디드/모바일 GPU 드라이버는 drm_gem_shmem_object를 사용합니다.
시스템 메모리(shmem)에 버퍼를 할당하며, CMA(Contiguous Memory Allocator) 통합도 지원합니다.
#include <drm/drm_gem_shmem_helper.h>
/* GEM SHMEM 기반 드라이버의 dumb_create 구현 */
static int my_dumb_create(struct drm_file *file,
struct drm_device *dev,
struct drm_mode_create_dumb *args)
{
/* pitch 정렬 (하드웨어 요구사항에 맞춤) */
args->pitch = ALIGN(args->width * DIV_ROUND_UP(args->bpp, 8), 64);
args->size = args->pitch * args->height;
/* shmem 헬퍼가 할당 + 핸들 반환 */
return drm_gem_shmem_dumb_create(file, dev, args);
}
/* 드라이버에서 vmap으로 커널 가상 주소 획득 */
struct drm_gem_shmem_object *shmem = to_drm_gem_shmem_obj(gem_obj);
struct iosys_map map;
drm_gem_shmem_vmap(shmem, &map);
/* map.vaddr로 커널에서 버퍼 접근 */
TTM (Translation Table Manager)
TTM은 전용 VRAM(비디오 메모리)이 있는 GPU를 위한 메모리 관리자입니다. 버퍼를 VRAM과 시스템 메모리 사이에서 이동(eviction/migration)시키는 고급 기능을 제공합니다.
| 메모리 관리자 | 백업 스토리지 | VRAM 지원 | 사용 드라이버 |
|---|---|---|---|
| GEM (shmem) | 시스템 메모리 (shmem/CMA) | 없음 | panfrost, lima, vc4, v3d, virtio-gpu |
| GEM + VRAM helper | 시스템 + VRAM | 단순 | ast, simpledrm, hibmc |
| TTM | 시스템 + VRAM + GART | 전체 (eviction, migration) | amdgpu, nouveau, i915 (부분), vmwgfx, radeon |
/* TTM 메모리 도메인 기술자 */
struct ttm_place {
uint32_t mem_type; /* TTM_PL_SYSTEM, TTM_PL_VRAM, TTM_PL_TT */
uint32_t flags; /* TTM_PL_FLAG_CONTIGUOUS 등 */
uint64_t fpfn; /* 시작 페이지 프레임 번호 */
uint64_t lpfn; /* 끝 페이지 프레임 번호 (0=제한 없음) */
};
/* ttm_buffer_object 핵심 필드 발췌 */
struct ttm_buffer_object {
struct drm_gem_object base; /* GEM 상속 */
struct ttm_device *bdev; /* TTM 디바이스 */
enum ttm_bo_type type;
uint32_t page_alignment;
void (*destroy)(struct ttm_buffer_object *bo);
struct kref kref;
struct ttm_resource *resource; /* 현재 메모리 위치 */
struct ttm_tt *ttm; /* 시스템 메모리 페이지 */
bool deleted;
unsigned priority;
unsigned pin_count;
struct sg_table *sg;
};
/* TTM 메모리 도메인 상수 */
#define TTM_PL_SYSTEM 0 /* 시스템 RAM (커널 관리) */
#define TTM_PL_TT 1 /* GART/GTT 매핑된 시스템 메모리 */
#define TTM_PL_VRAM 2 /* 디바이스 전용 비디오 메모리 */
ttm_place/ttm_placement는 “이번 validate에서 허용할 위치 후보”를 설명하는 정책 객체입니다.
현재 실제 위치는 ttm_buffer_object.resource가 나타내며, TTM은 이 값과 새 placement 후보를 비교해
VRAM↔TT↔SYSTEM migration을 결정합니다.
ttm_device_funcs.evict_flags 콜백으로 퇴거 정책을 설정합니다.
GPU가 해당 버퍼에 접근하면 다시 VRAM으로 마이그레이션됩니다.
DMA-BUF (버퍼 공유)
DMA-BUF는 디바이스 간 버퍼 공유를 위한 커널 프레임워크입니다. GPU에서 렌더링한 버퍼를 디스플레이 컨트롤러, 비디오 인코더, 카메라 등 다른 디바이스와 복사 없이 공유할 수 있습니다.
/* DMA-BUF exporter 오퍼레이션 (드라이버 구현) */
static const struct dma_buf_ops my_dmabuf_ops = {
.attach = my_dmabuf_attach, /* importer 디바이스 등록 */
.detach = my_dmabuf_detach,
.map_dma_buf = my_dmabuf_map, /* sg_table 반환 (DMA 주소) */
.unmap_dma_buf = my_dmabuf_unmap,
.release = my_dmabuf_release, /* 버퍼 해제 */
.mmap = my_dmabuf_mmap, /* 유저 공간 매핑 */
.vmap = my_dmabuf_vmap, /* 커널 가상 주소 매핑 */
};
/* GEM → DMA-BUF export (PRIME) */
struct dma_buf *dmabuf = drm_gem_prime_export(gem_obj, flags);
/* → 유저 공간에서 fd = DRM_IOCTL_PRIME_HANDLE_TO_FD(handle) */
/* DMA-BUF → GEM import (PRIME) */
struct drm_gem_object *obj = drm_gem_prime_import(dev, dmabuf);
/* → 유저 공간에서 handle = DRM_IOCTL_PRIME_FD_TO_HANDLE(fd) */
dma_fence & dma_resv (동기화)
GPU 작업은 비동기로 실행되므로, 버퍼를 공유하는 디바이스들은 작업 완료를 동기화해야 합니다.
| 구조체 | 역할 | 사용 예 |
|---|---|---|
dma_fence |
GPU 작업 하나의 완료를 나타내는 동기화 프리미티브 | GPU 커맨드 서브미션 후 fence 생성 |
dma_resv |
버퍼별 fence 집합 관리 (reservation object) | DMA-BUF에 내장. 읽기/쓰기 fence 추적 |
sync_file |
dma_fence를 유저 공간 fd로 노출 | Explicit sync (Vulkan, Android) |
/* Fence 기본 사용 패턴 */
struct dma_fence *fence;
/* GPU 작업 제출 시 fence 생성 */
fence = my_gpu_submit_job(job);
/* 버퍼의 reservation object에 fence 등록 */
dma_resv_add_fence(bo->base.resv, fence, DMA_RESV_USAGE_WRITE);
/* 다른 디바이스가 이 버퍼를 사용하기 전에 대기 */
ret = dma_resv_wait_timeout(bo->base.resv, DMA_RESV_USAGE_WRITE,
true, msecs_to_jiffies(5000));
/* Fence 시그널 (GPU 인터럽트 핸들러에서 호출) */
dma_fence_signal(fence);
dma_fence_put(fence);
Implicit Sync vs Explicit Sync
GPU 동기화 모델은 두 가지로 나뉩니다. Linux DRM은 전통적으로 implicit sync를 사용했으나, Vulkan과 Android를 중심으로 explicit sync로 전환하고 있습니다.
| 항목 | Implicit Sync (암시적) | Explicit Sync (명시적) |
|---|---|---|
| fence 관리 | 커널이 dma_resv에 자동 등록/확인 |
유저 공간이 sync_file fd로 직접 관리 |
| KMS 인터페이스 | 커널이 프레임버퍼의 fence를 자동 대기 | IN_FENCE_FD / OUT_FENCE_PTR plane property 사용 |
| 장점 | 유저 공간 구현 단순, 레거시 호환 | 세밀한 동기화 제어, GPU 파이프라인 최적화 가능 |
| 단점 | 불필요한 대기 발생 가능, 최적화 어려움 | 유저 공간 복잡도 증가 |
| 사용 API | OpenGL (EGL), GBM | Vulkan, Android HWC, Wayland explicit sync |
| 커널 구성 | dma_resv + 자동 fence 추적 |
sync_file + SYNC_IOC_MERGE / SYNC_IOC_FILE_INFO |
/* Explicit Sync: KMS IN/OUT fence 사용 */
/* 유저 공간: GPU 렌더링 완료 fence fd를 Plane에 전달 */
drmModeAtomicAddProperty(req, plane_id, IN_FENCE_FD_prop, gpu_fence_fd);
/* 유저 공간: 커밋 완료 fence fd를 받을 위치 지정 */
int64_t out_fence_fd = -1;
drmModeAtomicAddProperty(req, crtc_id, OUT_FENCE_PTR_prop,
(uint64_t)(uintptr_t)&out_fence_fd);
/* Atomic 커밋 */
drmModeAtomicCommit(fd, req, DRM_MODE_ATOMIC_NONBLOCK |
DRM_MODE_PAGE_FLIP_EVENT, NULL);
/* out_fence_fd에 유효한 fd가 설정됨
* → 다음 렌더링 시작 전에 이 fence를 GPU에 제출하여 대기
* → 완전한 파이프라인 동기화 */
wp_linux_drm_syncobj_v1 계열 프로토콜로 DRM syncobj 기반 explicit sync를 협상할 수 있습니다.
핵심은 “커널 버전 숫자”보다 사용 중인 compositor, Mesa/driver, 커널 UAPI가 모두 syncobj timeline과 KMS fence를 연결할 수 있는지입니다.
즉, explicit sync는 render node 제출 경로와 KMS atomic commit 경로가 같은 fence 언어를 공유할 때 가장 큰 효과를 냅니다.
GPU 커맨드 서브미션
GPU 렌더링 작업은 커맨드 버퍼(command buffer)를 통해 제출됩니다. 유저 공간(Mesa/Vulkan 드라이버)이 GPU 명령어를 버퍼에 기록하고, 커널 드라이버가 이를 GPU 하드웨어에 전달합니다.
서브미션 모델 비교
| 모델 | 드라이버 | ioctl | 특징 |
|---|---|---|---|
| Ring Buffer | i915 (레거시) | I915_GEM_EXECBUFFER2 |
커맨드 버퍼를 링 버퍼에 복사. relocation으로 GPU 주소 패칭 |
| Submit Queue | amdgpu | AMDGPU_CS |
IB(Indirect Buffer) 체인 제출. 다중 엔진(GFX/SDMA/UVD) 지원 |
| Exec Queue | xe (Intel) | DRM_XE_EXEC |
VM-bind 기반. relocation 없음, GPU 페이지 테이블 직접 관리 |
| Submitqueue | msm (Qualcomm) | MSM_GEM_SUBMIT |
우선순위별 큐. faults 지원 (sparse binding) |
| Simple Submit | panfrost, lima | 드라이버별 | drm_sched 기반. 단순 job 제출 |
/* amdgpu 커맨드 서브미션 예시 (유저 공간, libdrm_amdgpu) */
struct amdgpu_cs_request cs_req = {};
struct amdgpu_cs_ib_info ib_info = {
.ib_mc_address = ib_gpu_addr, /* IB의 GPU 가상 주소 */
.size = ib_size_dw, /* 더블워드 단위 크기 */
};
cs_req.ip_type = AMDGPU_HW_IP_GFX; /* GFX 엔진 */
cs_req.number_of_ibs = 1;
cs_req.ibs = &ib_info;
/* 의존 fence 목록 (이전 작업 완료 대기) */
struct amdgpu_cs_fence_info fence_info = { ... };
cs_req.fence_info = fence_info;
/* 서브미션 */
amdgpu_cs_submit(ctx, 0, &cs_req, 1);
/* 완료 대기 (선택적) */
struct amdgpu_cs_fence fence = {
.context = ctx,
.ip_type = AMDGPU_HW_IP_GFX,
.fence = cs_req.seq_no,
};
amdgpu_cs_query_fence_status(&fence, timeout_ns, 0, &expired);
- 커널 드라이버의 커맨드 서브미션 검증 (보안상 중요) */
- 버퍼 오브젝트 유효성 */
- 유저가 전달한 GEM 핸들이 실제 소유인지 확인 */
- BO가 올바른 메모리 도메인에 있는지 확인 */
- 커맨드 파싱 (일부 드라이버) */
- 금지된 GPU 레지스터 접근 차단 */
- 다른 프로세스의 GPU 주소 공간 접근 방지 */
- 최신 GPU는 하드웨어 격리 (per-context page table)로 대체 */
- 의존성 처리 */
- 공유 BO의 dma_resv에서 기존 fence 확인 */
- 의존 fence를 drm_sched_job의 dependency로 등록 */
- 모든 의존 fence가 시그널된 후에만 GPU에 제출 */
- fence 등록 */
- 새 작업의 fence를 사용 BO의 dma_resv에 추가 */
- explicit sync: fence를 sync_file fd로 유저에게 반환 */
GPU 스케줄러 (drm_sched)
DRM GPU 스케줄러(drivers/gpu/drm/scheduler/)는 여러 프로세스의 GPU 작업을 공정하게 스케줄링하고,
GPU 행(hang) 감지 및 복구를 처리합니다.
#include <drm/gpu_scheduler.h>
/* 스케줄러 초기화 (드라이버 probe에서) */
struct drm_gpu_scheduler sched;
drm_sched_init(&sched,
&my_sched_ops, /* 드라이버 콜백 */
NULL, /* workqueue (NULL=기본) */
num_hw_rings, /* 하드웨어 큐 수 */
0, /* credit limit */
msecs_to_jiffies(10000), /* 타임아웃 (10초) */
NULL, /* workqueue for timeout */
NULL, /* score */
"my-gpu", /* 이름 */
dev->dev);
/* 스케줄러 오퍼레이션 */
static const struct drm_sched_backend_ops my_sched_ops = {
.run_job = my_run_job, /* GPU에 작업 제출 */
.timedout_job = my_timedout_job, /* 타임아웃 시 GPU 리셋 */
.free_job = my_free_job, /* 완료된 작업 해제 */
};
/* run_job 구현 예시 */
static struct dma_fence *my_run_job(struct drm_sched_job *job)
{
struct my_job *mj = to_my_job(job);
/* GPU 하드웨어 레지스터에 커맨드 버퍼 주소 기록 */
writel(mj->cmd_buf_addr, gpu->regs + CMD_SUBMIT);
/* 완료 fence 반환 (GPU 인터럽트에서 시그널) */
return dma_fence_get(mj->fence);
}
| 구성 요소 | 설명 |
|---|---|
drm_sched_entity | 클라이언트별 작업 큐. 유저 컨텍스트 하나가 entity 하나를 소유. 우선순위 지정 가능 |
drm_sched_job | GPU에 제출할 작업 하나. 의존 fence 목록 보유 |
drm_gpu_scheduler | 하나의 GPU 엔진(링 버퍼)을 관리. 라운드 로빈으로 entity에서 job 디큐 |
| 우선순위 | DRM_SCHED_PRIORITY_KERNEL > HIGH > NORMAL > LOW |
| 타임아웃 | job이 지정 시간 내 완료 안 되면 timedout_job 콜백 → GPU 리셋 |
DRM 드라이버 기본 구조
최소한의 KMS DRM 드라이버 (디스플레이만 지원, GPU 가속 없음)의 골격입니다.
#include <linux/module.h>
#include <linux/platform_device.h>
#include <drm/drm_drv.h>
#include <drm/drm_gem_shmem_helper.h>
#include <drm/drm_atomic_helper.h>
#include <drm/drm_simple_kms_helper.h>
#include <drm/drm_fbdev_shmem.h>
struct my_drm {
struct drm_device drm;
struct drm_simple_display_pipe pipe;
struct drm_connector connector;
void __iomem *regs;
};
/* --- Connector --- */
static int my_connector_get_modes(struct drm_connector *conn)
{
/* 고정 해상도 모드 추가 (실제로는 EDID 파싱) */
return drm_add_modes_noedid(conn, 1920, 1080);
}
static const struct drm_connector_helper_funcs my_conn_helpers = {
.get_modes = my_connector_get_modes,
};
/* --- Simple Display Pipe --- */
static void my_pipe_enable(struct drm_simple_display_pipe *pipe,
struct drm_crtc_state *crtc_state,
struct drm_plane_state *plane_state)
{
struct my_drm *my = container_of(pipe, struct my_drm, pipe);
struct drm_framebuffer *fb = plane_state->fb;
/* 하드웨어 레지스터에 프레임버퍼 주소/포맷 설정 */
writel(drm_fb_dma_get_gem_addr(fb, plane_state, 0),
my->regs + FB_ADDR_REG);
writel(fb->pitches[0], my->regs + FB_PITCH_REG);
/* 디스플레이 엔진 활성화 */
writel(1, my->regs + DISPLAY_ENABLE_REG);
}
static void my_pipe_update(struct drm_simple_display_pipe *pipe,
struct drm_plane_state *old_state)
{
struct drm_plane_state *state = pipe->plane.state;
if (state->fb)
my_pipe_enable(pipe, NULL, state);
}
static const struct drm_simple_display_pipe_funcs my_pipe_funcs = {
.enable = my_pipe_enable,
.update = my_pipe_update,
};
/* --- DRM Driver --- */
DEFINE_DRM_GEM_FOPS(my_fops);
static const struct drm_driver my_driver = {
.driver_features = DRIVER_GEM | DRIVER_MODESET | DRIVER_ATOMIC,
.fops = &my_fops,
DRM_GEM_SHMEM_DRIVER_OPS,
.name = "my-display",
.desc = "My simple display driver",
.date = "20260101",
.major = 1,
.minor = 0,
};
/* --- Platform Driver --- */
static int my_probe(struct platform_device *pdev)
{
struct my_drm *my;
struct drm_device *drm;
static const uint32_t formats[] = { DRM_FORMAT_XRGB8888 };
int ret;
my = devm_drm_dev_alloc(&pdev->dev, &my_driver,
struct my_drm, drm);
if (IS_ERR(my))
return PTR_ERR(my);
drm = &my->drm;
/* MMIO 매핑 */
my->regs = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(my->regs))
return PTR_ERR(my->regs);
/* Connector 초기화 */
drm_connector_init(drm, &my->connector,
&my_conn_funcs, DRM_MODE_CONNECTOR_Unknown);
drm_connector_helper_add(&my->connector, &my_conn_helpers);
/* Simple display pipe 초기화 (CRTC+Plane+Encoder 한 번에) */
ret = drm_simple_display_pipe_init(drm, &my->pipe,
&my_pipe_funcs, formats, ARRAY_SIZE(formats),
NULL, &my->connector);
if (ret)
return ret;
drm_mode_config_reset(drm);
ret = drm_dev_register(drm, 0);
if (ret)
return ret;
/* fbdev 에뮬레이션 (콘솔 출력) */
drm_fbdev_shmem_setup(drm, 32);
return 0;
}
MODULE_LICENSE("GPL");
주요 오픈소스 DRM 드라이버
| 드라이버 | 하드웨어 | 커널 경로 | 메모리 관리 | 특징 |
|---|---|---|---|---|
| i915 | Intel 내장 GPU | drivers/gpu/drm/i915/ |
GEM + GTT/GGTT | GuC/HuC 펌웨어 지원, Display 13/14, Xe 드라이버로 전환 중 |
| xe | Intel Xe GPU (DG2+) | drivers/gpu/drm/xe/ |
TTM 기반 | i915 후속. 디스크리트 GPU(Arc) 지원, drm_sched 사용 |
| amdgpu | AMD Radeon (GCN+) | drivers/gpu/drm/amd/ |
TTM | DC (Display Core), KFD (HSA 컴퓨트), SR-IOV, RAS |
| nouveau | NVIDIA (리버스 엔지니어링) | drivers/gpu/drm/nouveau/ |
TTM | 커뮤니티 개발, 최신 GPU 지원 제한적, GSP 펌웨어 지원 |
| panfrost | ARM Mali (Midgard/Bifrost) | drivers/gpu/drm/panfrost/ |
GEM shmem | OpenGL ES 지원, drm_sched 사용, MMU 자체 관리 |
| lima | ARM Mali (Utgard) | drivers/gpu/drm/lima/ |
GEM shmem | 레거시 ARM Mali-400/450, OpenGL ES 2.0 |
| vc4 | Broadcom VideoCore IV | drivers/gpu/drm/vc4/ |
GEM CMA | Raspberry Pi 0~3. v3d는 Pi 4/5용 별도 드라이버 |
| msm | Qualcomm Adreno | drivers/gpu/drm/msm/ |
GEM | Freedreno 스택, submitqueue 기반 커맨드 제출 |
| virtio-gpu | 가상 GPU (QEMU/virgl) | drivers/gpu/drm/virtio/ |
GEM shmem | VM 내 3D 가속, virglrenderer로 호스트 GPU 사용 |
| simpledrm | EFI/BIOS 프레임버퍼 | drivers/gpu/drm/tiny/simpledrm.c |
GEM shmem | 부팅 초기 콘솔. UEFI GOP/VBE 프레임버퍼 위에서 동작 |
nvidia-open).
nouveau는 커뮤니티 리버스 엔지니어링 기반으로, 최신 GPU에서는 전력 관리(reclocking)와 성능이 제한됩니다.
최근 커널에서는 GSP(GPU System Processor) 펌웨어를 통해 nouveau의 기능이 개선되고 있습니다.
최근 DRM 코어의 방향
버전 번호 자체보다 중요한 것은 메인라인이 어떤 설계 축으로 이동하는가입니다. 최근 DRM 문서와 헤더를 보면 다음 네 가지 흐름이 거의 모든 드라이버 설계에 공통으로 반영됩니다.
| 축 | 최근 방향 | 실무 의미 |
|---|---|---|
| 권한 분리 | primary/render/accel 노드 역할이 더 명확해짐 | display server, 일반 앱, AI runtime이 같은 fd 모델을 공유하지 않도록 설계해야 합니다. |
| 상태 객체 확장 | atomic property가 색 관리, writeback, HDR/VRR, content protection까지 포괄 | 새 기능은 대개 ioctl 추가보다 property/state 추가로 들어옵니다. |
| 동기화 명시화 | dma_resv, syncobj, explicit sync, timeline fence 사용이 확대 |
render 완료와 KMS 표시 완료를 같은 fence 체인으로 연결하는 능력이 중요해집니다. |
| 복구/관측성 | drm_sched timeout, reset recovery, drm_panic, tracepoint 강화 |
좋은 드라이버는 빠르기만 한 것이 아니라 hang 후에도 원인과 복구 경로를 설명할 수 있어야 합니다. |
drm_panic의 위치: GPU가 패닉 화면을 직접 그리는 기능은 “최근 흐름”의 일부입니다.
하지만 이것은 특정 드라이버가 패닉 시점에도 안전하게 최소 렌더 경로를 유지할 수 있어야 가능한 기능이므로,
일반적인 fast path 설계와는 분리해서 생각해야 합니다.
DRM Bridge & Panel 프레임워크
임베디드/모바일 SoC에서는 디스플레이 출력이 여러 개의 하드웨어 블록 체인으로 구성됩니다.
drm_bridge는 이 체인의 중간 단계(MIPI-DSI to HDMI 변환기, LVDS 직렬화기 등)를,
drm_panel은 최종 디스플레이 패널을 추상화합니다.
/* DRM Bridge 드라이버 구현 예시 */
#include <drm/drm_bridge.h>
static const struct drm_bridge_funcs my_bridge_funcs = {
/* 디스플레이 파이프라인 활성화 순서 */
.pre_enable = my_bridge_pre_enable, /* PLL/클럭 설정 */
.enable = my_bridge_enable, /* 출력 활성화 */
.disable = my_bridge_disable,
.post_disable = my_bridge_post_disable,
/* 모드 검증 */
.mode_valid = my_bridge_mode_valid, /* 지원 해상도 필터링 */
.mode_fixup = my_bridge_mode_fixup, /* adjusted_mode 수정 */
/* Atomic 지원 (권장) */
.atomic_pre_enable = my_bridge_atomic_pre_enable,
.atomic_enable = my_bridge_atomic_enable,
.atomic_disable = my_bridge_atomic_disable,
/* 출력 감지 (DRM_BRIDGE_OP_DETECT) */
.detect = my_bridge_detect,
.get_modes = my_bridge_get_modes, /* EDID 기반 모드 목록 */
.get_edid = my_bridge_get_edid, /* EDID 읽기 */
};
static int my_bridge_probe(struct i2c_client *client)
{
struct my_bridge *mb = devm_kzalloc(&client->dev, sizeof(*mb), GFP_KERNEL);
mb->bridge.funcs = &my_bridge_funcs;
mb->bridge.of_node = client->dev.of_node;
mb->bridge.type = DRM_MODE_CONNECTOR_HDMIA; /* 출력 커넥터 타입 */
mb->bridge.ops = DRM_BRIDGE_OP_DETECT |
DRM_BRIDGE_OP_EDID |
DRM_BRIDGE_OP_MODES;
drm_bridge_add(&mb->bridge);
return 0;
}
/* DRM Panel 드라이버 구현 예시 */
#include <drm/drm_panel.h>
static const struct drm_panel_funcs my_panel_funcs = {
.prepare = my_panel_prepare, /* 전원 ON, 리셋 해제 */
.enable = my_panel_enable, /* 백라이트 ON, 디스플레이 ON */
.disable = my_panel_disable, /* 백라이트 OFF */
.unprepare = my_panel_unprepare, /* 전원 OFF */
.get_modes = my_panel_get_modes, /* 고정 모드 반환 */
};
static int my_panel_probe(struct mipi_dsi_device *dsi)
{
struct my_panel *panel = devm_kzalloc(&dsi->dev, sizeof(*panel), GFP_KERNEL);
drm_panel_init(&panel->panel, &dsi->dev, &my_panel_funcs,
DRM_MODE_CONNECTOR_DSI);
drm_panel_add(&panel->panel);
return 0;
}
/* 디스플레이 드라이버에서 bridge + panel 연결 */
struct drm_bridge *bridge;
bridge = devm_drm_of_get_bridge(dev, dev->of_node, 0, 0);
/* drm_panel은 자동으로 panel-bridge로 래핑됨 */
drm_bridge_attach(encoder, bridge, NULL, DRM_BRIDGE_ATTACH_NO_CONNECTOR);
/* 체인의 마지막 bridge에서 connector 생성 */
connector = drm_bridge_connector_init(drm, encoder);
ports/port/endpoint 노드로 연결됩니다.
of_graph_get_remote_port_parent()로 연결된 디바이스를 탐색하고,
devm_drm_of_get_bridge()가 DT 그래프를 따라 bridge 또는 panel-bridge를 자동으로 찾습니다.
GPU 가상 메모리 (GPUVM)
현대 GPU는 자체 MMU를 가지며, 프로세스별 독립적인 GPU 가상 주소 공간을 제공합니다.
커널 DRM 프레임워크는 drm_gpuvm(drivers/gpu/drm/drm_gpuvm.c)으로 이를 추상화합니다.
GPU 전원 관리
GPU는 시스템에서 가장 전력을 많이 소비하는 디바이스 중 하나이므로, 정교한 전원 관리가 매우 중요합니다.
| 메커니즘 | 설명 | 커널 인터페이스 |
|---|---|---|
| Runtime PM | GPU 유휴 시 자동 절전. D3cold까지 진입 가능 | pm_runtime_get_sync() / pm_runtime_put_autosuspend() |
| DVFS | Dynamic Voltage and Frequency Scaling. 부하에 따라 클럭/전압 조절 | devfreq 프레임워크 또는 드라이버 자체 구현 |
| Power Gating | 미사용 GPU 블록(셰이더 유닛 등)의 전원을 완전히 차단 | 드라이버별 구현 (HW 의존) |
| Clock Gating | 미사용 블록의 클럭만 차단 (power gating보다 빠른 복구) | 드라이버별 구현 |
| Hybrid GPU | 내장/외장 GPU 전환 (PRIME offload, reverse PRIME) | DRI_PRIME=1 환경 변수, switcheroo sysfs |
# GPU Runtime PM 상태 확인
cat /sys/class/drm/card0/device/power/runtime_status
# active / suspended / suspending
# Runtime PM 자동 절전 지연 설정 (밀리초)
echo 5000 > /sys/class/drm/card0/device/power/autosuspend_delay_ms
# GPU 클럭 주파수 확인 (amdgpu 예시)
cat /sys/class/drm/card0/device/pp_dpm_sclk
# 0: 300Mhz
# 1: 600Mhz *
# 2: 900Mhz
# PRIME GPU 오프로드 (외장 GPU로 렌더링)
DRI_PRIME=1 glxinfo | grep "OpenGL renderer"
# vga_switcheroo 상태 (하이브리드 GPU)
cat /sys/kernel/debug/vgaswitcheroo/switch
GPU 리셋 및 복구
GPU는 잘못된 셰이더, 무한 루프, 하드웨어 결함 등으로 응답 불능(hang) 상태에 빠질 수 있습니다. DRM 프레임워크는 hang 감지, GPU 리셋, 작업 재제출의 3단계 복구 메커니즘을 제공합니다.
Hang 감지
| 메커니즘 | 설명 | 구현 |
|---|---|---|
| 스케줄러 타임아웃 | drm_sched이 job별 타이머 설정. 만료 시 timedout_job 콜백 |
대부분의 현대 드라이버 |
| 하드웨어 워치독 | GPU 내부 워치독 타이머가 hang 감지 후 인터럽트 발생 | amdgpu (UVD/VCE), 일부 ARM GPU |
| Heartbeat | 주기적으로 GPU에 nop 작업 제출 후 완료 확인 | i915 (engine heartbeat) |
| Seqno 모니터링 | GPU가 작업 완료 시 증가시키는 시퀀스 번호를 주기적으로 확인 | 레거시 드라이버 |
리셋 수준
/* GPU 리셋 수준 (세밀한 것부터 거친 순서) */
/* Level 1: Per-engine 리셋 (권장, 최소 영향) */
/* 특정 GPU 엔진(GFX, SDMA, 비디오 등)만 리셋 */
/* 다른 엔진은 계속 동작 */
amdgpu_device_reset_engine(adev, ring); /* amdgpu 예시 */
/* Level 2: Per-context 리셋 (하드웨어 지원 필요) */
/* 특정 GPU 컨텍스트만 무효화 */
/* i915: 잘못된 컨텍스트를 ban (금지) 처리 */
intel_context_ban(ce, NULL);
/* Level 3: 전체 GPU 리셋 */
/* 모든 엔진/컨텍스트 중단 후 GPU 완전 재초기화 */
amdgpu_device_gpu_recover(adev, NULL, false);
/* Level 4: FLR (Function Level Reset) - PCIe */
/* PCIe 레벨에서 디바이스 전체 리셋 */
pci_reset_function(pdev);
/* Level 5: BACO (Bus Active, Chip Off) - AMD */
/* GPU 전원을 완전히 차단 후 재투입 */
amdgpu_device_baco_enter(adev_to_drm(adev));
amdgpu_device_baco_exit(adev_to_drm(adev));
복구 흐름
/* drm_sched 타임아웃 → 복구 흐름 */
/* 1. 타임아웃 콜백 호출 */
static enum drm_gpu_sched_stat
my_timedout_job(struct drm_sched_job *job)
{
struct my_device *dev = job_to_dev(job);
/* 2. GPU 상태 덤프 (디버깅용) */
dev_coredump_snapshot(dev); /* devcoredump 프레임워크 */
/* 3. GPU 리셋 수행 */
my_gpu_reset(dev);
/* 4. 스케줄러에 리셋 완료 통보 */
/* → 대기 중인 fence들을 에러로 시그널 */
/* → guilty 작업의 entity에 guilty 플래그 설정 */
return DRM_GPU_SCHED_STAT_NOMINAL;
}
/* 5. 유저 공간 알림 */
/* - 리셋 후 fence가 에러(-EIO)로 시그널됨 */
/* - Vulkan: VK_ERROR_DEVICE_LOST 반환 */
/* - GL: GL_CONTEXT_LOST 또는 glGetGraphicsResetStatusARB() */
/* - amdgpu: AMDGPU_CTX_QUERY2 ioctl로 리셋 감지 */
- VRAM 콘텐츠: 전체 리셋 시 VRAM 내용이 소실될 수 있음. TTM이 시스템 메모리에 백업한 BO만 복구 가능
- 디스플레이: GPU 리셋 중 화면이 일시적으로 검은색으로 전환될 수 있음 (modeset 재설정 필요)
- 멀티 GPU: SR-IOV 환경에서는 VF(Virtual Function) 리셋이 다른 VF에 영향을 줄 수 있음
- RAS (Reliability): amdgpu는
ras_controller로 ECC 에러 감지 후 수정 불가 에러 시 자동 리셋
# GPU 리셋 관련 sysfs/debugfs
# amdgpu: 수동 GPU 복구 트리거
echo 1 > /sys/kernel/debug/dri/0/amdgpu_gpu_recover
# i915: 엔진 리셋 카운트
cat /sys/kernel/debug/dri/0/i915_reset_info
# devcoredump: GPU 상태 덤프 읽기
cat /sys/class/devcoredump/devcd0/data > gpu_dump.bin
# devcoredump 삭제 (5분 후 자동 삭제)
echo 1 > /sys/class/devcoredump/devcd0/data
# dmesg에서 GPU 리셋 로그 확인
dmesg | grep -i "gpu\|reset\|hang\|timed out"
커널 설정 (Kconfig)
# DRM 핵심 설정
CONFIG_DRM=m # DRM 코어 모듈
CONFIG_DRM_KMS_HELPER=m # KMS 헬퍼 함수
CONFIG_DRM_GEM_SHMEM_HELPER=m # GEM shmem 헬퍼
CONFIG_DRM_SCHED=m # GPU 스케줄러
CONFIG_DRM_TTM=m # TTM 메모리 관리자
CONFIG_DRM_DISPLAY_HELPER=m # DP/HDMI 디스플레이 헬퍼
# 드라이버별 설정
CONFIG_DRM_I915=m # Intel i915
CONFIG_DRM_XE=m # Intel Xe
CONFIG_DRM_AMDGPU=m # AMD GPU
CONFIG_DRM_NOUVEAU=m # NVIDIA (오픈소스)
CONFIG_DRM_PANFROST=m # ARM Mali (Midgard/Bifrost)
CONFIG_DRM_LIMA=m # ARM Mali (Utgard)
CONFIG_DRM_VC4=m # Broadcom VideoCore
CONFIG_DRM_MSM=m # Qualcomm Adreno
CONFIG_DRM_VIRTIO_GPU=m # 가상 GPU (QEMU)
CONFIG_DRM_SIMPLEDRM=y # EFI/BIOS 프레임버퍼
# fbdev 에뮬레이션 (DRM 위에서 /dev/fb0 제공)
CONFIG_DRM_FBDEV_EMULATION=y
# DMA-BUF
CONFIG_DMA_SHARED_BUFFER=y
DRM 디버깅
디버깅 도구
| 도구 | 용도 | 사용법 |
|---|---|---|
modetest |
KMS 오브젝트 나열, 모드 테스트 | modetest -M i915 (libdrm 제공) |
drm_info |
DRM 디바이스 전체 정보 출력 (JSON) | drm_info |
intel_gpu_top |
Intel GPU 엔진별 사용률 모니터링 | intel_gpu_top (intel-gpu-tools) |
umr |
AMD GPU 레지스터 읽기/디코딩 | umr -O bits -r *.mmMC_VM_FB_LOCATION_BASE |
weston-debug |
Wayland compositor 디버깅 | Weston의 DRM backend 로그 |
| debugfs | DRM 내부 상태 확인 | /sys/kernel/debug/dri/0/ |
커널 디버그 메시지
# DRM 디버그 레벨 설정 (비트마스크)
echo 0x1ff > /sys/module/drm/parameters/debug
# 비트마스크 의미:
# 0x001 = DRM_UT_CORE — DRM 코어
# 0x002 = DRM_UT_DRIVER — 드라이버
# 0x004 = DRM_UT_KMS — 모드 설정
# 0x008 = DRM_UT_PRIME — PRIME/DMA-BUF
# 0x010 = DRM_UT_ATOMIC — Atomic modesetting
# 0x020 = DRM_UT_VBL — Vblank
# 0x040 = DRM_UT_STATE — Atomic 상태
# 0x080 = DRM_UT_LEASE — DRM 리스
# 0x100 = DRM_UT_DP — DisplayPort
# 0x200 = DRM_UT_DRMRES — 리소스 관리
# 부팅 시 커널 파라미터로 설정
# drm.debug=0x1e (DRIVER+KMS+PRIME+ATOMIC)
# DRM debugfs 정보 확인
ls /sys/kernel/debug/dri/0/
# clients gem_names name state ...
# GEM 객체 목록 (메모리 사용량 확인)
cat /sys/kernel/debug/dri/0/gem_names
# 현재 KMS 상태 (모든 CRTC/Plane/Connector)
cat /sys/kernel/debug/dri/0/state
# GPU 행(hang) 시 ftrace로 추적
echo 1 > /sys/kernel/debug/tracing/events/drm/enable
cat /sys/kernel/debug/tracing/trace_pipe
dmesg에서"GPU HANG"또는"timed out"메시지 확인- i915:
/sys/kernel/debug/dri/0/i915_gpu_info에서 엔진 상태 덤프 - amdgpu:
/sys/kernel/debug/dri/0/amdgpu_gpu_recover로 수동 GPU 리셋 - GPU coredump: 일부 드라이버는
devcoredump프레임워크로 GPU 상태 덤프 생성 (/sys/class/devcoredump/)
GPU 성능 프로파일링
| 도구 | 대상 GPU | 측정 항목 | 사용 예 |
|---|---|---|---|
intel_gpu_top |
Intel | 엔진 사용률, 주파수, 전력 | intel_gpu_top -l |
radeontop |
AMD | 파이프라인 사용률, VRAM 사용량 | radeontop -d - |
nvtop |
AMD/Intel/NVIDIA | 프로세스별 GPU 사용률 (htop 스타일) | nvtop |
gpu_metrics |
AMD (sysfs) | 온도, 전력, 주파수, 팬 속도 | cat /sys/class/drm/card0/device/gpu_metrics |
| i915 perf | Intel | EU 효율, 메모리 대역폭, 캐시 히트율 | intel_gpu_frequency -g |
| ftrace | 모든 DRM | drm_vblank, atomic_commit 이벤트 추적 | trace-cmd record -e drm |
# 프레임 드롭 디버깅 — vblank 이벤트 추적
echo 1 > /sys/kernel/debug/tracing/events/drm/drm_vblank_event/enable
echo 1 > /sys/kernel/debug/tracing/events/drm/drm_vblank_event_delivered/enable
cat /sys/kernel/debug/tracing/trace_pipe
# 출력: vblank 시퀀스 번호와 타임스탬프 → 누락된 프레임 확인
# Atomic commit 성능 추적
echo 1 > /sys/kernel/debug/tracing/events/drm/drm_atomic_state/enable
# commit 소요 시간, 포함된 CRTC/Plane 수 확인
# GPU 메모리 사용량 상세 (드라이버별 debugfs)
# amdgpu:
cat /sys/kernel/debug/dri/0/amdgpu_gem_info # 프로세스별 GEM 사용량
cat /sys/kernel/debug/dri/0/amdgpu_vram_mm # VRAM 할당 맵
cat /sys/kernel/debug/dri/0/amdgpu_vm_info # GPU 가상 메모리 통계
# i915:
cat /sys/kernel/debug/dri/0/i915_gem_objects # GEM 객체 통계
cat /sys/kernel/debug/dri/0/i915_frequency_info # GPU 주파수 정보
DRM ioctl 요약
| ioctl | 용도 | 필요 권한 |
|---|---|---|
DRM_IOCTL_VERSION | 드라이버 이름/버전 조회 | 없음 |
DRM_IOCTL_GET_CAP | 기능 조회 (PRIME, ATOMIC 등) | 없음 |
DRM_IOCTL_MODE_GETRESOURCES | CRTC/Connector/Encoder ID 목록 | 없음 |
DRM_IOCTL_MODE_GETCONNECTOR | Connector 상태, 지원 모드 조회 | 없음 |
DRM_IOCTL_MODE_GETENCODER | Encoder 정보 조회 | 없음 |
DRM_IOCTL_MODE_GETCRTC | CRTC 현재 모드 조회 | 없음 |
DRM_IOCTL_MODE_SETCRTC | 모드 설정 (레거시) | DRM Master |
DRM_IOCTL_MODE_ATOMIC | Atomic 모드 설정/페이지 플립 | DRM Master |
DRM_IOCTL_MODE_CREATE_DUMB | Dumb 버퍼 생성 | 없음 |
DRM_IOCTL_MODE_MAP_DUMB | Dumb 버퍼 mmap 오프셋 | 없음 |
DRM_IOCTL_MODE_DESTROY_DUMB | Dumb 버퍼 해제 | 없음 |
DRM_IOCTL_PRIME_HANDLE_TO_FD | GEM → DMA-BUF fd | 없음 |
DRM_IOCTL_PRIME_FD_TO_HANDLE | DMA-BUF fd → GEM | 없음 |
DRM_IOCTL_GEM_CLOSE | GEM 핸들 해제 | 없음 |
DRM_IOCTL_MODE_ADDFB2 | 프레임버퍼 객체 생성 | 없음 |
DRM_IOCTL_MODE_PAGE_FLIP | 페이지 플립 (레거시) | DRM Master |
DRM_IOCTL_SET_CLIENT_CAP | 클라이언트 기능 활성화 (ATOMIC 등) | DRM Master |
DRM Lease (디스플레이 리스)
DRM Lease는 DRM Master가 자신의 KMS 리소스(CRTC, Connector, Plane) 일부를 다른 프로세스에게 독점적으로 위임하는 메커니즘입니다. VR 헤드셋, 멀티시트 디스플레이, 게임 전용 출력 등에서 사용됩니다.
| ioctl | 설명 | 호출자 |
|---|---|---|
DRM_IOCTL_MODE_CREATE_LEASE |
KMS 오브젝트 집합을 리스로 생성, lessee fd 반환 | DRM Master (Lessor) |
DRM_IOCTL_MODE_LIST_LESSEES |
현재 활성 lessee 목록 조회 | DRM Master |
DRM_IOCTL_MODE_GET_LEASE |
리스에 포함된 오브젝트 ID 목록 조회 | Lessee |
DRM_IOCTL_MODE_REVOKE_LEASE |
리스 취소 (오브젝트 회수) | DRM Master |
/* DRM Lease 생성 (유저 공간, libdrm) */
uint32_t objects[] = {
crtc_id, /* 리스할 CRTC */
connector_id, /* 리스할 Connector */
plane_id, /* 리스할 Plane */
};
struct drm_mode_create_lease lease = {
.object_ids = (uint64_t)(uintptr_t)objects,
.object_count = 3,
.flags = O_CLOEXEC | O_NONBLOCK,
};
drmIoctl(master_fd, DRM_IOCTL_MODE_CREATE_LEASE, &lease);
/* lease.lessee_id = 리스 ID */
/* lease.fd = lessee가 사용할 새 DRM fd */
/* Lessee는 lease.fd를 통해 리스된 오브젝트만 접근 가능 */
/* → 독립적인 atomic commit, 페이지 플립 수행 */
wp_drm_lease_device_v1 프로토콜로 compositor가 lease를 제공합니다.
Content Protection (HDCP)
HDCP(High-bandwidth Digital Content Protection)는 디스플레이 출력의 콘텐츠를 암호화하여 무단 복제를 방지하는 DRM(Digital Rights Management) 기술입니다. Linux DRM 서브시스템은 KMS property를 통해 HDCP를 지원합니다.
| HDCP 버전 | 대역폭 | 사용 인터페이스 | 특징 |
|---|---|---|---|
| HDCP 1.4 | TMDS (HDMI 1.x/DP) | HDMI, DP, DVI | 기본 암호화, 리피터 인증 |
| HDCP 2.2 | HDMI 2.0+ | HDMI 2.x | 향상된 암호화(AES-128), LC 검증 |
| HDCP 2.3 | HDMI 2.1/DP 2.x | HDMI 2.1, DP 2.x | 최신 보안 개선 |
/* HDCP KMS Properties (Connector) */
/* "Content Protection" property */
/* 값: Undesired (0) → Desired (1) → Enabled (2) */
/* 유저가 Desired로 설정하면 드라이버가 HDCP 인증 시작 */
/* 성공 시 Enabled로 전환 (읽기 전용) */
/* "HDCP Content Type" property */
/* 값: Type 0 — HDCP 1.4+ 허용 */
/* Type 1 — HDCP 2.2+ 필수 (4K UHD 콘텐츠) */
/* Connector에 HDCP property 등록 (드라이버) */
drm_connector_attach_content_protection_property(connector, true);
/* true = HDCP Content Type property도 함께 등록 */
/* HDCP 인증 흐름 (드라이버 내부) */
/* 1. 유저가 "Content Protection" = Desired 설정 */
/* 2. atomic_check에서 HDCP 요청 감지 */
/* 3. 드라이버가 수신기(모니터)와 HDCP 핸드셰이크 */
/* - AKE_Init / AKE_Send_Cert / AKE_Send_Km ... */
/* 4. 인증 성공 → "Content Protection" = Enabled */
/* 5. 링크 실패/핫플러그 → "Content Protection" = Desired */
/* → 유저 공간에 uevent 통보 → 재인증 시도 */
디스플레이 연결 프로토콜
| 프로토콜 | DRM Connector 타입 | 최대 대역폭 | 특징 |
|---|---|---|---|
| HDMI 2.1 | DRM_MODE_CONNECTOR_HDMIA/B |
48 Gbps | ARC/eARC, VRR, DSC, 4K@120Hz |
| DisplayPort 2.1 | DRM_MODE_CONNECTOR_DisplayPort |
80 Gbps (UHBR20) | MST (데이지체인), DSC, Adaptive-Sync |
| eDP | DRM_MODE_CONNECTOR_eDP |
DP 기반 | 내장 디스플레이 (노트북), PSR (Panel Self Refresh) |
| DSI (MIPI) | DRM_MODE_CONNECTOR_DSI |
레인당 ~4.5 Gbps | 모바일/임베디드 디스플레이, 커맨드/비디오 모드 |
| DPI (RGB) | DRM_MODE_CONNECTOR_DPI |
병렬 전송 | 패럴렐 RGB, 임베디드 LCD 직결 |
| LVDS | DRM_MODE_CONNECTOR_LVDS |
채널당 ~112 MHz | 레거시 노트북/산업용 패널 |
| VGA | DRM_MODE_CONNECTOR_VGA |
~400 MHz (아날로그) | 레거시 아날로그, DAC 필요 |
| USB-C (DP Alt) | DRM_MODE_CONNECTOR_USB |
DP 기반 | USB Type-C를 통한 DP 출력, typec_mux 드라이버 |
DisplayPort MST (Multi-Stream Transport)
DP MST는 하나의 DP 출력에서 여러 디스플레이를 데이지 체인으로 연결하거나 MST 허브를 통해 분기하는 기술입니다.
커널은 drm_dp_mst_topology_mgr로 MST 토폴로지를 관리합니다.
| 구성 요소 | 커널 구조체 | 역할 |
|---|---|---|
| Topology Manager | drm_dp_mst_topology_mgr |
MST 허브/브랜치 장치 탐색, 대역폭 할당, 핫플러그 처리 |
| MST Port | drm_dp_mst_port |
브랜치 장치의 각 출력 포트 (downstream connector) |
| Payload | drm_dp_mst_atomic_payload |
각 스트림의 대역폭 할당 (시간 슬롯) |
| VCPI (Virtual Channel Payload ID) | Atomic state 내장 | 가상 채널별 대역폭 크기 (시간 슬롯 수) |
/* MST 토폴로지 매니저 초기화 (드라이버) */
drm_dp_mst_topology_mgr_init(&my->mst_mgr, &my->aux,
16, /* 최대 페이로드 수 */
4, /* 최대 레인 수 */
conn_base_id);
/* MST 허브 감지 시 (HPD IRQ에서) */
drm_dp_mst_hpd_irq_handle_event(&my->mst_mgr, esi);
/* → 토폴로지 변경 시 connector 추가/제거 이벤트 */
/* MST 대역폭 계산 예시 */
/* DP 1.4 HBR3: 8.1 Gbps/레인 × 4 레인 = 32.4 Gbps 총 대역폭 */
/* 4K@60Hz (XRGB8888): ~12.5 Gbps 필요 → 63개 시간 슬롯 중 ~25개 사용 */
/* 나머지 슬롯으로 2번째 모니터 (FHD@60Hz) 연결 가능 */
DSC (Display Stream Compression)
DSC는 VESA가 표준화한 시각적 무손실(visually lossless) 디스플레이 압축입니다.
고해상도(8K, 4K@120Hz+)에서 DP/HDMI 대역폭 한계를 극복하기 위해 사용됩니다.
Linux DRM에서는 drm_dsc_config로 DSC 파라미터를 관리합니다.
| 항목 | 설명 |
|---|---|
| 압축률 | 일반적으로 3:1 (24bpp → 8bpp), 최대 4:1 가능 |
| 슬라이스 | 프레임을 수평 슬라이스로 분할하여 병렬 압축/복원. 슬라이스 수는 모니터 DSC 능력에 의존 |
| 커널 헬퍼 | drm_dsc_compute_rc_parameters()로 Rate Control 파라미터 자동 계산 |
| 협상 | Source(GPU)와 Sink(모니터) 양쪽이 DSC 지원해야 사용 가능. DPCD/EDID에서 DSC 능력 파싱 |
| 활용 예 | 8K@60Hz (48 Gbps 필요 → HDMI 2.1 48 Gbps에 DSC 적용), DP MST에서 대역폭 절감 |
/* DSC 설정 예시 (드라이버 atomic_check에서) */
struct drm_dsc_config dsc_cfg = {};
dsc_cfg.line_buf_depth = 13;
dsc_cfg.bits_per_component = 8;
dsc_cfg.bits_per_pixel = 128; /* 8 bpp × 16 (4비트 소수부) */
dsc_cfg.slice_width = 1920; /* 슬라이스 너비 */
dsc_cfg.slice_height = 108; /* 슬라이스 높이 */
dsc_cfg.pic_width = 3840;
dsc_cfg.pic_height = 2160;
/* RC (Rate Control) 파라미터 자동 계산 */
drm_dsc_compute_rc_parameters(&dsc_cfg);
/* PPS (Picture Parameter Set) 생성 → DP SDP 또는 HDMI infoframe으로 전송 */
drm_dsc_pps_payload_pack(pps_payload, &dsc_cfg);
DRM_MODE_CONNECTOR_WRITEBACK 타입의 가상 커넥터를 지원합니다.
물리 디스플레이 대신 CRTC의 합성 결과를 GEM 버퍼에 기록합니다. 스크린 캡처, 녹화, 가상 디스플레이에 사용되며,
drm_writeback_connector_init()으로 초기화합니다. Atomic commit에서 WRITEBACK_FB_ID와
WRITEBACK_OUT_FENCE_PTR property로 출력 버퍼와 완료 fence를 지정합니다.
fbdev 에뮬레이션
DRM/KMS는 /dev/fb0 (fbdev) 인터페이스를 에뮬레이션하여 레거시 애플리케이션과 콘솔 출력을 지원합니다.
커널 콘솔(fbcon), Plymouth(부팅 스플래시), 일부 임베디드 UI 프레임워크가 fbdev에 의존합니다.
| fbdev 헬퍼 | 메모리 관리 | 사용 드라이버 |
|---|---|---|
drm_fbdev_shmem |
GEM shmem 기반 | panfrost, lima, vc4, simpledrm, virtio-gpu |
drm_fbdev_ttm |
TTM 기반 (VRAM) | amdgpu, nouveau, radeon |
drm_fbdev_dma |
DMA/CMA 기반 | meson, sun4i, rockchip |
| 드라이버 자체 구현 | 드라이버별 | i915 (커스텀 fbdev) |
CONFIG_FRAMEBUFFER_CONSOLE=y 설정 시, DRM 드라이버가 로드되면 자동으로 fbcon이 해당 fbdev에 연결됩니다.
이를 통해 커널 패닉 메시지, systemd 부팅 로그 등이 물리 디스플레이에 출력됩니다.
fbcon=map:0 커널 파라미터로 fbcon이 사용할 fb 디바이스를 지정할 수 있습니다.
GPU 컴퓨트 (GPGPU)
GPU는 렌더링 외에도 대규모 병렬 연산(GPGPU)에 사용됩니다.
Linux 커널은 render node(/dev/dri/renderD128)를 통해 비특권 GPU 컴퓨트 접근을 제공하며,
AMD는 KFD(Kernel Fusion Driver)로 HSA 컴퓨트를 추가 지원합니다.
| 프레임워크 | 커널 인터페이스 | 유저 공간 | GPU |
|---|---|---|---|
| OpenCL (Mesa) | DRM render node | Mesa Clover/Rusticl | AMD, Intel, 일부 ARM |
| ROCm (AMD) | KFD (/dev/kfd) |
ROCm runtime, HIP | AMD GCN/RDNA/CDNA |
| oneAPI (Intel) | DRM render node (xe/i915) | Level Zero, SYCL | Intel Xe/Arc |
| Vulkan Compute | DRM render node | Vulkan compute shader | 모든 Vulkan 지원 GPU |
| CUDA (NVIDIA) | nvidia.ko (독점) | CUDA runtime | NVIDIA (nouveau 미지원) |
KFD (Kernel Fusion Driver) — AMD HSA
SVM (Shared Virtual Memory)
SVM은 CPU와 GPU가 동일한 가상 주소 공간을 공유하는 기술입니다.
malloc()으로 할당한 메모리를 GPU가 동일한 포인터로 접근할 수 있습니다.
/* 커널 SVM 지원 (amdgpu/KFD) */
#include <linux/hmm.h>
/* GPU 페이지 폴트 핸들러 */
/* 1. GPU가 매핑되지 않은 주소 접근 → 인터럽트 */
/* 2. 커널이 CPU 페이지 테이블에서 물리 주소 조회 */
/* (페이지 없으면 CPU 페이지 폴트도 처리) */
/* 3. GPU 페이지 테이블에 매핑 추가 */
/* 4. GPU 작업 재개 */
/dev/dri/renderD128은 DRM Master 없이 접근 가능하므로, 일반 사용자도 GPU 컴퓨트를 사용할 수 있습니다.
그러나 GPU 가상 메모리 격리가 제대로 구현되어야 다른 프로세스의 GPU 데이터가 유출되지 않습니다.
per-process GPU page table과 command validation이 보안의 핵심입니다.
# GPU 컴퓨트 관련 확인 명령
# Render node 확인
ls -la /dev/dri/renderD*
# KFD 디바이스 확인 (AMD)
ls -la /dev/kfd
# GPU 토폴로지 (AMD ROCm)
cat /sys/class/kfd/kfd/topology/nodes/0/properties
# GPU 메모리 사용량 (amdgpu)
cat /sys/class/drm/card0/device/mem_info_vram_used
cat /sys/class/drm/card0/device/mem_info_gtt_used
# clinfo (OpenCL 디바이스 정보)
clinfo
# vulkaninfo (Vulkan 컴퓨트 능력)
vulkaninfo --summary
DRM Accel 서브시스템 (AI/NPU 가속기)
최근 커널은 AI/ML/NPU 같은 비그래픽 가속기를 위해 DRM Accel 서브시스템을 제공합니다.
핵심 아이디어는 GPU DRM이 이미 갖고 있는 파일 디스크립터별 세션 상태, 버퍼 객체, 동기화, ioctl 디스패치를 재사용하되,
디스플레이와 렌더 노드 개념은 제거하고 /dev/accel/accel0 같은 전용 노드에 compute UAPI만 싣는 것입니다.
| 항목 | DRM GPU | DRM Accel |
|---|---|---|
| 디바이스 노드 | /dev/dri/card0, /dev/dri/renderD128 |
/dev/accel/accel0 |
| 용도 | 그래픽 렌더링 + 디스플레이 + GPGPU | AI 추론/훈련, 신호 처리 등 비그래픽 가속 |
| KMS | 지원 (디스플레이 파이프라인) | 미지원 (컴퓨트 전용) |
| GEM/DMA-BUF | 지원 | 지원 (GPU와 버퍼 공유 가능) |
| 권한 모델 | primary는 특권, render는 비특권 | display 관련 권한 없이 compute job과 buffer mapping만 노출 |
| 드라이버 예시 | amdgpu, i915, xe, panfrost | amdxdna, qaic, rocket 같은 전용 accel 드라이버 계열 |
| 활성화 플래그 | DRIVER_RENDER |
DRIVER_COMPUTE_ACCEL |
/* DRM Accel 드라이버 등록 (최소 골격) */
static const struct drm_driver my_accel_driver = {
.driver_features = DRIVER_GEM | DRIVER_COMPUTE_ACCEL,
.fops = &my_accel_fops,
.ioctls = my_accel_ioctls,
.num_ioctls = ARRAY_SIZE(my_accel_ioctls),
.name = "my-npu",
.desc = "My NPU Accelerator",
.date = "20260101",
.major = 1, .minor = 0,
};
/* drm_dev_register() 시 /dev/accel/accel0 자동 생성 */
/* (DRIVER_COMPUTE_ACCEL 플래그에 의해 accel 네임스페이스 사용) */
DRIVER_COMPUTE_ACCEL 제약: 최신 drm_drv.h 기준으로 이 플래그는
DRIVER_RENDER, DRIVER_MODESET와 상호 배타적입니다.
즉, 하나의 디바이스가 그래픽과 compute를 모두 지원하더라도 UAPI 계약상 “한 드라이버가 render node와 accel node를 동시에 제공”하는 방식은 권장되지 않으며,
메인라인 문서는 보조 버스(auxiliary bus)로 연결된 두 드라이버로 분리하는 설계를 권합니다.
CUDA / NVIDIA — GPU 컴퓨트
CUDA(Compute Unified Device Architecture)는 NVIDIA가 2006년에 도입한 GPU 범용 컴퓨팅 플랫폼으로,
GPU의 수천 개 코어를 C/C++ 확장 문법으로 프로그래밍할 수 있게 합니다.
Linux에서 CUDA는 독점 커널 모듈(nvidia.ko)과 사용자 공간 런타임(libcuda.so, libcudart.so)으로 구성되며,
딥러닝(cuDNN, TensorRT), 과학 계산(cuBLAS, cuFFT), 고성능 컴퓨팅(NCCL, GPUDirect RDMA) 생태계가 핵심 경쟁력입니다.
CUDA 프로그래밍 모델의 핵심은 이종 컴퓨팅(Heterogeneous Computing)입니다.
CPU(호스트)가 프로그램 흐름을 제어하고 GPU(디바이스)에 병렬 작업을 위임하는 구조로,
호스트 코드는 표준 C/C++ 컴파일러(gcc/clang)가, 디바이스 코드는 NVIDIA의 nvcc가 처리합니다.
CUDA Runtime API(libcudart.so)는 고수준 추상화를, Driver API(libcuda.so)는
컨텍스트·모듈·함수 수준의 세밀한 제어를 제공합니다.
대부분의 애플리케이션은 Runtime API를 사용하며, 멀티 GPU·JIT 컴파일 등 고급 시나리오에서 Driver API를 활용합니다.
NVIDIA GPU 아키텍처 진화
NVIDIA GPU 아키텍처는 2006년 Tesla(CUDA 최초 지원)부터 시작하여 세대마다 SM(Streaming Multiprocessor) 구조,
메모리 계층, 인터커넥트, 전용 하드웨어 유닛을 혁신해 왔습니다.
각 세대의 Compute Capability는 지원하는 CUDA 기능 집합을 결정하며,
nvcc의 -arch=sm_XX 옵션으로 대상 아키텍처를 지정합니다.
| 아키텍처 | CC | 대표 GPU | CUDA 코어 | Tensor Core | 메모리 | NVLink | 핵심 혁신 |
|---|---|---|---|---|---|---|---|
| Tesla | 1.0 | G80 | 128 | — | 768MB GDDR3 | — | CUDA 최초 도입 |
| Fermi | 2.0 | GF100 | 512 | — | 6GB GDDR5 | — | L1/L2 캐시, ECC |
| Kepler | 3.5 | GK110 | 2880 | — | 12GB GDDR5 | — | Dynamic Parallelism |
| Maxwell | 5.2 | GM200 | 3072 | — | 12GB GDDR5 | — | 에너지 효율 2× |
| Pascal | 6.0 | GP100 | 3840 | — | 16GB HBM2 | 1.0 (160GB/s) | NVLink, FP16 |
| Volta | 7.0 | V100 | 5120 | 640 (1세대) | 32GB HBM2 | 2.0 (300GB/s) | Tensor Core 도입 |
| Turing | 7.5 | T4 | 2560 | 320 (2세대) | 16GB GDDR6 | — | RT Core, INT8/INT4 |
| Ampere | 8.0 | A100 | 6912 | 432 (3세대) | 80GB HBM2e | 3.0 (600GB/s) | TF32, MIG, 희소성 2:4 |
| Hopper | 9.0 | H100 | 16896 | 528 (4세대) | 80GB HBM3 | 4.0 (900GB/s) | FP8, TMA, DPX |
| Blackwell | 10.0 | B200 | 18432 | 576 (5세대) | 192GB HBM3e | 5.0 (1800GB/s) | FP4, 2세대 TMA |
-arch 관계:
nvcc -arch=sm_80은 Ampere(CC 8.0) 대상으로 컴파일합니다.
CC가 높을수록 더 많은 명령어(FP8, TMA 등)와 하드웨어 기능을 사용할 수 있습니다.
현재 GPU의 CC는 nvidia-smi --query-gpu=compute_cap --format=csv,noheader로 확인합니다.
하위 호환성: sm_80 바이너리는 sm_90 GPU에서 실행 가능하지만,
sm_90 전용 기능(FP8 등)은 사용하지 못합니다. 반대로 sm_90 바이너리는 sm_80에서 실행 불가합니다.
NVIDIA Linux 드라이버 스택
NVIDIA GPU를 CUDA로 활용하려면 커널 모듈 + 사용자 라이브러리가 함께 설치되어야 합니다. 아래 다이어그램은 CUDA 애플리케이션에서 GPU 하드웨어까지의 전체 소프트웨어 스택을 보여줍니다.
| 디바이스 노드 | 제공 모듈 | 용도 |
|---|---|---|
/dev/nvidia0..N | nvidia.ko | GPU별 컨트롤 채널 (컴퓨트, 메모리 할당) |
/dev/nvidiactl | nvidia.ko | 전역 컨트롤 (디바이스 열거, 초기화) |
/dev/nvidia-uvm | nvidia-uvm.ko | Unified Virtual Memory 관리 |
/dev/nvidia-uvm-tools | nvidia-uvm.ko | UVM 프로파일링 / 디버깅 |
/dev/nvidia-modeset | nvidia-modeset.ko | 디스플레이 모드 설정 |
/dev/dri/card* | nvidia-drm.ko | DRM primary 노드 (Wayland/X11 연동) |
/dev/dri/renderD* | nvidia-drm.ko | DRM render 노드 (비특권 GPU 접근) |
# NVIDIA 커널 모듈 확인
lsmod | grep nvidia
# nvidia 61440000 5 nvidia_uvm,nvidia_modeset
# nvidia_uvm 3280896 0
# nvidia_modeset 1282048 1 nvidia_drm
# nvidia_drm 94208 3
# nvidia_peermem 16384 0
# 디바이스 노드 확인
ls -la /dev/nvidia*
# crw-rw-rw- 1 root root 195, 0 ... /dev/nvidia0
# crw-rw-rw- 1 root root 195, 255 ... /dev/nvidiactl
# crw-rw-rw- 1 root root 511, 0 ... /dev/nvidia-uvm
CUDA는 두 가지 API 레벨을 제공합니다. Runtime API(libcudart.so)는
cudaMalloc(), cudaMemcpy() 등 간결한 함수로 대부분의 사용 사례를 커버합니다.
Driver API(libcuda.so)는 cuCtxCreate(), cuModuleLoad() 등
더 세밀한 제어를 제공하며, PTX JIT 컴파일이나 멀티 컨텍스트 관리에 필수적입니다.
| 항목 | Runtime API (cudart) | Driver API (cuda) |
|---|---|---|
| 헤더 | cuda_runtime.h | cuda.h |
| 라이브러리 | libcudart.so | libcuda.so (드라이버와 함께 설치) |
| 초기화 | 암묵적 (첫 API 호출 시) | 명시적 (cuInit(0)) |
| 컨텍스트 | 기본 컨텍스트 자동 생성 | 수동 생성/파괴 (cuCtxCreate) |
| 커널 실행 | <<<...>>> 구문 | cuLaunchKernel() |
| PTX JIT 로드 | 불가 | cuModuleLoadDataEx() |
| 디바이스 관리 | cudaSetDevice() | cuDeviceGet() + cuCtxCreate() |
| 혼용 | 가능 — 동일 프로세스에서 두 API를 함께 사용할 수 있음 | |
nvidia-smi 출력 상단의 "CUDA Version"은 해당 드라이버가 지원하는 최대 CUDA 버전이며,
실제 설치된 Toolkit 버전과 다를 수 있습니다. Toolkit 버전은 nvcc --version으로 확인합니다.
Linux CUDA 설치 및 환경 설정
Linux에서 CUDA 환경을 구성하는 방법은 크게 세 가지입니다: 배포판 패키지 매니저(apt/dnf), NVIDIA CUDA 저장소(cuda-keyring), runfile 직접 설치. 프로덕션 환경에서는 NVIDIA 공식 저장소를 통한 설치가 버전 관리와 업데이트 측면에서 권장됩니다.
# === 방법 1: NVIDIA 공식 저장소 (Ubuntu/Debian) ===
# 저장소 키링 패키지 설치
wget https://developer.download.nvidia.com/compute/cuda/repos/ubuntu2204/x86_64/cuda-keyring_1.1-1_all.deb
sudo dpkg -i cuda-keyring_1.1-1_all.deb
sudo apt-get update
# CUDA Toolkit + 드라이버 설치 (메타 패키지)
sudo apt-get install cuda-toolkit-12-4
sudo apt-get install nvidia-open # 오픈 커널 모듈 (Turing+)
# 또는: sudo apt-get install cuda-drivers (독점 모듈)
# === 방법 2: RHEL/Rocky/AlmaLinux ===
sudo dnf config-manager --add-repo \
https://developer.download.nvidia.com/compute/cuda/repos/rhel9/x86_64/cuda-rhel9.repo
sudo dnf install cuda-toolkit-12-4 nvidia-open
# === 환경 변수 설정 (~/.bashrc) ===
export PATH=/usr/local/cuda-12.4/bin:$PATH
export LD_LIBRARY_PATH=/usr/local/cuda-12.4/lib64:$LD_LIBRARY_PATH
# === 설치 검증 ===
nvcc --version # CUDA 컴파일러 버전
nvidia-smi # 드라이버 및 GPU 상태
cuda-install-samples-12.4.sh ~/cuda-samples # 샘플 코드 설치
cd ~/cuda-samples/Samples/1_Utilities/deviceQuery
make && ./deviceQuery # GPU 정보 상세 출력
| 방법 | 장점 | 단점 | 적합한 환경 |
|---|---|---|---|
| NVIDIA 저장소 (cuda-keyring) | 자동 업데이트, 의존성 해결 | 시스템 전역 설치 | 프로덕션, CI/CD |
| runfile 직접 설치 | 설치 경로 지정, 다중 버전 공존 | 의존성 수동 관리 | 개발, HPC 클러스터 |
| Conda (conda-forge) | 가상환경 격리, 크로스 플랫폼 | 드라이버는 별도 설치 | 데이터 과학, ML |
| 컨테이너 (nvidia/cuda) | 완전 격리, 재현성 | nvidia-container-toolkit 필요 | 클라우드, K8s |
/usr/local/cuda는 심볼릭 링크이며
update-alternatives --config cuda로 활성 버전을 전환할 수 있습니다.
ls /usr/local/cuda-*/로 설치된 모든 버전을 확인하고,
프로젝트별로 PATH와 LD_LIBRARY_PATH를 조정하세요.
CUDA_HOME 환경 변수를 설정하면 CMake의 FindCUDA 모듈이 자동으로 인식합니다.
CUDA 프로그래밍 모델
CUDA는 SIMT(Single Instruction Multiple Thread) 실행 모델을 사용합니다.
프로그래머는 __global__ 함수(커널)를 정의하고, 호스트에서 <<<gridDim, blockDim>>> 구문으로 수천~수백만 스레드를 동시에 실행합니다.
| CUDA | OpenCL | 설명 |
|---|---|---|
| Grid | NDRange | 전체 문제 공간 (커널 1회 실행) |
| Block | Work-group | SM에 매핑, 공유 메모리/배리어 범위 |
| Thread | Work-item | 개별 실행 단위 |
| Warp (32) | Sub-group | SIMT 동시 실행 단위, 하드웨어 결정 |
__shared__ | __local | 블록/그룹 내 공유 메모리 |
__syncthreads() | barrier() | 블록/그룹 내 동기화 |
threadIdx.x | get_local_id(0) | 블록/그룹 내 인덱스 |
blockIdx.x | get_group_id(0) | 블록/그룹 ID |
/* 벡터 덧셈 — CUDA 커널 기본 예제 */
__global__ void vecAdd(const float *A, const float *B, float *C, int N) {
int i = blockIdx.x * blockDim.x + threadIdx.x;
if (i < N)
C[i] = A[i] + B[i];
}
int main(void) {
int N = 1 << 20; /* 1M 원소 */
float *d_A, *d_B, *d_C;
cudaMalloc(&d_A, N * sizeof(float));
cudaMalloc(&d_B, N * sizeof(float));
cudaMalloc(&d_C, N * sizeof(float));
/* 호스트→디바이스 전송 (생략) */
int threadsPerBlock = 256;
int blocksPerGrid = (N + threadsPerBlock - 1) / threadsPerBlock;
vecAdd<<<blocksPerGrid, threadsPerBlock>>>(d_A, d_B, d_C, N);
cudaDeviceSynchronize();
/* 디바이스→호스트 전송 (생략) */
cudaFree(d_A); cudaFree(d_B); cudaFree(d_C);
return 0;
}
CUDA 커널 실행의 핵심 개념을 정리하면 다음과 같습니다:
| 개념 | 설명 | 제약 조건 |
|---|---|---|
__global__ | 호스트에서 호출, 디바이스에서 실행되는 커널 함수 | 반환형 void, 재귀 불가(CC < 3.5), 가변 인자 불가 |
__device__ | 디바이스에서만 호출/실행되는 함수 | 호스트에서 직접 호출 불가 |
__host__ | 호스트에서만 호출/실행 (기본값) | __host__ __device__ 조합으로 양쪽 컴파일 가능 |
blockDim | 블록당 스레드 수 (1D/2D/3D) | 최대 1024 스레드/블록 (SM 아키텍처별 상이) |
gridDim | 그리드당 블록 수 (1D/2D/3D) | 최대 2³¹-1 × 65535 × 65535 |
__syncthreads() | 블록 내 모든 스레드 배리어 | 조건 분기 내에서 호출 시 데드락 위험 |
| Cooperative Groups | 워프/블록/그리드/멀티 GPU 수준 동기화 | CC 6.0+, 그리드 동기화는 cudaLaunchCooperativeKernel |
| Dynamic Parallelism | 커널 내에서 새 커널 실행 | CC 3.5+, 중첩 깊이 24, 동기화 오버헤드 있음 |
/* Cooperative Groups — 워프 수준 리덕션 (CC 7.0+) */
#include <cooperative_groups.h>
#include <cooperative_groups/reduce.h>
namespace cg = cooperative_groups;
__global__ void warpReduce(const float *input, float *output, int N) {
cg::thread_block block = cg::this_thread_block();
cg::thread_block_tile<32> warp = cg::tiled_partition<32>(block);
int idx = blockIdx.x * blockDim.x + threadIdx.x;
float val = (idx < N) ? input[idx] : 0.0f;
/* 워프 내 셔플 리덕션 — 레지스터 수준, 공유메모리 불필요 */
float sum = cg::reduce(warp, val, cg::plus<float>());
if (warp.thread_rank() == 0)
atomicAdd(output, sum);
}
워프 실행과 분기 다이버전스
GPU의 SIMT(Single Instruction, Multiple Thread) 실행 모델에서 32개 스레드로 구성된 워프(Warp)는 동일한 프로그램 카운터(PC)를 공유합니다. 워프 내 모든 스레드가 같은 분기 경로를 따르면 최대 효율이지만, 서로 다른 경로를 택하면 분기 다이버전스(Branch Divergence)가 발생하여 각 경로를 순차적으로 실행해야 합니다.
분기 다이버전스를 최소화하는 것은 CUDA 최적화의 기본입니다. 워프 내 스레드들이 서로 다른 경로를 따를 때 SM의 실행 유닛 활용률이 떨어지며, 최악의 경우(32개 스레드 모두 다른 경로) 성능이 1/32로 저하될 수 있습니다.
| 전략 | 설명 | 예제 |
|---|---|---|
| 워프 정렬 분기 | 조건을 워프 경계(32 배수)로 정렬 | if (threadIdx.x / 32 < threshold) |
| 프레디케이션 | 짧은 분기는 컴파일러가 predicated 명령으로 변환 | val = (cond) ? a : b; (2~3 명령어) |
| 데이터 재배치 | 분기 패턴이 같은 데이터를 워프 단위로 그룹 | CSR 행렬의 행 길이별 정렬 |
| 셔플 기반 리덕션 | 조건 분기 대신 __shfl_down_sync() | 워프 리덕션, 프리픽스 합 |
| 선택 함수 | __any_sync(), __all_sync() 투표 | 워프 전체가 특정 조건을 만족하는지 확인 |
/* 워프 셔플 리덕션 — 분기 없이 워프 합 계산 */
__device__ float warpReduceSum(float val) {
for (int offset = warpSize / 2; offset > 0; offset /= 2)
val += __shfl_down_sync(0xFFFFFFFF, val, offset);
return val; /* lane 0에 합 결과 */
}
/* 워프 투표 함수 활용 */
__global__ void earlyExit(const int *data, int *result) {
int val = data[blockIdx.x * blockDim.x + threadIdx.x];
/* 워프 전체가 0이면 조기 종료 — 분기 다이버전스 없음 */
if (__all_sync(0xFFFFFFFF, val == 0))
return;
/* ... 실제 연산 ... */
}
__syncwarp(mask)로 명시적 재수렴을 보장하고,
__shfl_sync(mask, ...)에서 항상 유효한 마스크를 전달하세요.
CUDA 스트림과 비동기 실행
CUDA 스트림(Stream)은 순서가 보장되는 GPU 명령 큐입니다. 서로 다른 스트림의 명령은 하드웨어가 허용하는 한 동시에 실행될 수 있으며, 이를 통해 커널 실행, 메모리 전송, 호스트 연산을 오버랩하여 GPU 파이프라인 활용률을 극대화합니다.
/* 3-스트림 파이프라인 — 전송과 커널 오버랩 */
const int nStreams = 3;
cudaStream_t streams[nStreams];
for (int i = 0; i < nStreams; i++)
cudaStreamCreate(&streams[i]);
int chunkSize = N / nStreams;
for (int i = 0; i < nStreams; i++) {
int offset = i * chunkSize;
/* 비동기 H→D 전송 (핀드 메모리 필수) */
cudaMemcpyAsync(d_in + offset, h_in + offset,
chunkSize * sizeof(float), cudaMemcpyHostToDevice, streams[i]);
/* 커널 실행 — 같은 스트림이므로 전송 완료 후 자동 실행 */
myKernel<<<chunkSize/256, 256, 0, streams[i]>>>(d_in + offset, d_out + offset);
/* 비동기 D→H 전송 */
cudaMemcpyAsync(h_out + offset, d_out + offset,
chunkSize * sizeof(float), cudaMemcpyDeviceToHost, streams[i]);
}
/* 모든 스트림 완료 대기 */
cudaDeviceSynchronize();
/* 이벤트로 경과 시간 측정 */
cudaEvent_t start, stop;
cudaEventCreate(&start); cudaEventCreate(&stop);
cudaEventRecord(start, streams[0]);
myKernel<<<grid, block, 0, streams[0]>>>(d_in, d_out);
cudaEventRecord(stop, streams[0]);
cudaEventSynchronize(stop);
float ms;
cudaEventElapsedTime(&ms, start, stop);
/* ms = 커널 실행 시간 (밀리초) */
| 메커니즘 | 범위 | 동기화 | 사용 사례 |
|---|---|---|---|
| 기본 스트림 (stream 0) | 디바이스 전역 | 암묵적 직렬화 | 단순 순차 실행 |
| 비기본 스트림 | 스트림 단위 | 스트림 내 순서 보장 | 파이프라인, 다중 커널 |
| CUDA 이벤트 | 스트림 간 | cudaStreamWaitEvent() | 스트림 간 의존성, 타이밍 |
| CUDA 그래프 | 전체 워크플로 | 그래프 구조로 정의 | 반복 실행 최적화, 실행 오버헤드 최소화 |
| 동적 병렬처리 | 커널 내부 | 커널 내 cudaDeviceSynchronize() | 적응적 알고리즘, 재귀 분할 |
/* CUDA 그래프 — 반복 실행 워크플로 최적화 (CC 7.0+) */
cudaGraph_t graph;
cudaGraphExec_t graphExec;
/* 1. 스트림 캡처로 그래프 기록 */
cudaStreamBeginCapture(stream, cudaStreamCaptureModeGlobal);
cudaMemcpyAsync(d_in, h_in, size, cudaMemcpyHostToDevice, stream);
myKernel<<<grid, block, 0, stream>>>(d_in, d_out);
cudaMemcpyAsync(h_out, d_out, size, cudaMemcpyDeviceToHost, stream);
cudaStreamEndCapture(stream, &graph);
/* 2. 그래프 인스턴스화 (1회) */
cudaGraphInstantiate(&graphExec, graph, 0);
/* 3. 반복 실행 — 실행 오버헤드 대폭 감소 */
for (int iter = 0; iter < 1000; iter++)
cudaGraphLaunch(graphExec, stream);
cudaGraphExecDestroy(graphExec);
cudaGraphDestroy(graph);
cudaGraphExecUpdate()로
재인스턴스화 없이 갱신할 수 있습니다.
CUDA 메모리 계층
GPU 성능 최적화의 핵심은 메모리 계층을 이해하고 활용하는 것입니다. CUDA 메모리는 크게 레지스터 → 공유 메모리(Shared) → L1/L2 캐시 → 글로벌 메모리(VRAM) 순으로 용량이 커지고 지연 시간이 증가합니다.
| 메모리 | 선언 | 범위 | 수명 | 지연 | Hopper (H100) 기준 용량 |
|---|---|---|---|---|---|
| 레지스터 | 자동 변수 | 스레드 | 스레드 | ~1 사이클 | SM당 256 KB (65536 × 32b) |
| 로컬 | 자동 (스필) | 스레드 | 스레드 | L1/L2 캐시 | 글로벌에 배치 |
| 공유 | __shared__ | 블록 | 블록 | ~5 사이클 | SM당 최대 228 KB |
| 상수 | __constant__ | 그리드 | 호스트 할당 | 캐시 히트 ~4 사이클 | 64 KB (전용 캐시) |
| 글로벌 | cudaMalloc | 그리드+호스트 | 호스트 할당 | ~400 사이클 | 80 GB HBM3 |
/* 공유 메모리 타일링 — 행렬 곱셈 최적화 */
#define TILE 16
__global__ void matMul(const float *A, const float *B, float *C, int N) {
__shared__ float sA[TILE][TILE], sB[TILE][TILE];
int row = blockIdx.y * TILE + threadIdx.y;
int col = blockIdx.x * TILE + threadIdx.x;
float sum = 0.0f;
for (int t = 0; t < N / TILE; t++) {
sA[threadIdx.y][threadIdx.x] = A[row * N + t * TILE + threadIdx.x];
sB[threadIdx.y][threadIdx.x] = B[(t * TILE + threadIdx.y) * N + col];
__syncthreads(); /* 블록 내 동기화 */
for (int k = 0; k < TILE; k++)
sum += sA[threadIdx.y][k] * sB[k][threadIdx.x];
__syncthreads();
}
C[row * N + col] = sum;
}
nvidia-smi dmon의 mem_util이 낮다면
접근 패턴 최적화를 먼저 확인하세요.
| 접근 패턴 | 트랜잭션 수 | 대역폭 활용 | 설명 |
|---|---|---|---|
| 연속 정렬 (Aligned Coalesced) | 1× (128B) | 100% | T0→addr[0], T1→addr[1], ..., T31→addr[31] |
| 연속 비정렬 (Misaligned) | 2× | ~50% | 시작 주소가 128B 경계에 비정렬 |
| 스트라이드 (Strided) | 최대 32× | ~3% | T0→addr[0], T1→addr[stride], ... (열 우선 접근) |
| 랜덤 (Scattered) | 최대 32× | ~3% | 각 스레드가 무관한 주소 접근 |
/* 공유 메모리 뱅크 충돌 회피 — 패딩 기법 */
/* 뱅크 충돌: 같은 뱅크를 동시 접근하면 순차 처리 */
/* 32개 뱅크, 4B 인터리빙: bank = (addr / 4) % 32 */
/* ❌ 뱅크 충돌 발생 — 열 접근 */
__shared__ float tile[32][32]; /* tile[0][0], tile[1][0]은 같은 뱅크 */
float val = tile[threadIdx.x][0]; /* 32-way 뱅크 충돌! */
/* ✅ 패딩으로 해결 */
__shared__ float tile[32][33]; /* +1 패딩 → 뱅크 오프셋 이동 */
float val = tile[threadIdx.x][0]; /* 충돌 없음 */
/* ✅ swizzle 기법 (CUTLASS 스타일) */
int swizzled_col = col ^ (row & 0x1F);
float val = tile[row][swizzled_col];
UVM (Unified Virtual Memory) 심층 분석
Unified Virtual Memory(UVM)는 CPU와 GPU가 동일한 가상 주소 공간을 공유하는 메커니즘입니다.
nvidia-uvm.ko 커널 모듈이 페이지 폴트 기반 마이그레이션을 처리하여,
프로그래머가 명시적 cudaMemcpy() 없이도 양쪽에서 데이터에 접근할 수 있습니다.
| API | 설명 | 성능 힌트 |
|---|---|---|
cudaMallocManaged() | 통합 메모리 할당 | 초기: CPU 상주, GPU 접근 시 마이그레이션 |
cudaMemPrefetchAsync() | 선제적 페이지 마이그레이션 | 폴트 오버헤드 제거, 대량 전송 최적화 |
cudaMemAdvise() | 접근 패턴 힌트 제공 | ReadMostly: 양쪽 복제본 유지, 무효화 최소화 |
cudaMemAdvise(SetPreferredLocation) | 기본 상주 디바이스 지정 | 마이그레이션 대신 원격 매핑(Access Counter 기반) |
cudaMemAdvise(SetAccessedBy) | 접근 디바이스 알림 | 직접 매핑 생성으로 폴트 회피 |
/* UVM 최적화 패턴 — 프리페치 + 힌트 */
float *data;
cudaMallocManaged(&data, size);
/* CPU에서 데이터 초기화 */
initDataOnCPU(data, N);
/* GPU로 선제적 마이그레이션 (폴트 없이 전송) */
cudaMemPrefetchAsync(data, size, deviceId, stream);
/* 읽기 전용 데이터: 양쪽에 복제본 유지 */
cudaMemAdvise(readOnlyData, size, cudaMemAdviseSetReadMostly, deviceId);
/* GPU 커널 실행 */
myKernel<<<grid, block, 0, stream>>>(data);
/* CPU로 다시 마이그레이션 */
cudaMemPrefetchAsync(data, size, cudaCpuDeviceId, stream);
cudaStreamSynchronize(stream);
processOnCPU(data, N);
cudaMemPrefetchAsync()로 명시적 프리페치하거나,
접근 패턴이 명확한 경우 cudaMalloc() + cudaMemcpy()가 더 효율적입니다.
HMM(Heterogeneous Memory Management) 통합 시 nvidia-uvm.ko는
Linux 커널의 hmm_range_fault()와 연동하여 시스템 통합 메모리 관리를 구현합니다.
자세한 내용은 HMM 페이지를 참조하세요.
Tensor Core 연산
Tensor Core는 NVIDIA GPU에 내장된 행렬 연산 전용 하드웨어 유닛으로,
Volta(V100) 세대에서 처음 도입되었습니다. 단일 사이클에 작은 행렬의
FMA(Fused Multiply-Add) 연산 D = A × B + C를 수행하며,
일반 CUDA 코어 대비 4~16× 높은 연산 처리량을 달성합니다.
| 정밀도 | 입력→출력 | Volta | Ampere | Hopper | Blackwell | 주요 사용처 |
|---|---|---|---|---|---|---|
| FP16 | FP16→FP32 | 125 T | 312 T | 989 T | 2250 T | 딥러닝 학습/추론 |
| BF16 | BF16→FP32 | — | 312 T | 989 T | 2250 T | LLM 학습 (높은 동적 범위) |
| TF32 | TF32→FP32 | — | 156 T | 495 T | 1125 T | FP32 드롭인 대체 (cuBLAS 자동) |
| FP8 (E4M3) | FP8→FP16/32 | — | — | 1979 T | 4500 T | LLM 추론, 양자화 학습 |
| FP4 | FP4→FP16/32 | — | — | — | 9000 T | 초저정밀도 추론 |
| INT8 | INT8→INT32 | — | 624 T | 1979 T | 4500 T | INT8 양자화 추론 |
| 2:4 희소 | 구조적 희소 × 입력 | — | 2× 위 수치 | 2× 위 수치 | 2× 위 수치 | 프루닝된 모델 가속 |
/* WMMA API — Tensor Core 프로그래밍 (CC 7.0+) */
#include <mma.h>
using namespace nvcuda::wmma;
__global__ void tensorGemm(const half *A, const half *B, float *C) {
/* 16×16×16 타일 단위 MMA */
fragment<matrix_a, 16, 16, 16, half, row_major> a_frag;
fragment<matrix_b, 16, 16, 16, half, col_major> b_frag;
fragment<accumulator, 16, 16, 16, float> c_frag;
fill_fragment(c_frag, 0.0f);
int warpM = (blockIdx.x * blockDim.x + threadIdx.x) / warpSize;
int warpN = blockIdx.y;
/* A와 B 타일 로드 */
load_matrix_sync(a_frag, A + warpM * 16 * K, K);
load_matrix_sync(b_frag, B + warpN * 16, N);
/* Tensor Core MMA: D = A × B + C */
mma_sync(c_frag, a_frag, b_frag, c_frag);
/* 결과 저장 */
store_matrix_sync(C + warpM * 16 * N + warpN * 16, c_frag, N, mem_row_major);
}
cublasSgemm()(FP32 GEMM)을 호출하면,
cuBLAS가 자동으로 TF32 Tensor Core를 활용합니다. TF32는 FP32와 동일한 지수 범위(8비트)에
축소된 가수(10비트)를 사용하여, FP32 정밀도에 근접하면서 Tensor Core 속도를 얻습니다.
비활성화: cublasSetMathMode(handle, CUBLAS_DEFAULT_MATH).
구조적 희소성 2:4: 4개 원소 중 2개가 0인 패턴에서 Tensor Core가 자동으로 2× 가속합니다.
cusparseLt 라이브러리나 PyTorch의 to_sparse_semi_structured()로 활용합니다.
오큐펀시 최적화
오큐펀시(Occupancy)는 SM이 동시에 유지할 수 있는 활성 워프 수 대비 실제 실행 중인 워프의 비율입니다. 높은 오큐펀시는 메모리 지연 시간을 워프 스위칭으로 효과적으로 숨길 수 있게 합니다. 오큐펀시를 결정하는 3대 제약 요소는 레지스터 사용량, 공유 메모리 사용량, 블록당 스레드 수입니다.
/* 오큐펀시 최적화 — 최적 블록 크기 자동 결정 */
int minGridSize, blockSize;
cudaOccupancyMaxPotentialBlockSize(
&minGridSize, &blockSize,
myKernel, /* 대상 커널 */
0, /* 동적 공유메모리 크기 */
0 /* 블록 크기 제한 (0 = 제한 없음) */
);
/* blockSize = SM 리소스를 최대 활용하는 블록 크기 */
int gridSize = (N + blockSize - 1) / blockSize;
myKernel<<<gridSize, blockSize>>>(data, N);
/* 커널 레지스터 제한 — 스필과 오큐펀시 트레이드오프 */
__global__ void
__launch_bounds__(256, 8) /* 최대 256 스레드/블록, 최소 8 블록/SM */
myOptimizedKernel(float *data) {
/* 컴파일러가 레지스터를 256*8 블록에 맞게 할당 */
/* → 레지스터/스레드 = 65536 / (256*8) = 32 */
}
ncu)의 "Occupancy" 섹션에서 실측 오큐펀시와
병목 요인(레지스터/공유메모리/블록 수)을 확인하고, 성능 프로파일링 결과에 기반하여 조정하세요.
NVIDIA 커널 모듈 상세
NVIDIA 독점 드라이버는 5개의 커널 모듈로 구성됩니다. 각 모듈은 역할이 명확히 분리되어 있으며,
sysfs와 procfs를 통해 런타임 상태를 조회하거나 파라미터를 변경할 수 있습니다.
| 모듈 | 역할 | 주요 인터페이스 |
|---|---|---|
nvidia.ko |
GPU 하드웨어 제어 핵심 (MMIO, 인터럽트, DMA, 전원 관리) | /dev/nvidia0..N, /dev/nvidiactl |
nvidia-modeset.ko |
디스플레이 엔진 제어 (모드 설정, HDMI/DP 출력) | /dev/nvidia-modeset |
nvidia-uvm.ko |
Unified Virtual Memory — CPU↔GPU 페이지 마이그레이션, 폴트 처리 | /dev/nvidia-uvm, /dev/nvidia-uvm-tools |
nvidia-drm.ko |
DRM/KMS 브릿지 — Wayland/X11 compositing, GBM 버퍼 할당 | /dev/dri/card*, /dev/dri/renderD* |
nvidia-peermem.ko |
GPUDirect RDMA — InfiniBand/RoCE NIC↔GPU 직접 DMA | peer_memory_client 커널 API |
# 주요 모듈 파라미터 확인
cat /proc/driver/nvidia/params
# NVreg_EnablePCIeGen3=1
# NVreg_MemoryPoolSize=256 (MB, nvidia-uvm 내부 풀)
# NVreg_PreserveVideoMemoryAllocations=0
# GPU 정보 조회
cat /proc/driver/nvidia/gpus/0000:01:00.0/information
# Model: NVIDIA H100 80GB HBM3
# IRQ: 153
# GPU UUID: GPU-xxxx-xxxx-xxxx-xxxx
# sysfs 전원 관리
cat /sys/bus/pci/devices/0000:01:00.0/power_state
# D0 (활성) / D3hot (절전)
dkms status nvidia로 현재 빌드 상태를 확인하세요.
| 파라미터 | 기본값 | 설명 | 조정 시나리오 |
|---|---|---|---|
NVreg_EnablePCIeGen3 | 1 | PCIe Gen3 모드 활성화 | 호환성 문제 시 0으로 비활성화 |
NVreg_MemoryPoolSize | 256 | UVM 내부 메모리 풀 (MB) | 대규모 UVM 사용 시 증가 |
NVreg_PreserveVideoMemoryAllocations | 0 | 서스펜드 시 VRAM 보존 | 절전/하이버네이트 사용 시 1 |
NVreg_RegistryDwords | — | 레지스터 레벨 설정 주입 | NVIDIA 지원팀 지시에 따라 |
NVreg_EnableGpuFirmware | 0 | GSP 펌웨어 강제 활성화 | 오픈 커널 모듈 전환 시 1 |
NVreg_OpenRmEnableUnsupportedGpus | 0 | 미지원 GPU에서 오픈 모듈 허용 | 실험적 하드웨어 테스트 |
# modprobe 옵션으로 파라미터 설정 (/etc/modprobe.d/nvidia.conf)
options nvidia NVreg_PreserveVideoMemoryAllocations=1
options nvidia NVreg_MemoryPoolSize=512
# DKMS 빌드 상태 확인
dkms status nvidia
# nvidia/550.127.05, 6.1.0-26-amd64, x86_64: installed
# nvidia-persistenced — GPU 컨텍스트 지속 (초기화 지연 제거)
sudo systemctl enable nvidia-persistenced
sudo systemctl start nvidia-persistenced
# GPU 초기화가 첫 CUDA 호출 시 ~0.5s 걸리는 문제 해소
# HPC/ML 서버에서 필수 — 잦은 CUDA 프로세스 시작/종료 시
# Fabricmanager — NVSwitch 기반 멀티 GPU 시스템 (DGX)
sudo systemctl enable nvidia-fabricmanager
# NVSwitch 토폴로지 관리, GPU 간 NVLink 풀 메시 구성
# /proc/driver/nvidia/ 전체 구조 확인
ls /proc/driver/nvidia/
# gpus/ params patches registry version
cat /proc/driver/nvidia/version
# NVRM version: NVIDIA UNIX x86_64 Kernel Module 550.127.05
nvidia-smi -pm 1로 설정하는 Persistence Mode는 GPU 초기화 상태를 유지하지만,
nvidia-persistenced 데몬이 더 안정적입니다. 데몬은 GPU당 최소 컨텍스트를 유지하여
마지막 사용자 프로세스 종료 후에도 드라이버가 언로드되지 않게 합니다.
HPC 클러스터에서는 두 방법 모두 활성화하는 것이 일반적입니다.
GPUDirect / RDMA
GPUDirect 기술은 GPU 메모리와 외부 디바이스(다른 GPU, NIC, NVMe) 간의 직접 DMA 경로를 제공하여 CPU 메모리 복사를 제거합니다. HPC와 대규모 AI 학습에서 노드 간 통신 병목을 해소하는 핵심 기술입니다.
| 기술 | 경로 | 커널 모듈 | 대역폭 (예시) |
|---|---|---|---|
| GPUDirect P2P | GPU↔GPU (동일 노드, PCIe) | nvidia.ko | PCIe 4.0: ~32 GB/s |
| NVLink | GPU↔GPU (전용 인터커넥트) | nvidia.ko | NVLink 4.0: 900 GB/s (H100) |
| GPUDirect RDMA | GPU↔NIC (CPU 바이패스) | nvidia-peermem.ko | IB HDR: ~25 GB/s |
| GPUDirect Storage | GPU↔NVMe (CPU 바이패스) | nvidia-fs.ko | PCIe: ~7 GB/s |
nvidia-peermem.ko는 Linux 커널의
peer_memory_client API에 등록하여 InfiniBand 서브시스템(mlx5_ib 등)이 GPU 메모리의
물리 주소를 직접 얻을 수 있게 합니다. NCCL은 이를 활용해 AllReduce 등 집합 통신 시
시스템 RAM을 거치지 않는 제로카피 전송을 수행합니다.
# GPUDirect P2P 토폴로지 확인
nvidia-smi topo -m
# GPU0 GPU1 GPU2 GPU3 NIC0 CPU
# GPU0 X NV12 NV12 NV12 SYS SYS
# GPU1 NV12 X NV12 NV12 SYS SYS
# NV12 = NVLink 12 hops, SYS = PCIe through CPU
# GPU 간 P2P 접근 가능 여부 확인
nvidia-smi topo -p2p r
# GPU0 GPU1: OK (NVLink P2P 가능)
# nvidia-peermem 로드 확인
lsmod | grep nvidia_peermem
# nvidia_peermem 16384 0
cat /sys/kernel/mm/memory_peers/nvidia-peermem/version
# 1.3
# NCCL 환경 변수로 GPUDirect 제어
export NCCL_NET_GDR_LEVEL=5 # GPUDirect RDMA 활성화 수준
export NCCL_IB_HCA=mlx5_0:1 # InfiniBand HCA 지정
export NCCL_P2P_LEVEL=NVL # NVLink P2P 사용
export NCCL_DEBUG=INFO # 디버그 로그
NCCL 집합 통신
NCCL(NVIDIA Collective Communications Library)은 다중 GPU 간 집합 통신을 최적화하는 라이브러리로, 분산 딥러닝 학습의 핵심 인프라입니다. NVLink, PCIe, InfiniBand 등 사용 가능한 모든 인터커넥트를 자동으로 감지하여 최적의 통신 토폴로지를 구성합니다.
| 연산 | 입력→출력 | 통신량 | 대표 사용 사례 |
|---|---|---|---|
ncclAllReduce | 각 GPU 텐서 → 합산 결과 전체 복제 | 2(N-1)/N × size | 데이터 병렬 그래디언트 동기화 |
ncclAllGather | 각 GPU 조각 → 전체 텐서 복제 | (N-1)/N × total | Tensor Parallelism 출력 수집 |
ncclReduceScatter | 합산 + 분산 | (N-1)/N × size | ZeRO Stage 2/3 옵티마이저 |
ncclBroadcast | 루트 → 전체 | size | 모델 가중치 초기 배포 |
ncclReduce | 전체 → 루트 합산 | (N-1) × size | 메트릭 수집, 체크포인팅 |
ncclSend/Recv | 점대점 | size | Pipeline Parallelism 스테이지 간 전송 |
/* NCCL AllReduce — 4-GPU 그래디언트 동기화 */
#include <nccl.h>
ncclComm_t comms[4];
ncclCommInitAll(comms, 4, devs); /* 4 GPU 커뮤니케이터 생성 */
/* 각 GPU에서 비동기 AllReduce 실행 */
ncclGroupStart();
for (int i = 0; i < 4; i++) {
cudaSetDevice(i);
ncclAllReduce(
sendbuff[i], recvbuff[i],
count, ncclFloat, ncclSum,
comms[i], streams[i]
);
}
ncclGroupEnd();
/* 모든 스트림 동기화 */
for (int i = 0; i < 4; i++) {
cudaSetDevice(i);
cudaStreamSynchronize(streams[i]);
}
/* 정리 */
for (int i = 0; i < 4; i++)
ncclCommDestroy(comms[i]);
NCCL_ALGO=Ring 또는 Tree로 강제 지정할 수 있으나, 대부분의 경우
자동 선택이 최적입니다. NCCL_DEBUG=INFO로 선택된 알고리즘과 대역폭을 확인하세요.
NVIDIA 오픈 GPU 커널 모듈
2022년 5월, NVIDIA는 open-gpu-kernel-modules 저장소를 통해 GPU 커널 드라이버의 소스 코드를 공개했습니다(GPL-2.0 / MIT 듀얼 라이선스). Turing(GTX 16xx) 이후 아키텍처에서 사용할 수 있으며, 장기적으로 독점 모듈을 대체할 계획입니다.
| 항목 | 오픈 커널 모듈 | 독점 커널 모듈 | nouveau (메인라인) |
|---|---|---|---|
| 라이선스 | GPL-2.0 / MIT | 독점 | GPL-2.0 |
| 소스 공개 | 전체 (커널 부분) | 비공개 | 전체 (리버스 엔지니어링) |
| 지원 GPU | Turing 이후 (530+) | Kepler 이후 | Tesla~Ampere (제한적) |
| CUDA 지원 | 완전 (독점 libcuda.so 필요) | 완전 | 제한적 (Volta+ GSP-RM 기반) |
| 메인라인 포함 | 아니오 (out-of-tree) | 아니오 | 예 |
| GSP 펌웨어 | 필수 (GPU System Processor) | 내장 | 가능 (Turing+) |
| 버그 리포트 | GitHub Issues | NVIDIA 포럼 | freedesktop GitLab |
NVIDIA 컨테이너 / 가상화
클라우드 및 AI 인프라에서 GPU를 컨테이너·가상 머신에 안전하게 공유하려면 별도 런타임이 필요합니다. NVIDIA는 Container Toolkit, MIG, vGPU, K8s Device Plugin을 통해 GPU 격리와 스케줄링을 제공합니다.
| 기술 | 격리 수준 | 대상 GPU | 사용 사례 |
|---|---|---|---|
| nvidia-container-toolkit | 소프트웨어 (OCI 런타임 훅) | 전체 | Docker/Podman에서 --gpus 플래그로 GPU 할당 |
| MIG (Multi-Instance GPU) | 하드웨어 (SM/메모리 파티셔닝) | A100, H100, H200 | 하나의 GPU를 최대 7개 독립 인스턴스로 분할 |
| vGPU | 하이퍼바이저 (SR-IOV / 메디에이티드) | 데이터센터 GPU (라이선스 필요) | VM에 가상 GPU 할당, VDI/원격 데스크톱 |
| K8s Device Plugin | 스케줄러 수준 | 전체 | nvidia.com/gpu: 1 리소스 요청, 노드 선택 |
| Time-Slicing | 시간 분할 (컨텍스트 스위칭) | 전체 | MIG 미지원 GPU에서 다중 워크로드 공유 |
# Docker에서 CUDA 컨테이너 실행
docker run --gpus all nvidia/cuda:12.4.0-runtime-ubuntu22.04 nvidia-smi
# MIG 인스턴스 생성 (A100/H100)
sudo nvidia-smi mig -cgi 19,19,19 -C # 3x 3g.40gb 프로파일
nvidia-smi mig -lgi # GPU 인스턴스 목록
nvidia-smi mig -lci # 컴퓨트 인스턴스 목록
# Kubernetes GPU 리소스 요청 (Pod spec)
# resources:
# limits:
# nvidia.com/gpu: 1
| 프로파일 | SM 수 | 메모리 | L2 캐시 | 최대 인스턴스 수 | 사용 사례 |
|---|---|---|---|---|---|
| 1g.10gb | 14 | 10 GB | 5 MB | 7 | 소형 추론, 개발/테스트 |
| 1g.20gb | 14 | 20 GB | 10 MB | 4 | 메모리 집약 추론 |
| 2g.20gb | 28 | 20 GB | 10 MB | 3 | 중형 학습/추론 |
| 3g.40gb | 42 | 40 GB | 20 MB | 2 | 대형 모델 학습 |
| 4g.40gb | 56 | 40 GB | 20 MB | 1 | 대형 학습 (단독) |
| 7g.80gb | 108 | 80 GB | 40 MB | 1 | 전체 GPU 활용 |
# nvidia-container-toolkit 설치 (Ubuntu)
distribution=$(. /etc/os-release; echo $ID$VERSION_ID)
curl -fsSL https://nvidia.github.io/libnvidia-container/gpgkey | \
sudo gpg --dearmor -o /usr/share/keyrings/nvidia-container-toolkit-keyring.gpg
sudo apt-get update && sudo apt-get install nvidia-container-toolkit
# Docker 런타임 설정
sudo nvidia-ctk runtime configure --runtime=docker
sudo systemctl restart docker
# MIG + 컨테이너 — 특정 MIG 인스턴스에서 컨테이너 실행
# MIG 디바이스 UUID 확인
nvidia-smi -L
# GPU 0: A100-SXM4-80GB (UUID: GPU-xxxxx)
# MIG 1g.10gb Device 0: (UUID: MIG-yyyyy)
# MIG UUID로 컨테이너 실행
docker run --gpus '"device=MIG-yyyyy"' nvidia/cuda:12.4.0-base-ubuntu22.04 nvidia-smi
# Kubernetes MIG 리소스 — Device Plugin 설정
# helm install --set migStrategy=single nvidia-device-plugin ...
# Pod spec: nvidia.com/mig-1g.10gb: 1
nvidia-container-cli가 GPU 디바이스 노드(/dev/nvidia*)와
드라이버 라이브러리(libnvidia-*.so)를 컨테이너 네임스페이스에 바인드 마운트합니다.
컨테이너 이미지에는 CUDA 런타임만 포함하면 되며, 드라이버 버전은 호스트에서 자동으로 주입됩니다.
이를 통해 동일 이미지가 다양한 드라이버 버전의 호스트에서 동작합니다.
CUDA 컴파일 파이프라인
CUDA 소스(.cu)는 nvcc 컴파일러 드라이버를 통해 여러 단계를 거칩니다.
호스트 코드는 gcc/clang에 위임되고, 디바이스 코드는 PTX(가상 ISA) → SASS(실제 기계어)로 변환됩니다.
| 단계 | 입력 | 출력 | 도구 | 설명 |
|---|---|---|---|---|
| 1. 전처리 | .cu | .cu.cpp.ii / .cu.gpu | nvcc (cudafe++) | 호스트/디바이스 코드 분리 |
| 2. PTX 컴파일 | 디바이스 코드 | .ptx | cicc | 가상 ISA (중간 표현) |
| 3. 어셈블 | .ptx | .cubin (SASS) | ptxas | 대상 SM 아키텍처용 기계어 |
| 4. Fatbinary | .ptx + .cubin | .fatbin | fatbinary | 다중 아키텍처 번들 |
| 5. 호스트 컴파일 | 호스트 코드 + .fatbin | .o | gcc / clang | fatbin을 ELF에 임베드 |
| 6. 링크 | .o + libcudart | 실행 파일 | ld | CUDA 런타임 라이브러리 링크 |
# 기본 컴파일 (sm_80 = Ampere, sm_90 = Hopper)
nvcc -arch=sm_80 -o matmul matmul.cu
# Fatbinary: 여러 아키텍처 동시 타겟
nvcc -gencode arch=compute_80,code=sm_80 \
-gencode arch=compute_90,code=sm_90 \
-gencode arch=compute_90,code=compute_90 \
-o matmul matmul.cu
# compute_90,code=compute_90 → PTX 포함 (미래 GPU JIT 호환)
# PTX 어셈블리 확인
nvcc -arch=sm_80 --ptx -o matmul.ptx matmul.cu
# SASS 디스어셈블
cuobjdump -sass matmul
~/.nv/ComputeCache에 캐시됩니다.
# NVRTC — 런타임 CUDA 컴파일 (Driver API)
# PTX를 프로그램 실행 중에 동적 생성하는 시나리오
# AI 프레임워크(PyTorch, JAX)의 커널 퓨전에 사용
# Compute Capability별 아키텍처 코드 매핑
# sm_70 = Volta (V100)
# sm_75 = Turing (T4, RTX 20xx)
# sm_80 = Ampere (A100, A10)
# sm_86 = Ampere (RTX 30xx, A40)
# sm_89 = Ada Lovelace (RTX 40xx, L4, L40)
# sm_90 = Hopper (H100, H200)
# sm_90a = Hopper (SM 전용 기능, GH200)
# sm_100 = Blackwell (B200, GB200)
# 현재 GPU의 Compute Capability 확인
nvidia-smi --query-gpu=compute_cap --format=csv,noheader
# 9.0
# NVCC 상세 컴파일 과정 보기
nvcc -v -arch=sm_80 -o test test.cu 2>&1 | head -50
# cudafe++ → cicc → ptxas → fatbinary → gcc 순서 확인 가능
CUDA 디버깅 및 프로파일링
CUDA 프로그램의 성능 분석과 디버깅에는 NVIDIA가 제공하는 전용 도구체인과
Linux 커널의 /proc/driver/nvidia/ 인터페이스를 활용합니다.
| 도구 | 용도 | 핵심 기능 |
|---|---|---|
nvidia-smi | GPU 모니터링 | 온도, 전력, 메모리, 프로세스, MIG, 클럭 |
nvtop | 실시간 모니터링 (htop 스타일) | GPU/메모리 사용률 그래프, 프로세스 목록 |
nsys (Nsight Systems) | 시스템 프로파일링 | 타임라인 뷰, CPU-GPU 상호작용, CUDA API 추적 |
ncu (Nsight Compute) | 커널 프로파일링 | 오큐펀시, 메모리 대역폭, 워프 스톨 분석 |
cuda-gdb | GPU 디버거 | 커널 내 브레이크포인트, 워프/스레드 단위 검사 |
compute-sanitizer | 메모리 검사 | 범위 초과 접근, 레이스 컨디션, 리크 탐지 |
/proc/driver/nvidia/ | 커널 모듈 상태 | GPU 정보, 파라미터, 메모리 할당, 에러 로그 |
# GPU 상태 모니터링
nvidia-smi
# +-------------------------+------+------+
# | GPU Name Temp | Util | MIG |
# | Fan Perf Pwr:Usage/Cap| GPU | Mode |
# |=========================+======+======|
# | 0 NVIDIA H100 38C | 85% | On |
# | N/A P0 310W / 350W | | |
# 시스템 프로파일링 (Nsight Systems)
nsys profile --stats=true ./my_cuda_app
# → report.nsys-rep 생성 (GUI에서 타임라인 분석)
# 커널 단위 프로파일링 (Nsight Compute)
ncu --set full --target-processes all ./my_cuda_app
# → 오큐펀시, SM 활용률, 메모리 throughput 상세 리포트
# 메모리 오류 탐지
compute-sanitizer --tool memcheck ./my_cuda_app
# → 범위 초과 접근, 초기화되지 않은 읽기 탐지
# 레이스 컨디션 탐지
compute-sanitizer --tool racecheck ./my_cuda_app
# → 공유 메모리 WAR/WAW/RAW 레이스 탐지
# 동기화 오류 탐지
compute-sanitizer --tool synccheck ./my_cuda_app
# → __syncthreads() 누락, 불균형 배리어 탐지
# cuda-gdb 디버깅 세션
cuda-gdb ./my_cuda_app
# (cuda-gdb) set cuda break_on_launch all
# (cuda-gdb) run
# (cuda-gdb) cuda thread (0,0,0) ← 특정 스레드 선택
# (cuda-gdb) info cuda threads ← 워프/블록 상태
# (cuda-gdb) print threadIdx ← 현재 스레드 인덱스
# (cuda-gdb) cuda kernel ← 활성 커널 정보
| 메트릭 | 의미 | 최적 범위 | 낮을 때 원인 |
|---|---|---|---|
| SM Occupancy (%) | 활성 워프 / 최대 워프 | 50~100% | 레지스터/공유메모리 과다, 작은 블록 |
| Compute Throughput (%) | SM 파이프라인 활용률 | >60% | 메모리 바운드, 명령어 레벨 의존성 |
| Memory Throughput (%) | 메모리 대역폭 활용률 | >60% | 비코얼레싱 접근, 낮은 오큐펀시 |
| Warp Stall (사이클) | 워프 대기 원인별 사이클 | 낮을수록 좋음 | long_scoreboard: 글로벌 메모리 대기 |
| L1 Hit Rate (%) | L1 캐시 적중률 | >80% | 랜덤 접근, 작업 세트 > L1 크기 |
| Achieved Bandwidth (GB/s) | 실제 메모리 대역폭 | 이론 대비 >70% | 비코얼레싱, 낮은 IPC |
nsys profile로 전체 타임라인 확인 → CPU-GPU 동기화 병목, 유휴 시간 식별.
② 병목 커널을 ncu --set full로 상세 분석 → Compute vs Memory 바운드 판별.
③ Memory 바운드면: 코얼레싱 접근, 공유메모리 타일링, L2 지역성 최적화.
④ Compute 바운드면: ILP(Instruction-Level Parallelism) 증가, Tensor Core 활용, 알고리즘 개선.
⑤ Latency 바운드(오큐펀시 낮음)면: 블록 크기/레지스터 조정.
이 과정을 반복하여 루프라인 모델(Roofline Model) 상에서 이론적 한계에 근접시킵니다.
멀티 GPU 프로그래밍
단일 노드에 여러 GPU가 장착된 환경에서 CUDA는 cudaSetDevice()로 활성 GPU를 전환하고,
P2P(Peer-to-Peer) 접근과 비동기 전송으로 GPU 간 데이터를 교환합니다.
대규모 AI 학습에서는 NCCL과 결합하여 데이터/모델/파이프라인 병렬처리를 구현합니다.
| 전략 | 분할 대상 | 통신 패턴 | GPU당 메모리 | 확장성 | 프레임워크 지원 |
|---|---|---|---|---|---|
| 데이터 병렬 (DP) | 배치 (데이터) | AllReduce (그래디언트) | 전체 모델 복제 | 높음 (수백 GPU) | DDP, FSDP, Horovod |
| 텐서 병렬 (TP) | 레이어 내 텐서 | AllReduce/AllGather | 텐서 1/N | 노드 내 (NVLink 필요) | Megatron-LM, DeepSpeed |
| 파이프라인 병렬 (PP) | 레이어 그룹 | Send/Recv (스테이지 간) | 레이어 1/N | 중간 (버블 오버헤드) | Megatron-LM, GPipe |
| ZeRO Stage 1 | 옵티마이저 상태 | AllGather (업데이트 시) | 옵티마이저 1/N | 높음 | DeepSpeed |
| ZeRO Stage 2 | 옵티마이저 + 그래디언트 | ReduceScatter + AllGather | 옵티마이저+그래디언트 1/N | 높음 | DeepSpeed, FSDP |
| ZeRO Stage 3 | 옵티마이저+그래디언트+파라미터 | AllGather (순전파/역전파) | 파라미터 1/N | 가장 높음 | DeepSpeed, FSDP |
/* 멀티 GPU — P2P 메모리 접근 + 비동기 전송 */
int deviceCount;
cudaGetDeviceCount(&deviceCount);
/* P2P 접근 활성화 (NVLink/PCIe P2P) */
for (int i = 0; i < deviceCount; i++) {
cudaSetDevice(i);
for (int j = 0; j < deviceCount; j++) {
if (i != j) {
int canAccess;
cudaDeviceCanAccessPeer(&canAccess, i, j);
if (canAccess)
cudaDeviceEnablePeerAccess(j, 0);
}
}
}
/* GPU 0의 메모리를 GPU 1에서 직접 접근 (UVA) */
cudaSetDevice(0);
float *d_gpu0;
cudaMalloc(&d_gpu0, size);
cudaSetDevice(1);
/* P2P 활성화 시 GPU1 커널에서 d_gpu0 직접 읽기 가능 */
readFromGpu0Kernel<<<grid, block>>>(d_gpu0);
/* 비동기 GPU간 복사 (Copy Engine 사용) */
cudaMemcpyPeerAsync(d_gpu1, 1, /* dst GPU 1 */
d_gpu0, 0, /* src GPU 0 */
size, stream);
cudaPointerGetAttributes()로 포인터가
어느 디바이스에 속하는지 조회할 수 있으며, P2P가 활성화되면 GPU 0의 포인터를 GPU 1의 커널에서
직접 역참조할 수 있습니다(NVLink 시 ~900 GB/s, PCIe 시 ~32 GB/s).
CUDA 에러 처리 패턴
CUDA API 호출은 cudaError_t를 반환하며, 커널 실행 오류는 비동기적으로 발생합니다.
프로덕션 코드에서는 모든 CUDA 호출을 검사하는 매크로를 사용하고,
커널 실행 후 cudaGetLastError()와 cudaDeviceSynchronize()로 에러를 포착합니다.
/* CUDA 에러 검사 매크로 — 프로덕션 필수 패턴 */
#define CUDA_CHECK(call) do { \
cudaError_t err = (call); \
if (err != cudaSuccess) { \
fprintf(stderr, "CUDA error at %s:%d: %s\n", \
__FILE__, __LINE__, cudaGetErrorString(err)); \
exit(EXIT_FAILURE); \
} \
} while(0)
/* 사용 예 */
CUDA_CHECK(cudaMalloc(&d_ptr, size));
CUDA_CHECK(cudaMemcpy(d_ptr, h_ptr, size, cudaMemcpyHostToDevice));
/* 커널 실행 에러 검사 — 2단계 필수 */
myKernel<<<grid, block>>>(d_ptr);
CUDA_CHECK(cudaGetLastError()); /* 실행 설정 오류 (즉시) */
CUDA_CHECK(cudaDeviceSynchronize()); /* 실행 중 오류 (비동기) */
/* 스트림 콜백으로 비동기 에러 처리 */
cudaLaunchHostFunc(stream, [](void* data) {
cudaError_t err = cudaGetLastError();
if (err != cudaSuccess)
handle_gpu_error(err);
}, nullptr);
| 에러 코드 | 원인 | 해결 방법 |
|---|---|---|
cudaErrorMemoryAllocation | GPU 메모리 부족 | 할당 크기 줄이기, 메모리 풀 사용, nvidia-smi 확인 |
cudaErrorInvalidConfiguration | 잘못된 블록/그리드 크기 | 블록 크기 ≤ 1024, CC별 제약 확인 |
cudaErrorIllegalAddress | 잘못된 메모리 접근 (SEGFAULT) | compute-sanitizer --tool memcheck |
cudaErrorLaunchTimeout | 커널 실행 시간 초과 (TDR) | 디스플레이 GPU에서 긴 커널 회피, TDR 타임아웃 증가 |
cudaErrorNoKernelImageForDevice | 현재 GPU CC 미지원 바이너리 | 적절한 -arch=sm_XX로 재컴파일 |
cudaErrorAssert | 커널 내 assert() 실패 | 디바이스 코드 로직 디버깅 |
cudaErrorECCUncorrectable | GPU 메모리 ECC 오류 (하드웨어) | nvidia-smi --query-gpu=ecc.errors.uncorrected.total |
cudaErrorIllegalAddress 같은 치명적 에러가 발생하면
CUDA 컨텍스트가 sticky error 상태가 되어 이후 모든 CUDA 호출이 실패합니다.
복구하려면 cudaDeviceReset()으로 컨텍스트를 완전히 재초기화해야 합니다.
프로덕션에서는 GPU 워커 프로세스를 분리하여, 오류 시 프로세스를 재시작하는 구조가 권장됩니다.
Xid 에러: 커널 로그(dmesg)에 나타나는 "NVRM: Xid" 메시지는 GPU 하드웨어/드라이버 오류입니다.
Xid 79(GPU 폴트), Xid 48(더블 비트 ECC)이 반복되면 GPU 교체를 검토하세요.
CUDA 라이브러리 에코시스템
CUDA 생태계의 진정한 강점은 수십 년간 최적화된 도메인별 라이브러리에 있습니다. 대부분의 AI/HPC 워크로드는 커널을 직접 작성하지 않고 이 라이브러리들을 조합합니다.
| CUDA 라이브러리 | 도메인 | ROCm 대응 | 설명 |
|---|---|---|---|
| cuBLAS | 선형대수 | rocBLAS | GEMM, 행렬 분해, Tensor Core 활용 |
| cuDNN | 딥러닝 | MIOpen | Conv, RNN, Attention, BN 등 DNN 프리미티브 |
| cuFFT | FFT | rocFFT | 1D/2D/3D FFT, 배치 처리 |
| cuSPARSE | 희소 행렬 | rocSPARSE | SpMV, SpMM, 희소 행렬 연산 |
| cuRAND | 난수 생성 | rocRAND | 의사/준난수, 병렬 RNG 스트림 |
| NCCL | 집합 통신 | RCCL | AllReduce, AllGather, 다중 GPU/노드 |
| TensorRT | 추론 최적화 | — | 그래프 최적화, INT8/FP8 양자화, 레이어 퓨전 |
| Thrust | 병렬 알고리즘 | rocThrust | sort, reduce, scan (C++ STL 스타일) |
| cuDSS | 직접 희소 솔버 | rocSOLVER | LU, Cholesky, QR 분해 (희소) |
| CUTLASS | GEMM 템플릿 | composable_kernel | Tensor Core GEMM 커스터마이징 |
/* cuBLAS GEMM — 행렬 곱셈 C = α·A·B + β·C */
cublasHandle_t handle;
cublasCreate(&handle);
float alpha = 1.0f, beta = 0.0f;
cublasSgemm(handle,
CUBLAS_OP_N, CUBLAS_OP_N,
M, N, K,
&alpha,
d_A, M, /* A: M×K */
d_B, K, /* B: K×N */
&beta,
d_C, M); /* C: M×N */
cublasDestroy(handle);
/* Tensor Core 자동 활용 (FP16/TF32/FP8 입력 시) */
hipify-perl / hipify-clang 도구는
CUDA 소스를 HIP 코드로 자동 변환합니다. cudaMalloc → hipMalloc,
cublasSgemm → rocblas_sgemm 등 API가 1:1 대응되어,
대부분의 CUDA 코드를 AMD GPU에서도 실행할 수 있습니다.
자세한 내용은 ROCm/HIP 섹션을 참조하세요.
/* cuDNN — 합성곱(Convolution) 연산 예제 */
cudnnHandle_t cudnn;
cudnnCreate(&cudnn);
cudnnTensorDescriptor_t inputDesc, outputDesc;
cudnnFilterDescriptor_t filterDesc;
cudnnConvolutionDescriptor_t convDesc;
/* 텐서/필터 디스크립터 설정 (NCHW 포맷) */
cudnnCreateTensorDescriptor(&inputDesc);
cudnnSetTensor4dDescriptor(inputDesc, CUDNN_TENSOR_NCHW,
CUDNN_DATA_FLOAT, batch, channels, height, width);
/* 최적 합성곱 알고리즘 자동 선택 */
cudnnConvolutionFwdAlgo_t algo;
cudnnGetConvolutionForwardAlgorithm_v7(cudnn,
inputDesc, filterDesc, convDesc, outputDesc,
1, &returnedCount, &perfResults);
algo = perfResults.algo; /* 가장 빠른 알고리즘 선택 */
/* 합성곱 실행 — Tensor Core 자동 활용 */
cudnnSetConvolutionMathType(convDesc, CUDNN_TENSOR_OP_MATH);
float alpha = 1.0f, beta = 0.0f;
cudnnConvolutionForward(cudnn, &alpha,
inputDesc, d_input, filterDesc, d_filter,
convDesc, algo, d_workspace, workspaceSize,
&beta, outputDesc, d_output);
cudnnDestroy(cudnn);
| 알고리즘 | 워크스페이스 | 속도 | 정밀도 | 사용 시나리오 |
|---|---|---|---|---|
| IMPLICIT_GEMM | 0 | 기본 | 정확 | 메모리 제약 환경 |
| IMPLICIT_PRECOMP_GEMM | 소량 | 빠름 | 정확 | 일반적 사용 |
| GEMM | 대량 (im2col) | 빠름 | 정확 | 큰 배치 |
| FFT | 대량 | 매우 빠름 | 근사 | 큰 필터 크기 |
| FFT_TILING | 중간 | 빠름 | 근사 | 중간 필터 |
| WINOGRAD | 소량 | 매우 빠름 | 근사 | 3×3, 5×5 필터 |
| WINOGRAD_NONFUSED | 중간 | 매우 빠름 | 근사 | 3×3 필터 최적 |
딥러닝 프레임워크 통합
PyTorch, TensorFlow, JAX 등 주요 딥러닝 프레임워크는 CUDA를 통해 GPU 가속을 구현합니다. 프레임워크는 내부적으로 cuBLAS, cuDNN, NCCL, cuFFT 등 CUDA 라이브러리를 호출하며, 사용자는 Python API만으로 Tensor Core, 멀티 GPU, 혼합 정밀도 학습을 활용할 수 있습니다.
| 프레임워크 | CUDA 백엔드 | 커널 생성 | 분산 학습 | 혼합 정밀도 |
|---|---|---|---|---|
| PyTorch | ATen + cuDNN + cuBLAS | torch.compile (Triton/CUDA) | DDP, FSDP (NCCL) | torch.amp (FP16/BF16/FP8) |
| TensorFlow | XLA + cuDNN + cuBLAS | XLA HLO → LLVM → PTX | tf.distribute (NCCL) | tf.keras.mixed_precision |
| JAX | XLA (GPU) | XLA HLO → LLVM → PTX | pjit (NCCL) | jnp.bfloat16 / jnp.float8 |
| ONNX Runtime | CUDA EP / TensorRT EP | 사전 컴파일 커널 | — | FP16/INT8 양자화 |
| TensorRT | 직접 CUDA | 레이어 퓨전 + 커널 자동 선택 | — | FP16/INT8/FP8 |
# PyTorch CUDA 사용 확인
python3 -c "import torch; print(torch.cuda.is_available())"
# True
python3 -c "import torch; print(torch.cuda.get_device_name(0))"
# NVIDIA H100 80GB HBM3
# PyTorch 혼합 정밀도 학습 (AMP)
# scaler = torch.amp.GradScaler()
# with torch.amp.autocast(device_type='cuda', dtype=torch.bfloat16):
# output = model(input)
# loss = criterion(output, target)
# scaler.scale(loss).backward()
# scaler.step(optimizer)
# TensorRT 모델 최적화 (FP16 추론)
trtexec --onnx=model.onnx \
--fp16 \
--workspace=4096 \
--saveEngine=model_fp16.trt \
--verbose
# → 레이어 퓨전, 텐서 레이아웃 최적화, Tensor Core 활용
# torch.compile — Triton 커널 자동 생성 (PyTorch 2.0+)
# model = torch.compile(model, mode='max-autotune')
# → 커널 퓨전, 메모리 접근 패턴 최적화, Triton→PTX 컴파일
torch.cuda.caching_allocator로 GPU 메모리를 풀링합니다.
nvidia-smi에 표시되는 메모리 사용량은 실제 텐서 크기보다 클 수 있습니다.
torch.cuda.memory_summary()로 실제 할당/캐시 상태를 확인하세요.
OOM 발생 시 torch.cuda.empty_cache()로 캐시를 반환하거나,
PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True로 메모리 단편화를 줄일 수 있습니다.
lspci -vvv),
DMA: dma_map_sg()를 통한 scatter-gather DMA,
IOMMU: VFIO를 통한 GPU 패스스루(가상화),
cgroups: devices 컨트롤러로 GPU 접근 제어,
udev: /dev/nvidia* 디바이스 노드 자동 생성,
eBPF: GPU 사용량 추적(bpftrace -e 'kprobe:nvidia_ioctl { ... }'),
NUMA: GPU 어피니티 설정(nvidia-smi topo -m으로 NUMA 노드 확인).
최적 성능을 위해 GPU와 같은 NUMA 노드의 CPU·메모리를 사용하는 것이 중요합니다.
OpenCL — 크로스 플랫폼 GPU 컴퓨트
OpenCL(Open Computing Language)은 Khronos Group이 표준화한 범용 병렬 컴퓨팅 API입니다. NVIDIA·AMD·Intel GPU, ARM Mali, Qualcomm Adreno 등 다양한 가속기를 동일한 코드로 활용할 수 있습니다. Linux에서는 Mesa Rusticl(신규 Rust 구현)과 Clover(레거시 C++ 구현)가 오픈소스 드라이버를 제공합니다.
실행 모델: Work-item / Work-group / NDRange
/* OpenCL 메모리 계층 */
/*
* Global Memory (~수 GB VRAM): 모든 work-item 공유, 높은 지연
* Local Memory (~16~64 KB): 한 work-group 내 공유, 빠름
* Private Memory (레지스터): 각 work-item 전용, 최고속
* Constant Memory (캐시): 읽기 전용, GPU가 캐싱 최적화
*/
/* GEMM OpenCL 커널 예제 (타일링 최적화) */
__kernel void gemm_tiled(
__global const float *A, /* Global memory 입력 */
__global const float *B,
__global float *C,
const int N)
{
__local float tileA[16][16]; /* Local memory 타일 */
__local float tileB[16][16];
int row = get_global_id(0); /* 전체 좌표 */
int col = get_global_id(1);
int lrow = get_local_id(0); /* work-group 내 좌표 */
int lcol = get_local_id(1);
float sum = 0.0f;
for (int t = 0; t < N / 16; t++) {
/* 타일을 Local memory로 협력 로딩 */
tileA[lrow][lcol] = A[row * N + (t * 16 + lcol)];
tileB[lrow][lcol] = B[(t * 16 + lrow) * N + col];
barrier(CLK_LOCAL_MEM_FENCE); /* work-group 동기화 */
for (int k = 0; k < 16; k++)
sum += tileA[lrow][k] * tileB[k][lcol];
barrier(CLK_LOCAL_MEM_FENCE);
}
C[row * N + col] = sum;
}
Mesa Rusticl vs Clover
| 항목 | Rusticl (신규) | Clover (레거시) |
|---|---|---|
| 언어 | Rust + 안전성 보장 | C++ |
| OpenCL 버전 | 3.0 지원 | 1.1~1.2 |
| SPIR-V | 네이티브 지원 | 제한적 |
| GPU 지원 | AMD (RadeonSI), Intel (iris/crocus) | AMD Radeon (레거시) |
| 상태 | 활발한 개발 중 (기본값 전환 중) | 유지보수 모드 |
# OpenCL 컴파일 파이프라인
# OpenCL C → LLVM IR → SPIR-V → GPU ISA
# clinfo로 OpenCL 드라이버 확인
clinfo | head -40
# Platform: Mesa/Rusticl or AMD ROCm or Intel OpenCL
# Rusticl 강제 활성화 (환경 변수)
RUSTICL_ENABLE=radeonsi clinfo
# 오프라인 OpenCL 커널 컴파일 (SPIR-V 생성)
clang -cl-std=CL3.0 -target spir64 -emit-llvm -c gemm.cl -o gemm.bc
llvm-spirv gemm.bc -o gemm.spv
Vulkan Compute 파이프라인
Vulkan은 Khronos Group이 표준화한 저수준 크로스 플랫폼 그래픽/컴퓨트 API입니다. Compute Pipeline을 통해 Graphics Pipeline 없이 GPGPU 연산을 수행할 수 있으며, 래스터라이저·프래그먼트 셰이더 대신 Compute Shader만으로 구성됩니다. Vulkan 컴퓨트는 CUDA와 달리 벤더 중립적이어서 NVIDIA, AMD, Intel, Qualcomm, ARM Mali 등 모든 Vulkan 호환 GPU에서 동일한 SPIR-V 셰이더를 실행할 수 있습니다.
Linux에서 Vulkan 드라이버는 크게 두 계열로 나뉩니다:
Mesa 오픈소스 드라이버(RadV, ANV/Hasvk, PanVK, Turnip 등)와
벤더 독점 드라이버(NVIDIA, AMDGPU-PRO).
모든 Vulkan 드라이버는 DRM 서브시스템의 /dev/dri/renderD* 노드를 통해 GPU에 접근하므로,
root 권한 없이도 컴퓨트 작업을 수행할 수 있습니다(render node 그룹 소속 필요).
Vulkan Linux 드라이버 스택
Vulkan 애플리케이션이 GPU에 접근하는 전체 소프트웨어 스택은 다음과 같습니다.
Vulkan 로더(libvulkan.so)가 ICD(Installable Client Driver)를 검색하여
적절한 드라이버를 동적 로드하고, 드라이버는 DRM 렌더 노드 ioctl로 GPU와 통신합니다.
| 드라이버 | GPU | 출처 | Vulkan 버전 | Compute 지원 | DRM 드라이버 |
|---|---|---|---|---|---|
| RadV | AMD GCN 이후 | Mesa (오픈소스) | 1.3 | 완전 | amdgpu |
| ANV | Intel Gen8 이후 | Mesa (오픈소스) | 1.3 | 완전 | i915 / xe |
| NVK | NVIDIA Turing 이후 | Mesa (오픈소스) | 1.3 | 완전 | nouveau (GSP) |
| PanVK | ARM Mali (Valhall) | Mesa (오픈소스) | 1.0~1.1 | 부분적 | panfrost |
| Turnip | Qualcomm Adreno | Mesa (오픈소스) | 1.3 | 완전 | msm |
| V3DV | Broadcom VideoCore VI | Mesa (오픈소스) | 1.2 | 부분적 | v3d |
| lavapipe | CPU (소프트웨어) | Mesa (오픈소스) | 1.3 | 완전 | — (CPU 실행) |
| NVIDIA 독점 | NVIDIA Kepler 이후 | 독점 | 1.3 | 완전 | nvidia-drm |
| AMDGPU-PRO | AMD (프로) | 반독점 | 1.3 | 완전 | amdgpu |
# Vulkan 드라이버 정보 확인
vulkaninfo --summary
# GPU0: AMD Radeon RX 7900 XTX (RADV NAVI31)
# apiVersion = 1.3.274
# driverVersion = 24.0.99
# driverID = DRIVER_ID_MESA_RADV
# ICD 파일 확인 (로더가 검색하는 JSON 매니페스트)
ls /usr/share/vulkan/icd.d/
# radeon_icd.x86_64.json intel_icd.x86_64.json nvidia_icd.json
# 특정 드라이버 강제 선택 (다중 GPU 시스템)
VK_ICD_FILENAMES=/usr/share/vulkan/icd.d/radeon_icd.x86_64.json ./my_app
# Vulkan 레이어 확인
vulkaninfo --show-layer-list 2>&1 | head -20
# Mesa 디버그: NIR→ISA 컴파일 덤프
RADV_DEBUG=preoptir,shaders ./my_vulkan_app 2>shader_dump.txt
MESA_VK_DEVICE_SELECT=nouveau로 NVK를 명시적으로 선택합니다.
Compute Shader와 파이프라인 구성
Vulkan Compute Pipeline의 구성 과정은 크게 6단계로 나뉩니다: ① GLSL/HLSL 작성 → ② SPIR-V 컴파일 → ③ VkDevice/VkQueue 생성 → ④ 리소스 할당(버퍼, 디스크립터) → ⑤ 파이프라인 생성 → ⑥ 커맨드 기록 및 제출.
/* GLSL Compute Shader — 행렬 곱셈 (공유 메모리 타일링) */
/* matmul.comp → glslc matmul.comp -o matmul.spv */
#version 450
#define TILE_SIZE 16
layout(local_size_x = TILE_SIZE, local_size_y = TILE_SIZE) in;
layout(set = 0, binding = 0) readonly buffer MatA { float A[]; };
layout(set = 0, binding = 1) readonly buffer MatB { float B[]; };
layout(set = 0, binding = 2) buffer MatC { float C[]; };
layout(push_constant) uniform PushConst { uint N; } pc;
/* 공유 메모리 — 워크그룹 내 invocation 간 공유 */
shared float tileA[TILE_SIZE][TILE_SIZE];
shared float tileB[TILE_SIZE][TILE_SIZE];
void main() {
uint row = gl_GlobalInvocationID.y;
uint col = gl_GlobalInvocationID.x;
uint lr = gl_LocalInvocationID.y;
uint lc = gl_LocalInvocationID.x;
float sum = 0.0;
uint numTiles = (pc.N + TILE_SIZE - 1) / TILE_SIZE;
for (uint t = 0; t < numTiles; t++) {
tileA[lr][lc] = A[row * pc.N + t * TILE_SIZE + lc];
tileB[lr][lc] = B[(t * TILE_SIZE + lr) * pc.N + col];
barrier(); /* 워크그룹 동기화 (CUDA __syncthreads 대응) */
for (uint k = 0; k < TILE_SIZE; k++)
sum += tileA[lr][k] * tileB[k][lc];
barrier();
}
C[row * pc.N + col] = sum;
}
/* vkCmdDispatch(N/TILE_SIZE, N/TILE_SIZE, 1) 으로 실행
* → work-group 수: (N/16)² 개
* → work-group 내 invocation: 16×16 = 256개 */
| CUDA | Vulkan / GLSL | 설명 |
|---|---|---|
| Grid | Dispatch (vkCmdDispatch) | 전체 실행 범위 |
| Block | Work-group (local_size) | 공유 메모리/배리어 범위 |
| Thread | Invocation | 개별 실행 단위 |
blockIdx | gl_WorkGroupID | 워크그룹 ID |
threadIdx | gl_LocalInvocationID | 로컬 인덱스 |
blockIdx*blockDim+threadIdx | gl_GlobalInvocationID | 글로벌 인덱스 |
__shared__ | shared | 워크그룹 내 공유 메모리 |
__syncthreads() | barrier() | 워크그룹 동기화 |
cudaMalloc | vkAllocateMemory + vkBindBufferMemory | 디바이스 메모리 할당 |
cudaMemcpy | vkMapMemory / vkCmdCopyBuffer | 호스트↔디바이스 전송 |
| CUDA Stream | VkQueue + VkFence/VkSemaphore | 비동기 실행/동기화 |
SPIR-V 셰이더 컴파일
SPIR-V(Standard Portable Intermediate Representation)는 Vulkan의 셰이더 바이트코드 형식입니다. GLSL·HLSL·Slang 등 고급 셰이딩 언어에서 SPIR-V로 컴파일한 뒤 Vulkan 드라이버에 전달하면, 드라이버가 대상 GPU의 네이티브 ISA(GCN/RDNA, Xe, SASS 등)로 최종 번역합니다.
# GLSL → SPIR-V 컴파일
glslc -fshader-stage=compute shader.comp -o shader.spv
# 최적화 옵션
glslc -O shader.comp -o shader.spv # 기본 최적화
glslc --target-env=vulkan1.3 shader.comp -o shader.spv # Vulkan 1.3 타겟
# SPIR-V 디스어셈블 (spirv-tools)
spirv-dis shader.spv
# OpCapability Shader
# OpMemoryModel Logical GLSL450
# OpEntryPoint GLCompute %main "main" %gl_GlobalInvocationID
# OpExecutionMode %main LocalSize 256 1 1
# SPIR-V 유효성 검사
spirv-val shader.spv
# SPIR-V 최적화 (size/performance)
spirv-opt -O shader.spv -o shader_opt.spv
# HLSL → SPIR-V (DirectX Shader Compiler)
dxc -spirv -T cs_6_0 -E CSMain shader.hlsl -Fo shader.spv
# spirv-cross: SPIR-V → GLSL/HLSL/MSL 역변환
spirv-cross --output shader_back.glsl shader.spv
| 컴파일러 | 입력 | 출력 | 특징 |
|---|---|---|---|
| glslc (shaderc) | GLSL | SPIR-V | Google 개발, Vulkan SDK 포함, glslang 기반 |
| glslangValidator | GLSL | SPIR-V | Khronos 공식 참조 컴파일러 |
| DXC | HLSL | SPIR-V / DXIL | Microsoft 개발, SM 6.x 지원, HLSL 2021 |
| slangc | Slang | SPIR-V / DXIL / PTX | NVIDIA 개발, 자동 미분, 제네릭 |
| naga | WGSL / GLSL / SPIR-V | SPIR-V / MSL / GLSL / HLSL | Rust wgpu 생태계, 다중 백엔드 |
Vulkan 메모리 관리
Vulkan은 명시적 메모리 관리를 요구합니다. OpenGL이 드라이버에 위임하던 메모리 할당/바인딩을
애플리케이션이 직접 제어하며, 이를 통해 메모리 레이아웃과 전송을 최적화할 수 있습니다.
GPU 메모리는 VkPhysicalDeviceMemoryProperties로 조회한 메모리 타입과
힙(Heap) 정보를 기반으로 할당합니다.
| 플래그 | 의미 | 성능 특성 |
|---|---|---|
DEVICE_LOCAL | GPU 로컬 메모리 (VRAM) | GPU 접근 최고 대역폭, CPU 직접 접근 불가(일반적) |
HOST_VISIBLE | CPU에서 vkMapMemory 가능 | CPU 쓰기 가능, GPU 대역폭은 PCIe 제한 |
HOST_COHERENT | CPU 쓰기 즉시 GPU에 가시적 | vkFlushMappedMemoryRanges 불필요 |
HOST_CACHED | CPU 읽기 캐시 | CPU→GPU 쓰기는 느림, GPU→CPU 읽기 최적화 |
LAZILY_ALLOCATED | 지연 할당 (타일 기반 GPU) | 모바일 GPU의 on-chip 메모리 (실제 VRAM 미사용) |
maxMemoryAllocationCount를
보장하며, 일반적으로 4096개입니다. 작은 버퍼마다 vkAllocateMemory를 호출하면
이 제한에 빠르게 도달합니다. VMA(Vulkan Memory Allocator) 같은 서브할당자로
하나의 큰 할당에서 여러 버퍼를 서브할당하세요. VMA는 AMD가 개발한 오픈소스 라이브러리로,
vmaCreateBuffer() 한 줄로 버퍼 생성+메모리 할당+바인딩을 처리합니다.
Vulkan 동기화 모델
Vulkan의 동기화는 완전히 명시적입니다. 드라이버가 자동으로 동기화하는 OpenGL과 달리, Vulkan에서는 모든 리소스 의존성을 개발자가 직접 선언해야 합니다. 잘못된 동기화는 데이터 레이스, 렌더링 아티팩트, 크래시의 주요 원인입니다.
| 프리미티브 | 범위 | 세분화 | 사용 사례 |
|---|---|---|---|
| Pipeline Barrier | 커맨드 버퍼 내부 | 파이프라인 스테이지 + 메모리 접근 | Compute→Compute, Compute→Transfer 의존성 |
| VkEvent | 커맨드 버퍼 내부 (분할 배리어) | set/wait 분리 | 더 세밀한 의존성 (두 지점 사이) |
| VkSemaphore | 큐 간 (GPU↔GPU) | 바이너리 또는 타임라인 | Compute Queue→Graphics Queue, 멀티 큐 |
| VkFence | CPU↔GPU | 제출 단위 | CPU에서 GPU 작업 완료 대기 |
| Timeline Semaphore | 큐 간 / CPU↔GPU | 단조 증가 카운터 | 파이프라인 스케줄링, CPU/GPU 혼합 의존성 |
/* Vulkan Pipeline Barrier — Compute 셰이더 결과 읽기 전 동기화 */
VkBufferMemoryBarrier barrier = {
.sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER,
.srcAccessMask = VK_ACCESS_SHADER_WRITE_BIT, /* Compute 셰이더 쓰기 */
.dstAccessMask = VK_ACCESS_HOST_READ_BIT, /* CPU 읽기 */
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.buffer = outputBuffer,
.offset = 0,
.size = VK_WHOLE_SIZE,
};
vkCmdPipelineBarrier(cmdBuf,
VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, /* src: Compute 완료 후 */
VK_PIPELINE_STAGE_HOST_BIT, /* dst: CPU 접근 전 */
0, /* 플래그 */
0, NULL, /* 메모리 배리어 */
1, &barrier, /* 버퍼 배리어 */
0, NULL /* 이미지 배리어 */
);
/* Vulkan 1.3 Synchronization2 — 더 직관적인 API */
VkBufferMemoryBarrier2 barrier2 = {
.sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER_2,
.srcStageMask = VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT,
.srcAccessMask = VK_ACCESS_2_SHADER_STORAGE_WRITE_BIT,
.dstStageMask = VK_PIPELINE_STAGE_2_HOST_BIT,
.dstAccessMask = VK_ACCESS_2_HOST_READ_BIT,
.buffer = outputBuffer,
.size = VK_WHOLE_SIZE,
};
VkDependencyInfo depInfo = {
.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO,
.bufferMemoryBarrierCount = 1,
.pBufferMemoryBarriers = &barrier2,
};
vkCmdPipelineBarrier2(cmdBuf, &depInfo);
vkCmdPipelineBarrier는
src/dst 스테이지와 접근 마스크를 배리어 호출과 개별 배리어 구조체에 분산시켜 혼란스러웠습니다.
VK_KHR_synchronization2(1.3 코어)는 각 배리어 구조체에 스테이지+접근 마스크를 함께 포함하여
가독성이 크게 향상됩니다. 신규 코드에서는 항상 Synchronization2를 사용하세요.
Vulkan Compute 호스트 코드 작성
Vulkan Compute의 전체 호스트 코드(C)는 초기화 → 리소스 할당 → 파이프라인 생성 → 디스패치 → 정리 순서로 구성됩니다. 아래는 GLSL Compute Shader로 벡터 덧셈을 수행하는 최소 완전 예제입니다.
/* Vulkan Compute 최소 예제 — 벡터 덧셈 (핵심 부분만 발췌) */
/* 전체 코드는 ~300줄이지만, 핵심 흐름만 표시 */
/* 1. Instance + Physical Device + Logical Device */
VkInstance instance;
vkCreateInstance(&instInfo, NULL, &instance);
VkPhysicalDevice physDev;
vkEnumeratePhysicalDevices(instance, &count, &physDev);
/* Compute 큐 패밀리 검색 */
uint32_t computeQueueFamily = UINT32_MAX;
for (uint32_t i = 0; i < queueFamilyCount; i++) {
if (queueFamilies[i].queueFlags & VK_QUEUE_COMPUTE_BIT)
computeQueueFamily = i;
}
VkDevice device;
vkCreateDevice(physDev, &devInfo, NULL, &device);
VkQueue computeQueue;
vkGetDeviceQueue(device, computeQueueFamily, 0, &computeQueue);
/* 2. 버퍼 생성 + 메모리 할당 (입력 A, B / 출력 C) */
VkBuffer bufA, bufB, bufC;
VkDeviceMemory memA, memB, memC;
createBuffer(device, size, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT, &bufA, &memA);
createBuffer(device, size, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT, &bufB, &memB);
createBuffer(device, size, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT, &bufC, &memC);
/* 호스트 데이터 업로드 (HOST_VISIBLE 메모리의 경우) */
float *mapped;
vkMapMemory(device, memA, 0, size, 0, (void**)&mapped);
memcpy(mapped, hostDataA, size);
vkUnmapMemory(device, memA);
/* 3. Descriptor Set Layout + Pipeline Layout */
VkDescriptorSetLayoutBinding bindings[3] = {
{ 0, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1, VK_SHADER_STAGE_COMPUTE_BIT },
{ 1, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1, VK_SHADER_STAGE_COMPUTE_BIT },
{ 2, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1, VK_SHADER_STAGE_COMPUTE_BIT },
};
/* ... DescriptorSetLayout, PipelineLayout 생성 생략 ... */
/* 4. Compute Pipeline 생성 */
VkShaderModule shaderModule;
/* shader.spv 파일 로드 → vkCreateShaderModule() */
VkComputePipelineCreateInfo pipelineInfo = {
.sType = VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO,
.stage = {
.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO,
.stage = VK_SHADER_STAGE_COMPUTE_BIT,
.module = shaderModule,
.pName = "main",
},
.layout = pipelineLayout,
};
VkPipeline pipeline;
vkCreateComputePipelines(device, NULL, 1, &pipelineInfo, NULL, &pipeline);
/* 5. Command Buffer 기록 */
VkCommandBuffer cmdBuf;
vkBeginCommandBuffer(cmdBuf, &beginInfo);
vkCmdBindPipeline(cmdBuf, VK_PIPELINE_BIND_POINT_COMPUTE, pipeline);
vkCmdBindDescriptorSets(cmdBuf, VK_PIPELINE_BIND_POINT_COMPUTE,
pipelineLayout, 0, 1, &descriptorSet, 0, NULL);
vkCmdDispatch(cmdBuf, (N + 255) / 256, 1, 1); /* 디스패치! */
vkEndCommandBuffer(cmdBuf);
/* 6. 제출 + 완료 대기 */
VkFence fence;
vkCreateFence(device, &fenceInfo, NULL, &fence);
VkSubmitInfo submitInfo = { .commandBufferCount = 1, .pCommandBuffers = &cmdBuf };
vkQueueSubmit(computeQueue, 1, &submitInfo, fence);
vkWaitForFences(device, 1, &fence, VK_TRUE, UINT64_MAX);
/* 7. 결과 읽기 */
vkMapMemory(device, memC, 0, size, 0, (void**)&mapped);
memcpy(hostResult, mapped, size);
vkUnmapMemory(device, memC);
Subgroup 연산과 고급 Compute 기능
Vulkan 1.1에서 도입된 Subgroup은 CUDA의 Warp에 대응하는 개념으로,
GPU 하드웨어가 동시에 SIMD 실행하는 invocation 그룹입니다.
Subgroup 크기는 GPU마다 다르며(NVIDIA: 32, AMD: 64/32, Intel: 8~32),
gl_SubgroupSize와 subgroupBallot() 등 내장 함수로 워프 수준 최적화를 수행합니다.
| 기능 | Vulkan 버전/확장 | CUDA 대응 | 설명 |
|---|---|---|---|
| Subgroup Operations | 1.1 코어 | Warp Intrinsics | 셔플, 투표, 리덕션 — 공유메모리 없이 워프 내 통신 |
| Push Constants | 1.0 코어 | 커널 인자 | 커맨드 버퍼에 인라인 상수 (최대 128~256B) |
| Specialization Constants | 1.0 코어 | 템플릿 파라미터 | 파이프라인 생성 시 SPIR-V 상수 주입 (JIT 최적화) |
| Descriptor Indexing | 1.2 코어 | — | 런타임 배열 인덱싱으로 바인딩리스 리소스 접근 |
| Buffer Device Address | 1.2 코어 | GPU 포인터 | GPU 메모리 주소를 정수로 전달 (포인터 산술) |
| Timeline Semaphore | 1.2 코어 | CUDA Event | 단조 증가 카운터로 세밀한 CPU/GPU 동기화 |
| Cooperative Matrix | VK_KHR_cooperative_matrix | WMMA / MMA | Tensor Core/Matrix Core 접근 (행렬 FMA) |
| Mesh Shaders | VK_EXT_mesh_shader | — | Compute-like 메시 처리 (그래픽 파이프라인) |
| Shader Int8/Float16 | 1.2 코어 | half/char | 저정밀도 연산, ML 추론 가속 |
/* Subgroup 리덕션 — 공유 메모리 없이 워크그룹 합 계산 */
#version 450
#extension GL_KHR_shader_subgroup_arithmetic : enable
layout(local_size_x = 256) in;
layout(set = 0, binding = 0) readonly buffer Input { float data[]; };
layout(set = 0, binding = 1) buffer Output { float result[]; };
shared float partialSums[8]; /* 256/32 = 8 subgroups */
void main() {
uint idx = gl_GlobalInvocationID.x;
float val = data[idx];
/* 1단계: Subgroup 내 합 (Warp 리덕션, 레지스터 수준) */
float subgroupSum = subgroupAdd(val);
/* 2단계: 각 Subgroup의 lane 0이 부분합 저장 */
if (subgroupElect())
partialSums[gl_SubgroupID] = subgroupSum;
barrier();
/* 3단계: 첫 Subgroup이 최종 합 계산 */
if (gl_SubgroupID == 0) {
float v = (gl_SubgroupInvocationID < gl_NumSubgroups)
? partialSums[gl_SubgroupInvocationID] : 0.0;
float total = subgroupAdd(v);
if (subgroupElect())
result[gl_WorkGroupID.x] = total;
}
}
/* Specialization Constants — 파이프라인 생성 시 상수 주입 */
#version 450
/* 컴파일 시 값이 결정되지 않고, VkSpecializationInfo로 주입 */
layout(constant_id = 0) const uint BLOCK_SIZE = 256;
layout(constant_id = 1) const uint ALGORITHM = 0;
layout(local_size_x_id = 0) in; /* local_size를 상수로 지정 */
void main() {
if (ALGORITHM == 0) {
/* 알고리즘 A — 상수 접기로 데드 코드 제거됨 */
} else {
/* 알고리즘 B */
}
}
coopMatLoad, coopMatMulAdd, coopMatStore로 MMA(Matrix Multiply-Accumulate)를 수행합니다.
NVIDIA(Volta+), AMD(RDNA3/CDNA), Intel(Xe-HPC)에서 지원되며,
크로스 벤더 ML 추론 가속에 유용합니다. 다만 아직 확정 확장(KHR)은 2024년 확정되었으며,
드라이버 지원 범위는 vulkaninfo로 확인하세요.
Vulkan Compute 디버깅
Vulkan은 드라이버가 에러 검증을 하지 않는 저수준 API이므로, Validation Layers를 활성화하여 API 오용을 검출하는 것이 필수적입니다.
| 도구 | 용도 | 핵심 기능 |
|---|---|---|
VK_LAYER_KHRONOS_validation | API 유효성 검사 | 잘못된 파라미터, 동기화 오류, 메모리 누수 탐지 |
VK_LAYER_KHRONOS_synchronization2 | 동기화 검증 | 배리어 누락, 레이스 컨디션 경고 |
| RenderDoc | GPU 캡처/리플레이 | Compute Dispatch 상태 검사, 버퍼 내용 확인 |
| Nsight Graphics | NVIDIA GPU 프로파일링 | SM 활용률, 메모리 대역폭, 워프 분석 |
| Radeon GPU Profiler (RGP) | AMD GPU 프로파일링 | 파이프라인 타임라인, 웨이브 오큐펀시 |
| GPA (Intel) | Intel GPU 프로파일링 | EU 활용률, 메모리 대역폭 |
vulkaninfo | GPU 기능 조회 | 확장, 제한, 메모리 타입, 큐 패밀리 |
spirv-val | SPIR-V 검증 | 셰이더 바이트코드 유효성 검사 |
# Validation Layer 활성화 (환경 변수)
VK_INSTANCE_LAYERS=VK_LAYER_KHRONOS_validation ./my_vulkan_app
# GPU 선택 (다중 GPU 시스템)
VK_ICD_FILENAMES=/usr/share/vulkan/icd.d/radeon_icd.x86_64.json ./my_app
# Mesa 드라이버 디버그 (RadV)
RADV_DEBUG=info,preoptir ./my_vulkan_app # 드라이버 정보 + 셰이더 IR 덤프
RADV_PERFTEST=nosam ./my_vulkan_app # 성능 실험 플래그
# Mesa 드라이버 디버그 (ANV — Intel)
INTEL_DEBUG=cs ./my_vulkan_app # Compute Shader 컴파일 로그
# RenderDoc CLI 캡처
renderdoccmd capture --wait-for-exit ./my_vulkan_app
# → .rdc 파일 생성 → GUI에서 Compute Dispatch 분석
# vulkaninfo — 디바이스 Compute 제한 확인
vulkaninfo 2>/dev/null | grep -A5 "maxComputeWorkGroupSize"
# maxComputeWorkGroupSize[0] = 1024
# maxComputeWorkGroupSize[1] = 1024
# maxComputeWorkGroupSize[2] = 64
# maxComputeWorkGroupInvocations = 1024
# maxComputeSharedMemorySize = 49152 (48 KB)
/dev/dri/renderD128의 ioctl로 변환됩니다.
DRM 드라이버가 GPU 커맨드를 검증(command validation)한 뒤 하드웨어 링 버퍼(ring buffer)에 삽입합니다.
GPU fence 완료 시 dma_fence가 시그널되어 VkFence가 해제됩니다.
Mesa 드라이버의 SPIR-V 처리 흐름: SPIR-V → spirv_to_nir() → NIR 최적화 패스 →
백엔드 ISA 생성(AMD: ACO 컴파일러 → GCN/RDNA ISA, Intel: Brw 컴파일러 → EU ISA).
Vulkan Compute 실전 사용 사례
Vulkan Compute는 크로스 플랫폼 GPU 가속이 필요하면서 벤더 종속성을 피하고 싶은 시나리오에서 사용됩니다. 특히 모바일/임베디드 ML 추론, 영상 처리, 게임 엔진 물리/파티클, 그래픽 후처리 분야에서 활발합니다.
| 분야 | 대표 프로젝트 | 왜 Vulkan Compute? |
|---|---|---|
| ML 추론 (모바일) | ncnn, MNN, ONNX Runtime | ARM Mali/Adreno에서 CUDA 불가, OpenCL 제한적 |
| ML 추론 (데스크톱) | llama.cpp (ggml), Kompute | NVIDIA/AMD/Intel 모든 GPU에서 LLM 추론 |
| 영상/이미지 처리 | FFmpeg (Vulkan 필터), darktable | 하드웨어 디코더(VkVideoDecodeKHR)와 통합 |
| 과학 계산 | VkFFT, VkCV | 크로스 벤더 FFT, 이미지 처리 |
| 게임 엔진 | Godot, Unreal Engine 5 | 그래픽과 컴퓨트 동일 API, 큐 오버랩 |
| UI 렌더링 | Zed (GPU UI), Flutter | GPU 가속 텍스트/레이아웃 연산 |
| 블록체인 | GPU 마이너 | 크로스 벤더 해시 연산 |
| 항목 | CUDA | Vulkan Compute | OpenCL | ROCm/HIP | oneAPI/SYCL |
|---|---|---|---|---|---|
| 벤더 | NVIDIA 전용 | 크로스 벤더 | 크로스 벤더 | AMD (+ NVIDIA via HIP) | Intel (+ 크로스) |
| 추상화 수준 | 중간 | 매우 낮음 | 중간 | 중간 (CUDA 호환) | 높음 (C++17) |
| 셰이더/커널 | CUDA C (.cu) | SPIR-V (GLSL/HLSL) | OpenCL C / SPIR-V | HIP C++ (.hip) | SYCL/DPC++ |
| Tensor Core | WMMA, MMA PTX | VK_KHR_cooperative_matrix | — | Matrix Core (MFMA) | XMX |
| 디버깅 도구 | cuda-gdb, NSight | Validation Layer, RenderDoc | — | rocgdb, rocprof | oneAPI debugger |
| AI 생태계 | cuDNN, TensorRT, NCCL | ncnn, Kompute | — | MIOpen, RCCL | oneDNN |
| 보일러플레이트 | ~20줄 | ~300줄 | ~100줄 | ~20줄 | ~30줄 |
| 모바일 GPU | 불가 | Mali, Adreno, PowerVR | 제한적 | 불가 | 불가 |
kp::Manager가 디바이스 초기화, 메모리 할당, 파이프라인 생성을 자동화하여,
CUDA 수준의 간결함으로 크로스 벤더 GPU 컴퓨트를 작성할 수 있습니다.
llama.cpp의 Vulkan 백엔드(ggml-vulkan)도 유사한 추상화를 내부적으로 구현하여,
NVIDIA/AMD/Intel GPU에서 LLM 추론을 수행합니다.
ROCm / HIP — AMD GPU 컴퓨트
ROCm(Radeon Open Compute)은 AMD의 오픈소스 GPU 컴퓨트 플랫폼입니다.
HIP(Heterogeneous Interface for Portability)은 CUDA와 호환되는 API를 제공해
CUDA 코드를 AMD GPU용으로 이식하기 용이합니다.
커널 레벨에서는 /dev/kfd(KFD — Kernel Fusion Driver)를 통해 GPU와 통신하며,
amdgpu DRM 드라이버가 GFX/SDMA/VCN 등 IP 블록을 관리합니다.
Intel oneAPI / Level Zero
Intel oneAPI는 CPU·GPU·FPGA를 통합하는 오픈 표준 프로그래밍 플랫폼입니다. Level Zero는 그 중 GPU와 직접 통신하는 저수준 API로, DRM render node(xe/i915 드라이버) 위에서 동작합니다. SYCL/DPC++ 컴파일러가 Level Zero를 백엔드로 사용합니다.
Level Zero 아키텍처
Level Zero API 핵심 객체
/* Level Zero 핵심 API 예제 (행렬 곱) */
#include <level_zero/ze_api.h>
/* 1. 초기화 및 디바이스 선택 */
zeInit(ZE_INIT_FLAG_GPU_ONLY);
ze_driver_handle_t hDriver;
ze_device_handle_t hDevice;
ze_context_handle_t hContext;
zeDriverGet(&driverCount, &hDriver);
zeDeviceGet(hDriver, &deviceCount, &hDevice);
zeContextCreate(hDriver, &ctxDesc, &hContext);
/* 2. GPU 메모리 할당 */
ze_device_mem_alloc_desc_t memDesc = {
.stype = ZE_STRUCTURE_TYPE_DEVICE_MEM_ALLOC_DESC,
.flags = ZE_DEVICE_MEM_ALLOC_FLAG_BIAS_CACHED,
};
void *d_A, *d_B, *d_C;
zeMemAllocDevice(hContext, &memDesc, N*N*sizeof(float), 64, hDevice, &d_A);
/* 3. 커맨드 리스트 생성 및 커널 제출 */
ze_command_list_handle_t hCmdList;
zeCommandListCreate(hContext, hDevice, &cmdListDesc, &hCmdList);
zeCommandListAppendLaunchKernel(
hCmdList, hKernel,
&groupCount, /* dispatch 크기 */
hSignalEvent, /* 완료 시그널 (ze_event) */
0, NULL /* 대기 이벤트 없음 */
);
zeCommandListClose(hCmdList);
zeCommandQueueExecuteCommandLists(hCmdQueue, 1, &hCmdList, hFence);
zeFenceHostSynchronize(hFence, UINT64_MAX); /* CPU 대기 */
Intel XMX — AI 행렬 가속 유닛
Intel Arc GPU(Xe-HPG 마이크로아키텍처)부터 탑재된 XMX(Xe Matrix eXtensions)는 행렬 곱을 하드웨어에서 가속하는 전용 유닛입니다. 딥러닝 추론 성능을 크게 향상시킵니다.
| 레벨 | 크기 | 접근 범위 | 지연 |
|---|---|---|---|
| 레지스터 파일 | 256KB/EU | 단일 Execution Unit | 1 사이클 |
| L1 캐시 (SLM) | 128KB/서브슬라이스 | 서브슬라이스 내 공유 | ~10 사이클 |
| L2 캐시 | 16MB (Arc A770) | 칩 전체 공유 | ~100 사이클 |
| VRAM (GDDR6/LPDDR5) | 8~16GB | 전체 GPU | ~500 사이클 |
# Intel GPU 컴퓨트 환경 확인
# xe 드라이버 (Linux 6.2+, Intel Arc/Meteor Lake/Lunar Lake)
ls /dev/dri/renderD*
dmesg | grep -i "xe\|i915"
# Level Zero 디바이스 정보
zello_world # Level Zero 기본 테스트 도구
# Intel GPU top (GPU 사용률 모니터링)
intel_gpu_top
# DPC++ 컴파일 (SYCL → SPIR-V → Intel GPU ISA)
icpx -fsycl -o matmul matmul.cpp
# OpenCL + Rusticl 또는 Intel NEO 드라이버로 Intel GPU 사용
OCL_ICD_FILENAMES=/usr/lib/intel-opencl/libigdrcl.so clinfo
- DPC++ (Intel LLVM 기반) — SYCL C++ 소스 파싱
- SPIR-V 중간 표현 생성 (
-fsycl-targets=spir64) - Intel GPU OpenCL 드라이버 (NEO/ocloc) — SPIR-V → GPU ISA 컴파일
- Level Zero / OpenCL runtime — ISA를 GPU에 로딩 및 실행
/dev/dri/renderD128ioctl → DRM xe/i915 드라이버 → 하드웨어
참고 사항
drivers/gpu/drm/— DRM 코어 + 모든 GPU 드라이버drivers/accel/— compute accelerator 드라이버include/drm/— DRM 헤더 파일include/uapi/drm/— 유저 공간 API (ioctl, 구조체)drivers/dma-buf/— DMA-BUF 프레임워크Documentation/gpu/— 커널 공식 GPU 문서Documentation/accel/— 커널 공식 accelerator 문서
- DRM UAPI — primary/render node, client capability, dumb buffer 제약
- DRM KMS — atomic modesetting, plane/connector/CRTC helper 계약
- DRM Memory Management — GEM, PRIME, VRAM helper, TTM, GPUVA
- Compute Accelerators — accel 디바이스 노드와 드라이버 모델
- GPU Driver Documentation — 드라이버별 하위 문서 색인
- DRM Panic — 패닉 화면 출력 설계와 제약
- Fence 시그널링 규칙 —
dma_fence는 반드시 유한 시간 내에 시그널되어야 합니다. 무한 대기 fence는 시스템 전체를 교착시킬 수 있습니다 - 메모리 매핑 주의 — GPU VRAM을 WC(Write-Combining)로 매핑할 때 캐시 일관성에 주의.
ioremap_wc()사용 - Atomic check 무결성 —
atomic_check에서 부작용(side effect) 금지. 검증만 수행하고, 하드웨어 변경은atomic_commit에서 수행 - KMS 핫플러그 — Connector 상태 변경은
drm_kms_helper_hotplug_event()로 유저 공간에 통보. HPD(Hot Plug Detect) IRQ 핸들러에서 호출 - GPU 리셋 격리 — 하나의 컨텍스트 행이 다른 컨텍스트에 영향을 주지 않도록 per-engine 또는 per-context 리셋 구현 권장
- 펌웨어 로딩 — 최신 GPU는 대부분
request_firmware()로 마이크로코드를 로딩합니다. initramfs에 펌웨어 포함 필요 (CONFIG_EXTRA_FIRMWARE)
관련 문서
이 주제와 관련된 다른 문서를 더 깊이 이해하고 싶다면 다음을 참고하세요.