drm_sched (GPU Job 스케줄러(Scheduler))
drm_sched를 Linux DRM 계층의 공통 GPU 작업 스케줄러로 심층 분석합니다. 다중 프로세스(Process) job 큐 공정성(Fairness), entity 기반 우선순위(Priority) 제어, dma_fence 의존성 해석과 동기화, submit backlog와 워커 스레드(Thread) 운영, timeout detection 및 GPU reset 복구 경로, amdgpu/i915 등 드라이버별 통합 패턴, 긴 작업과 짧은 작업 혼재 시 tail latency 관리, tracepoint/debugfs를 활용한 스케줄링 병목(Bottleneck) 분석까지 GPU 안정성 확보에 필요한 실전 내용을 다룹니다.
핵심 요약
- drm_gpu_scheduler — 하나의 GPU 엔진(링 버퍼(Ring Buffer))을 관리하는 스케줄러 인스턴스
- drm_sched_entity — 클라이언트별 Job 큐. 하나의 DRM 컨텍스트 = 하나의 entity
- drm_sched_job — GPU에 제출할 작업 하나. 의존 fence 목록 보유
- dma_fence — Job 완료 신호. CPU/GPU 간, GPU 엔진 간 동기화에 사용
- Priority — KERNEL > HIGH > NORMAL > LOW 4단계 우선순위
- TDR (Timeout Detection Recovery) — Job 타임아웃 시 GPU 리셋 후 복구
- i915 GuC — Intel GPU 마이크로컨트롤러가 스케줄링을 담당하는 방식
- ring buffer / IB — GPU 명령 버퍼(Buffer) 구조 — 링 버퍼와 Indirect Buffer
단계별 이해
- drm_gpu_scheduler 구조 파악
스케줄러, entity, job의 계층 관계를 이해합니다. - Job 라이프사이클 추적
push_job → scheduled → run → fence signal 전체 흐름을 따라갑니다. - dma_fence 동기화 이해
fence_ops와 signaling 메커니즘을 파악합니다. - 우선순위 큐 동작 확인
여러 entity가 어떻게 라운드 로빈(Round Robin)으로 스케줄되는지 이해합니다. - TDR 흐름 분석
timedout_job 콜백(Callback)과 GPU 리셋 절차를 학습합니다. - ftrace로 Job 추적
drm_sched 이벤트를 ftrace/bpftrace로 관찰합니다.
drm_sched 개요
GPU는 CPU와 달리 수천 개의 스레드를 병렬로 처리하는 하드웨어입니다. 여러 프로세스가 동시에 GPU를 사용하려 할 때, 각 프로세스의 명령 버퍼를 공정하고 효율적으로 GPU 하드웨어에 제출하는 소프트웨어가 필요합니다. 이것이 drm_sched의 역할입니다.
| 특성 | CPU 스케줄러 | drm_sched (GPU 스케줄러) |
|---|---|---|
| 스케줄 단위 | task_struct (프로세스/스레드) | drm_sched_job (GPU 커맨드 버퍼) |
| 컨텍스트 전환 | 레지스터(Register) save/restore | 링 버퍼 포인터 전환 |
| 동기화 | spinlock, mutex, semaphore | dma_fence (GPU 인터럽트(Interrupt) 기반) |
| 타임슬라이싱 | jiffies 기반 선점(Preemption) | Job 단위 (Job 중단은 드문 편) |
| 우선순위 | nice값, cgroup | DRM_SCHED_PRIORITY_* 4단계 |
| 장애 복구 | 프로세스 종료 | GPU 리셋 (TDR) |
drivers/gpu/drm/scheduler/에 위치하며,
GPU 드라이버(amdgpu, i915, nouveau, panfrost, lima 등)에서 공통으로 사용합니다.
헤더는 include/drm/gpu_scheduler.h에 있습니다.
drm_sched 아키텍처
핵심 구조체(Struct)
#include <drm/gpu_scheduler.h>
/* 1. drm_gpu_scheduler — 하나의 GPU 엔진 관리 */
struct drm_gpu_scheduler {
const struct drm_sched_backend_ops *ops; /* 드라이버 콜백 */
struct drm_sched_rq sched_rq[DRM_SCHED_PRIORITY_COUNT]; /* 우선순위별 큐 */
wait_queue_head_t wake_up_worker; /* 스케줄러 워커 웨이크업 */
struct task_struct *thread; /* kthread */
struct list_head pending_list; /* 실행 중인 Job 목록 */
long timeout; /* Job 타임아웃 (jiffies) */
atomic_t hw_rq_count; /* 실행 중 Job 수 */
const char *name; /* 디버그용 이름 */
};
/* 2. drm_sched_entity — 클라이언트별 Job 큐 */
struct drm_sched_entity {
struct spsc_queue job_queue; /* 단일 생산자-소비자 큐 */
struct drm_gpu_scheduler **sched_list; /* 사용 가능한 스케줄러 목록 */
unsigned int num_sched_list; /* 스케줄러 수 */
enum drm_sched_priority priority; /* 우선순위 */
struct dma_fence *dependency; /* 현재 대기 중인 의존 fence */
};
/* 3. drm_sched_job — GPU에 제출할 작업 */
struct drm_sched_job {
struct spsc_node queue_node; /* entity 큐의 노드 */
struct drm_sched_entity *entity; /* 소속 entity */
struct dma_fence s_fence; /* 완료 fence */
struct dma_fence *dependency; /* 선행 Job fence */
uint64_t id; /* Job 고유 ID */
struct list_head list; /* pending_list 노드 */
};
Job 라이프사이클
drm_sched_job이 생성되어 GPU에서 실행 완료되기까지의 전체 흐름을 설명합니다.
Job API 사용 예시
#include <drm/gpu_scheduler.h>
/* GPU 드라이버에서 Job 생성과 제출 흐름 */
static int my_gpu_submit_job(struct my_gpu_ctx *ctx,
struct my_gpu_cmd_buf *cmdbuf)
{
struct my_gpu_job *job;
struct dma_fence *fence;
int ret;
job = kzalloc(sizeof(*job), GFP_KERNEL);
/* 1. drm_sched_job 초기화 (entity와 연결) */
ret = drm_sched_job_init(&job->base, ctx->entity, 1, ctx);
if (ret) goto err;
/* 2. 의존 fence 추가 (이전 Job 완료 후 실행) */
ret = drm_sched_job_add_dependency(&job->base, dep_fence);
/* 3. Job 준비 (fence 서명 준비) */
fence = drm_sched_job_arm(&job->base);
/* 4. Job을 entity 큐에 추가 */
drm_sched_entity_push_job(&job->base);
/* fence를 유저스페이스에 반환 (syncobj 또는 out_fence) */
drm_syncobj_replace_fence(syncobj, fence);
dma_fence_put(fence);
return 0;
err:
kfree(job);
return ret;
}
/* drm_sched_backend_ops — GPU 드라이버가 구현 */
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, /* Job 완료 후 메모리 해제 */
};
dma_fence 기반 동기화
dma_fence는 GPU 작업 완료를 나타내는 커널 동기화 프리미티브입니다.
CPU가 GPU 완료를 기다리거나, GPU 엔진 간 순서를 보장하거나,
유저스페이스에 완료를 통보하는 모든 경우에 dma_fence가 사용됩니다.
fence_ops 구현
#include <linux/dma-fence.h>
/* GPU 드라이버별 fence 오퍼레이션 */
static const struct dma_fence_ops my_fence_ops = {
.get_driver_name = my_fence_get_driver_name,
.get_timeline_name = my_fence_get_timeline_name,
.enable_signaling = my_fence_enable_signaling, /* IRQ 활성화 */
.release = my_fence_release,
};
/* GPU 완료 인터럽트 핸들러에서 fence 시그널 */
static irqreturn_t my_gpu_irq_handler(int irq, void *data)
{
struct my_gpu *gpu = data;
struct my_job *job;
job = my_get_completed_job(gpu);
if (job) {
/* fence 시그널 → 대기 중인 CPU 스레드 깨움 */
dma_fence_signal(&job->fence);
/* drm_sched에 Job 완료 알림 */
drm_sched_job_cleanup(&job->base);
}
return IRQ_HANDLED;
}
/* CPU에서 GPU 완료 대기 */
int wait_for_gpu(struct dma_fence *fence, int64_t timeout_ns)
{
return dma_fence_wait_timeout(fence, true,
nsecs_to_jiffies(timeout_ns));
}
fence_chain — 순서 보장(Ordering)
/* dma_fence_chain: 여러 fence를 순서대로 연결 */
struct dma_fence_chain *chain;
chain = dma_fence_chain_alloc();
dma_fence_chain_init(chain, prev_fence, cur_fence, seqno);
/* 이후 cur_fence 대신 chain fence를 syncobj에 저장 */
/* → 이전 fence가 완료돼야 cur_fence도 완료로 처리 */
Priority Run-Queue
drm_sched는 4개의 우선순위별 run-queue를 유지하고, 스케줄러 kthread가 높은 우선순위 큐부터 라운드 로빈으로 entity를 선택합니다.
/* 우선순위 레벨 (include/drm/gpu_scheduler.h) */
enum drm_sched_priority {
DRM_SCHED_PRIORITY_MIN = 0, /* 가장 낮음 */
DRM_SCHED_PRIORITY_LOW = 0,
DRM_SCHED_PRIORITY_NORMAL = 1, /* 기본값 */
DRM_SCHED_PRIORITY_HIGH = 2, /* 실시간 렌더링 */
DRM_SCHED_PRIORITY_KERNEL = 3, /* 커널 작업 (리셋 등) */
DRM_SCHED_PRIORITY_COUNT,
};
/* entity 우선순위 변경 */
drm_sched_entity_set_priority(entity, DRM_SCHED_PRIORITY_HIGH);
/* 스케줄러 main loop (sched_thread 함수 내부 핵심 로직) */
while (!kthread_should_stop()) {
/* 높은 우선순위 큐부터 순서대로 entity 선택 */
entity = drm_sched_select_entity(sched);
if (!entity) {
wait_event_interruptible(sched->wake_up_worker, ...);
continue;
}
/* entity에서 Job 꺼내 실행 */
sched_job = drm_sched_entity_pop_job(entity);
fence = sched->ops->run_job(sched_job); /* GPU에 제출 */
drm_sched_job_begin(sched_job);
}
스케줄러 초기화와 해제
GPU 드라이버가 drm_sched를 사용하려면 엔진(링)별로 drm_gpu_scheduler를 초기화하고,
클라이언트(GEM context)별로 drm_sched_entity를 생성해야 합니다.
초기화 순서를 잘못 하면 Job 제출 시 커널 패닉(Kernel Panic)이 발생할 수 있습니다.
drm_sched_init() 호출
#include <drm/gpu_scheduler.h>
/* GPU 드라이버 probe 시 엔진별 스케줄러 초기화 */
static int my_gpu_sched_init(struct my_gpu *gpu)
{
int ret;
/* GFX 엔진 스케줄러 */
ret = drm_sched_init(&gpu->sched_gfx,
&my_sched_ops, /* 백엔드 콜백 */
NULL, /* submit_wq (NULL=내부 생성) */
1, /* hw_submission: 동시 실행 가능한 Job 수 */
0, /* hang_limit: 0=무제한 리셋 */
msecs_to_jiffies(10000), /* timeout: 10초 */
NULL, /* timeout_wq */
NULL, /* score (부하 분산용) */
"my_gpu_gfx", /* 디버그 이름 */
gpu->dev->dev);
if (ret)
return ret;
/* DMA 엔진 스케줄러 (별도 인스턴스) */
ret = drm_sched_init(&gpu->sched_dma,
&my_dma_sched_ops,
NULL, 2, 0,
msecs_to_jiffies(5000),
NULL, NULL,
"my_gpu_dma",
gpu->dev->dev);
return ret;
}
/* 드라이버 제거 시 해제 — 반드시 entity보다 나중에 */
static void my_gpu_sched_fini(struct my_gpu *gpu)
{
drm_sched_fini(&gpu->sched_dma);
drm_sched_fini(&gpu->sched_gfx);
}
Entity 초기화와 해제
/* 클라이언트 GEM context 생성 시 entity 초기화 */
static int my_ctx_create(struct my_gpu *gpu,
struct my_ctx *ctx)
{
struct drm_gpu_scheduler *scheds[] = {
&gpu->sched_gfx, /* 부하 분산 시 여러 스케줄러 지정 가능 */
};
return drm_sched_entity_init(&ctx->entity,
DRM_SCHED_PRIORITY_NORMAL,
scheds,
ARRAY_SIZE(scheds),
NULL); /* guilty 플래그 */
}
/* GEM context 파괴 시 entity 정리 */
static void my_ctx_destroy(struct my_ctx *ctx)
{
/* entity 큐에 남은 Job이 완료될 때까지 대기 후 해제 */
drm_sched_entity_destroy(&ctx->entity);
}
hw_submission은 GPU 하드웨어가 동시에 처리할 수 있는 Job 수입니다.
이 값을 너무 크게 설정하면 타임아웃 감지가 늦어지고, 너무 작게 설정하면 GPU 파이프라인(Pipeline)이 비어 성능이 저하됩니다.
amdgpu는 링당 보통 256을, panfrost는 1~2를 사용합니다.
Entity 마이그레이션과 부하 분산(Load Balancing)
동일한 유형의 GPU 엔진이 여러 개 있을 때(예: amdgpu의 compute ring 0~7),
drm_sched는 entity를 가장 부하가 적은 스케줄러로 자동 마이그레이션할 수 있습니다.
이는 drm_sched_entity_init()에 복수의 스케줄러를 전달하여 활성화합니다.
/* 부하 분산: 여러 compute 엔진에 entity 분배 */
struct drm_gpu_scheduler *compute_scheds[8];
for (int i = 0; i < 8; i++)
compute_scheds[i] = &adev->gfx.compute_sched[i];
/* entity 초기화 시 모든 compute 스케줄러를 후보로 전달 */
drm_sched_entity_init(&ctx->compute_entity,
DRM_SCHED_PRIORITY_NORMAL,
compute_scheds,
8, /* num_sched_list */
NULL);
/* drm_sched_entity_pop_job() 내부에서 부하 분산 수행:
* 1. entity->num_sched_list > 1이면
* 2. 각 스케줄러의 score(atomic_t)를 비교
* 3. 가장 score가 낮은 스케줄러로 entity 이동
* 4. run_queue에서 entity를 제거하고 새 run_queue에 추가
*/
drm_sched_entity_pop_job()에서 Job을 꺼낼 때마다
현재 스케줄러의 score(pending Job 수 기반)를 다른 후보 스케줄러와 비교합니다.
더 낮은 score의 스케줄러가 있으면 entity를 이동시켜, Job이 실제로 GPU에 제출될 때
가장 여유 있는 엔진이 선택됩니다.
선점 (Preemption)
GPU 선점은 현재 실행 중인 Job을 중단하고 더 높은 우선순위 Job을 먼저 실행하는 기능입니다. drm_sched는 소프트웨어 수준의 선점(Job 경계에서만)과 하드웨어 선점(컨텍스트 저장/복원)을 지원합니다.
/* drm_sched_stop() — 하드웨어 이상 또는 선점 시 스케줄러 일시 중단 */
void drm_sched_stop(struct drm_gpu_scheduler *sched,
struct drm_sched_job *bad)
{
/* kthread 중단 */
kthread_park(sched->thread);
/* pending_list에서 완료되지 않은 Job 제거 */
list_for_each_entry_safe(s_job, tmp, &sched->pending_list, list) {
if (s_job == bad) break;
drm_sched_fence_scheduled(s_job->s_fence, NULL);
}
}
/* drm_sched_start() — 복구 후 스케줄러 재시작 */
void drm_sched_start(struct drm_gpu_scheduler *sched, bool full_recovery)
{
/* GPU 리셋 후 남은 Job 재제출 */
if (full_recovery) {
list_for_each_entry(s_job, &sched->pending_list, list)
drm_sched_fence_scheduled(s_job->s_fence, NULL);
}
kthread_unpark(sched->thread);
}
TDR (Timeout Detection Recovery)
GPU hang(행)은 GPU가 명령 처리를 멈추는 심각한 상황입니다.
drm_sched는 delayed work를 통해 Job 완료 타임아웃을 감지하고,
드라이버의 timedout_job 콜백을 호출하여 GPU를 리셋합니다.
TDR 흐름
/* drm_sched가 타임아웃 감지 시 호출하는 delayed work */
static void drm_sched_job_timedout(struct work_struct *work)
{
struct drm_gpu_scheduler *sched;
struct drm_sched_job *job;
sched = container_of(work, struct drm_gpu_scheduler,
work_tdr.work);
/* pending_list의 첫 번째 Job이 타임아웃 대상 */
job = list_first_entry_or_null(&sched->pending_list,
struct drm_sched_job, list);
if (job) {
/* 드라이버 timedout_job 콜백 호출 */
drm_sched_stop(sched, job);
enum drm_gpu_sched_stat stat =
sched->ops->timedout_job(job);
if (stat == DRM_GPU_SCHED_STAT_ENODEV) {
/* 하드웨어 불량 — 스케줄러 영구 중단 */
return;
}
drm_sched_start(sched, stat != DRM_GPU_SCHED_STAT_NOMINAL);
}
}
/* 드라이버 timedout_job 구현 예시 (amdgpu) */
static enum drm_gpu_sched_stat
amdgpu_job_timedout(struct drm_sched_job *job)
{
struct amdgpu_job *amdgpu_job = to_amdgpu_job(job);
/* 1. GPU hang 덤프 수집 */
amdgpu_device_gpu_recover(adev, amdgpu_job, &reset_context);
/* 2. GPU 풀 리셋 */
amdgpu_asic_reset(adev);
/* 3. 복구 완료 반환 */
return DRM_GPU_SCHED_STAT_NOMINAL;
}
공정성과 기아(Starvation) 방지 (Fairness)
drm_sched는 여러 프로세스가 GPU를 공유할 때 특정 프로세스가 GPU를 독점하지 않도록 라운드 로빈 기반 공정성 메커니즘을 제공합니다. 같은 우선순위의 entity들은 순서대로 Job을 하나씩 꺼내어 GPU에 제출됩니다.
Run-Queue 라운드 로빈
/* drm_sched_rq 구조 (우선순위별 큐) */
struct drm_sched_rq {
spinlock_t lock;
struct list_head entities; /* entity 리스트 */
struct drm_sched_entity *current_entity; /* 마지막 선택된 entity */
};
/* entity 선택 로직 (sched_main.c) */
static struct drm_sched_entity *
drm_sched_rq_select_entity_rr(struct drm_sched_rq *rq)
{
struct drm_sched_entity *entity;
spin_lock(&rq->lock);
/* current_entity 다음부터 탐색 → 라운드 로빈 보장 */
entity = rq->current_entity ?
list_next_entry(rq->current_entity, list) :
list_first_entry(&rq->entities, ...);
/* Job이 있는 entity를 찾을 때까지 순회 */
list_for_each_entry_continue(entity, &rq->entities, list) {
if (drm_sched_entity_is_ready(entity)) {
rq->current_entity = entity;
spin_unlock(&rq->lock);
return entity;
}
}
spin_unlock(&rq->lock);
return NULL;
}
| 시나리오 | 동작 | 결과 |
|---|---|---|
| Entity A: 100개 Job, Entity B: 1개 Job | 라운드 로빈으로 교대 선택 | B의 Job이 A의 100번째 Job보다 먼저 제출 |
| HIGH 우선순위 entity 존재 | HIGH run_queue를 먼저 확인 | NORMAL entity는 HIGH가 비어야 실행 |
| 모든 entity의 Job이 fence 대기 | 스케줄러 kthread sleep | fence signal 시 wake_up_worker |
| entity 큐 오버플로우 | spsc_queue가 꽉 차면 push 차단 | 유저 프로세스가 ioctl에서 대기 |
메모리 관리(Memory Management)와 Job 제출
GPU Job은 GPU가 접근할 메모리(커맨드 버퍼, 텍스처, 버텍스 버퍼 등)를 사용합니다. Job 실행 중에 해당 메모리가 evict되거나 해제되면 GPU hang이 발생하므로, drm_sched와 GEM/TTM 메모리 관리자는 긴밀하게 연동합니다.
BO Reservation과 Job 의존성
/* Job 제출 전 Buffer Object(BO) 예약 */
static int my_submit(struct my_ctx *ctx,
struct my_bo **bos, int num_bos)
{
struct drm_exec exec;
int ret;
/* 1. 모든 BO에 대한 reservation lock 획득 (데드락 방지) */
drm_exec_init(&exec, DRM_EXEC_INTERRUPTIBLE_WAIT, 0);
drm_exec_until_all_locked(&exec) {
for (int i = 0; i < num_bos; i++) {
ret = drm_exec_prepare_obj(&exec,
&bos[i]->base, 1);
drm_exec_retry_on_contention(&exec);
if (ret) goto err;
}
}
/* 2. BO에 연결된 기존 fence를 Job 의존성으로 추가 */
for (int i = 0; i < num_bos; i++) {
ret = drm_sched_job_add_implicit_dependencies(
&job->base, &bos[i]->base, true);
if (ret) goto err;
}
/* 3. Job을 arm하고 BO에 새 fence 설정 */
drm_sched_job_arm(&job->base);
for (int i = 0; i < num_bos; i++)
dma_resv_add_fence(bos[i]->base.resv,
&job->base.s_fence->finished,
DMA_RESV_USAGE_WRITE);
/* 4. Job 제출 */
drm_sched_entity_push_job(&job->base);
err:
drm_exec_fini(&exec);
return ret;
}
drm_exec은 여러 BO의 reservation lock을 데드락 없이 획득하는
헬퍼입니다. 내부적으로 ww_mutex(wound-wait)를 사용하며, contention 발생 시 자동으로 재시도합니다.
이전에 사용되던 ttm_eu_reserve_buffers()를 대체합니다.
Guilty Entity와 Ban 메커니즘
GPU hang을 반복적으로 유발하는 클라이언트(entity)를 자동으로 격리(Isolation)하는 메커니즘입니다.
hang_limit을 초과한 entity는 "guilty"로 표시되어 이후 Job이 거부됩니다.
/* entity의 guilty 플래그 설정 */
struct drm_sched_entity {
...
atomic_t *guilty; /* 공유 guilty 카운터 */
...
};
/* GPU hang 발생 시 guilty 처리 (sched_main.c 내부) */
if (sched->hang_limit &&
entity->guilty &&
atomic_inc_return(entity->guilty) > sched->hang_limit) {
/*
* 이 entity에서 제출한 Job이 hang_limit번 이상
* GPU hang을 유발 → 이후 Job은 즉시 -ECANCELED
*/
drm_sched_entity_kill(entity);
}
/* 유저스페이스에서 context guilty 상태 확인 (amdgpu 예시) */
/* amdgpu_cs_ioctl()에서 guilty entity의 Job은 -ECANCELED 반환 */
| 파라미터 | 설명 | 기본값 | 사용 예시 |
|---|---|---|---|
hang_limit | GPU hang 허용 횟수 | 0 (무제한) | amdgpu: amdgpu.lockup_timeout |
guilty | entity별 hang 카운터 | NULL (비활성) | GEM context당 1개 할당 |
timeout | Job 완료 대기 시간(Latency) | 드라이버 설정 | amdgpu: 10000ms, panfrost: 500ms |
i915 GuC vs amdgpu drm_sched 구현 비교
하드웨어 큐 관리 (링 버퍼 / IB)
GPU 드라이버는 drm_sched의 run_job 콜백에서 커맨드 버퍼를 GPU 링 버퍼에 기록합니다.
링 버퍼는 고정 크기의 원형 버퍼로, GPU의 Command Processor(CP)가 읽어서 실행합니다.
링 버퍼와 IB 구조
/* amdgpu 링 버퍼 커맨드 제출 예시 */
static struct dma_fence *amdgpu_job_run(struct drm_sched_job *sched_job)
{
struct amdgpu_job *job = to_amdgpu_job(sched_job);
struct amdgpu_ring *ring = job->ring;
/* 링 버퍼에 IB(Indirect Buffer) 실행 패킷 기록 */
amdgpu_ring_lock(ring, job->num_dw);
/* EXECUTE_INDIRECT 패킷: 실제 커맨드 버퍼 포인터 */
amdgpu_ring_write(ring, PACKET3(PACKET3_INDIRECT_BUFFER, 2));
amdgpu_ring_write(ring, lower_32_bits(job->ib.gpu_addr));
amdgpu_ring_write(ring, upper_32_bits(job->ib.gpu_addr));
amdgpu_ring_write(ring, job->ib.length_dw);
/* EOP(End-of-Pipe) 이벤트 기록 — 완료 시 인터럽트 발생 */
amdgpu_ring_write_eop_fence(ring, job->sync_seq);
amdgpu_ring_commit(ring); /* 링 버퍼 wptr 업데이트 */
return &job->base.s_fence->finished;
}
SDMA 큐 (DMA 엔진)
/* SDMA (System DMA) 큐 — 메모리 복사 전용 엔진 */
/* DMA 작업은 GFX 링과 별도의 SDMA 스케줄러 인스턴스 사용 */
/* amdgpu는 엔진 유형별로 별도 drm_gpu_scheduler 인스턴스를 생성: */
/* - adev->gfx.gfx_ring[i]: 그래픽스 링 (3D + 컴퓨팅) */
/* - adev->sdma.instance[i].ring: DMA 엔진 링 */
/* - adev->vcn.inst[i].ring_dec: 비디오 디코더 링 */
/* 각각 독립적인 drm_sched_entity와 drm_gpu_scheduler 사용 */
drm_sched를 사용하는 드라이버 비교
drm_sched는 2016년 amdgpu에서 분리된 공통 프레임워크로, 현재 다양한 GPU 및 AI 가속기 드라이버에서 사용됩니다. 각 드라이버별 구현 세부사항을 비교합니다.
| 드라이버 | GPU 유형 | 엔진 수 | hw_submission | timeout (ms) | 특징 |
|---|---|---|---|---|---|
amdgpu | AMD Radeon / Instinct | GFX+Compute+SDMA+VCN | 256 | 10000 | drm_sched의 주요 개발자, 가장 풍부한 구현 |
nouveau | NVIDIA (오픈소스) | GR+CE+NVDEC | 16 | 15000 | firmware 기반 스케줄링과 혼합 |
panfrost | ARM Mali (Midgard/Bifrost) | JS0+JS1+JS2 | 2 | 500 | Job Slot 기반, 간단한 구조 |
lima | ARM Mali (Utgard) | GP+PP | 1 | 500 | 매우 단순한 구현, PP 멀티코어 |
v3d | Broadcom VideoCore | BIN+RENDER+TFU+CSD | 1 | 10000 | 타일링 렌더러, bin/render 분리 |
etnaviv | Vivante (i.MX) | GPU 코어별 | 2 | 2000 | 임베디드 GPU, 다중 코어 지원 |
xe | Intel (차세대) | RCS+BCS+CCS+VCS | 64 | 5000 | i915 후속, GuC 기반 |
ivpu | Intel VPU (NPU) | Compute+Copy | 1 | 10000 | AI 추론용 NPU 가속기 |
drivers/gpu/drm/<드라이버>/에 위치합니다.
Job 구현 파일은 보통 *_job.c 또는 *_sched.c로 명명됩니다.
새로운 GPU 드라이버를 작성할 때는 panfrost의 구현(panfrost_job.c)이
가장 단순하여 참고하기 좋습니다.
성능 튜닝과 모범 사례
drm_sched 기반 GPU 드라이버의 성능을 최적화하기 위한 핵심 파라미터와 기법을 정리합니다.
튜닝 파라미터
| 파라미터 | 영향 | 너무 작으면 | 너무 크면 |
|---|---|---|---|
hw_submission | GPU 파이프라인 깊이 | GPU idle 시간 증가 | TDR 지연(Latency) 증가, 메모리 사용 증가 |
timeout | hang 감지 속도 | 긴 Job이 오탐으로 리셋 | 실제 hang 시 사용자 대기 시간 증가 |
num_sched_list | 부하 분산 범위 | 단일 엔진 병목 | entity 마이그레이션 오버헤드(Overhead) |
| Job 크기 (IB length) | GPU 활용률 | Job 제출 오버헤드 증가 | tail latency 증가, 공정성 저하 |
실전 튜닝 팁
# amdgpu Job 타임아웃 조정 (모듈 파라미터)
echo 30000 > /sys/module/amdgpu/parameters/lockup_timeout
# amdgpu 스케줄러별 pending Job 수 모니터링
for f in /sys/kernel/debug/dri/0/amdgpu_gpu_sched_*; do
echo "=== $(basename $f) ==="
cat "$f"
done
# GPU 엔진 사용률 확인 (amdgpu)
cat /sys/class/drm/card0/device/gpu_busy_percent
# VRAM/GTT 사용량 확인 (Job 메모리 압박 진단)
cat /sys/kernel/debug/dri/0/amdgpu_gem_info | head -20
# i915 GuC 스케줄러 통계
cat /sys/kernel/debug/dri/0/gt0/uc/guc_slpc_info
hw_submission을 높여
GPU 파이프라인을 충분히 채우는 것이 중요합니다. 반면 데스크톱 환경에서는
여러 프로세스가 GFX 엔진을 공유하므로 공정성을 위해 적절한 값을 유지합니다.
DMA-BUF 동기화와 implicit fence
GPU 간, 또는 GPU와 디스플레이 컨트롤러 간 버퍼를 공유할 때
dma-buf의 implicit fence가 drm_sched의 Job 의존성으로 자동 추가됩니다.
이를 통해 한 GPU가 렌더링 중인 버퍼를 다른 GPU가 읽는 것을 방지합니다.
/* DMA-BUF shared/exclusive fence와 drm_sched 연동 */
/* 1. import된 DMA-BUF의 fence를 Job 의존성에 추가 */
struct dma_buf *dmabuf = bo->base.import_attach->dmabuf;
struct dma_resv *resv = dmabuf->resv;
/* 읽기 접근: exclusive(write) fence만 대기 */
drm_sched_job_add_resv_dependencies(&job->base, resv,
DMA_RESV_USAGE_WRITE);
/* 쓰기 접근: 모든(read+write) fence 대기 */
drm_sched_job_add_resv_dependencies(&job->base, resv,
DMA_RESV_USAGE_READ);
/* 2. Job 완료 fence를 DMA-BUF에 기록 */
dma_resv_add_fence(resv,
&job->base.s_fence->finished,
DMA_RESV_USAGE_WRITE);
/* 이후 다른 드라이버(디스플레이, 비디오 등)가
* 같은 DMA-BUF를 사용할 때 이 fence를 자동으로 대기 */
진단 — ftrace와 bpftrace
ftrace drm_sched 이벤트
# drm_sched 추적 이벤트 목록
ls /sys/kernel/debug/tracing/events/drm_sched/
# 모든 drm_sched 이벤트 활성화
echo 1 > /sys/kernel/debug/tracing/events/drm_sched/enable
echo 1 > /sys/kernel/debug/tracing/tracing_on
cat /sys/kernel/debug/tracing/trace_pipe
# 예시 출력:
# kwin_wayland-1234 [003] drm_sched_job: sched=amdgpu_gfx.0.0 job=1234
# kwin_wayland-1234 [003] drm_run_job: sched=amdgpu_gfx.0.0 job=1234
# irq/42-amdgpu-1 [002] drm_sched_process_job: fence=0xffff... seqno=5678
bpftrace GPU Job 추적
# Job 제출 지연시간 측정 (push → GPU 실행까지)
bpftrace -e '
kprobe:drm_sched_entity_push_job {
@ts[arg0] = nsecs;
}
kprobe:drm_sched_backend_ops__run_job {
$job = (struct drm_sched_job *)arg0;
if (@ts[$job]) {
@latency = hist(nsecs - @ts[$job]);
delete(@ts[$job]);
}
}'
# GPU hang (timedout_job) 감지
bpftrace -e '
kprobe:drm_sched_job_timedout {
$sched = (struct drm_gpu_scheduler *)arg0;
printf("[TDR] GPU hang on scheduler: %s\n",
str($sched->name));
}'
# dma_fence 신호 지연 분석
bpftrace -e '
kprobe:dma_fence_signal {
@ts[arg0] = nsecs;
}
kretprobe:dma_fence_signal {
printf("fence signal: %ldns\n", nsecs - @ts[arg0]);
delete(@ts[arg0]);
}'
debugfs 진단
# amdgpu GPU 스케줄러 상태
cat /sys/kernel/debug/dri/0/amdgpu_sched_full
# GFX_SCHED_0.0: pending: 3, hw_rq: 1
# ENTITY: prio=NORMAL, jobs=2
# i915 GuC 스케줄러 상태
cat /sys/kernel/debug/dri/0/i915_guc_load_status
cat /sys/kernel/debug/dri/0/i915_scheduler_info
# dma_fence 추적 (CONFIG_DMA_FENCE_TRACE=y 필요)
echo 1 > /proc/sys/kernel/dma_fence_enable_signal_fences_debug
커널 소스 가이드
| 파일 / 디렉토리 | 설명 |
|---|---|
drivers/gpu/drm/scheduler/sched_main.c | drm_sched 핵심 — kthread, run_queue, TDR |
drivers/gpu/drm/scheduler/sched_entity.c | entity 관리 — push_job, pop_job |
drivers/gpu/drm/scheduler/sched_fence.c | drm_sched_fence — Job 완료 fence 생성 |
include/drm/gpu_scheduler.h | 공개 API — 모든 구조체와 함수 선언 |
drivers/dma-buf/dma-fence.c | dma_fence 기본 구현 |
drivers/dma-buf/dma-fence-chain.c | fence chain — 순서 보장 |
drivers/gpu/drm/amd/amdgpu/amdgpu_job.c | amdgpu drm_sched 통합 |
drivers/gpu/drm/amd/amdgpu/amdgpu_ring.c | amdgpu 링 버퍼 관리 |
drivers/gpu/drm/i915/gt/uc/intel_guc_submission.c | i915 GuC submission 드라이버 |
커널 설정
# DRM GPU 스케줄러 (GPU 드라이버 선택 시 자동)
CONFIG_DRM_SCHED=y # drm_sched 코어
CONFIG_DRM_AMDGPU=y # AMD GPU (drm_sched 주요 사용자)
CONFIG_DRM_I915=y # Intel GPU + GuC 스케줄러
CONFIG_DRM_NOUVEAU=y # Nouveau (NVIDIA 오픈소스)
CONFIG_DRM_PANFROST=y # ARM Mali GPU
# 디버그 옵션
CONFIG_DRM_SCHED_TRACEPOINTS=y # ftrace 이벤트 활성화
CONFIG_DMA_FENCE_TRACE=y # dma_fence 추적
DRM 스케줄러 작업 흐름 분석
drm_sched의 핵심 경로는 유저스페이스 Job 제출부터 GPU 실행 완료까지 이어지는 파이프라인입니다.
drm_sched_entity_push_job()으로 entity 큐에 진입한 Job이 스케줄러 메인 루프
drm_sched_main()에 의해 꺼내져 드라이버의 run_job()으로 GPU에 제출되고,
완료 인터럽트가 fence를 시그널하는 전체 흐름을 추적합니다.
코드 설명
위 다이어그램은 drm_sched의 전체 작업 흐름을 보여줍니다:
- 유저스페이스 제출 — DRM ioctl을 통해 GPU 커맨드 버퍼가 커널로 전달됩니다.
- entity_push_job() — Job이 entity의 SPSC(Single-Producer Single-Consumer) 큐에 추가되고, 스케줄러 kthread를 깨웁니다.
- drm_sched_main() 루프 — ① 의존 fence가 signal될 때까지 대기, ② 우선순위별 라운드 로빈으로 entity 선택, ③ 선택된 entity에서 Job을 꺼냅니다.
- run_job() — 드라이버 백엔드가 GPU 링 버퍼에 커맨드를 기록하고 hw_fence를 반환합니다.
- fence signal — GPU 완료 인터럽트가 hw_fence를 시그널하면, drm_sched가
finishedfence를 시그널하고free_job()으로 정리합니다.
struct drm_gpu_scheduler 필드별 해설
struct drm_gpu_scheduler는 하나의 GPU 엔진(예: GFX, Compute, SDMA)을 관리하는 핵심 구조체입니다.
커널 소스 include/drm/gpu_scheduler.h에서 정의되며, 각 필드가 스케줄러의 어떤 측면을 제어하는지 분석합니다.
/* include/drm/gpu_scheduler.h — drm_gpu_scheduler 주요 필드 */
struct drm_gpu_scheduler {
/* ── 드라이버 인터페이스 ── */
const struct drm_sched_backend_ops *ops;
/* run_job, timedout_job, free_job 콜백 테이블.
* 드라이버마다 GPU HW 접근 방식이 다르므로 ops로 추상화 */
uint32_t hw_submission_limit;
/* GPU에 동시에 보낼 수 있는 최대 Job 수.
* hw_rq_count가 이 값에 도달하면 새 Job 제출 차단 */
const char *name;
/* 디버그/tracepoint용 이름 (예: "gfx", "comp_1.0.0") */
/* ── 우선순위 큐 ── */
struct drm_sched_rq sched_rq[DRM_SCHED_PRIORITY_COUNT];
/* KERNEL(0), HIGH(1), NORMAL(2), LOW(3) 4개 run-queue.
* 높은 우선순위 큐부터 순회하며 entity를 선택 */
/* ── kthread 관리 ── */
struct task_struct *thread;
/* drm_sched_main()을 실행하는 커널 스레드.
* drm_sched_init() 시 kthread_run()으로 생성 */
wait_queue_head_t wake_up_worker;
/* kthread가 sleep하는 대기 큐.
* push_job이나 fence signal 시 wake_up() */
/* ── 실행 중 Job 추적 ── */
struct list_head pending_list;
/* GPU에 제출되어 완료 대기 중인 Job 목록.
* TDR이 이 목록의 첫 번째 Job을 타임아웃 대상으로 판별 */
spinlock_t job_list_lock;
/* pending_list 보호. IRQ 컨텍스트에서도 접근하므로 spinlock */
atomic_t hw_rq_count;
/* 현재 GPU에 제출된 Job 수.
* hw_submission_limit와 비교하여 flow control */
/* ── 타임아웃 / TDR ── */
long timeout;
/* Job 완료 타임아웃 (jiffies 단위).
* 0이면 타임아웃 비활성화 (무한 대기) */
struct delayed_work work_tdr;
/* 타임아웃 시 drm_sched_job_timedout() 실행.
* 첫 Job 제출 시 schedule_delayed_work()로 등록 */
unsigned int hang_limit;
/* 연속 hang 허용 횟수. 초과 시 entity를 guilty로 마킹 */
atomic_t _score;
/* 부하 점수 — entity 마이그레이션 시 가장 낮은 스케줄러 선택 */
bool ready;
/* false이면 스케줄러 일시 중단 (drm_sched_stop 상태) */
};
코드 설명
drm_gpu_scheduler의 필드는 크게 5가지 역할 그룹으로 나뉩니다:
- 드라이버 인터페이스 —
ops가 GPU 하드웨어 접근을 추상화합니다.hw_submission_limit은 GPU 링 버퍼 깊이에 맞춰 설정합니다 (amdgpu는 보통 256). - 우선순위 큐 —
sched_rq[]배열이 4단계 우선순위를 구현합니다. 스케줄러 루프는 인덱스 0(KERNEL)부터 순회하므로 높은 우선순위 entity가 먼저 실행됩니다. - kthread 관리 —
thread와wake_up_worker가 메인 루프의 실행과 sleep/wakeup을 제어합니다. - 실행 중 Job 추적 —
pending_list는 FIFO 순서로 Job을 유지하며, TDR의 타임아웃 판정 기준이 됩니다.hw_rq_count가hw_submission_limit에 도달하면 kthread는 sleep합니다. - 타임아웃/TDR —
work_tdrdelayed_work가timeoutjiffies 후에 발화하여drm_sched_job_timedout()를 호출합니다.
struct drm_sched_entity 필드별 해설
drm_sched_entity는 하나의 DRM 컨텍스트(프로세스 또는 GPU 채널)를 나타내며,
해당 컨텍스트에서 제출된 Job들의 큐와 스케줄링 속성을 관리합니다.
/* include/drm/gpu_scheduler.h — drm_sched_entity 주요 필드 */
struct drm_sched_entity {
/* ── 큐 연결 ── */
struct list_head list;
/* drm_sched_rq의 entities 리스트에 연결.
* 라운드 로빈 스케줄링의 순환 노드 */
struct drm_sched_rq *rq;
/* 현재 소속된 run-queue 포인터.
* entity 마이그레이션 시 변경 */
struct drm_gpu_scheduler **sched_list;
/* 사용 가능한 스케줄러 배열 (복수 엔진 부하 분산용).
* num_sched_list == 1이면 고정 엔진 */
unsigned int num_sched_list;
/* ── Job 큐 ── */
struct spsc_queue job_queue;
/* 단일 생산자(유저 스레드) - 단일 소비자(sched kthread) 큐.
* lock-free로 push/pop 가능 */
/* ── 우선순위 ── */
enum drm_sched_priority priority;
/* KERNEL / HIGH / NORMAL / LOW.
* 어떤 sched_rq에 배치될지 결정 */
/* ── 의존성 추적 ── */
struct dma_fence *dependency;
/* 현재 head job이 대기 중인 외부 fence.
* signal되면 다음 루프에서 Job을 pop */
struct dma_fence_cb cb;
/* dependency fence에 등록하는 콜백.
* fence signal → kthread wake_up 트리거 */
/* ── 실행 이력 ── */
struct dma_fence *last_scheduled;
/* 마지막으로 GPU에 제출한 Job의 fence.
* 같은 entity의 Job은 순서 보장이 필요하므로
* 이전 fence를 다음 Job의 암묵적 의존성으로 추가 */
atomic_t *guilty;
/* GPU hang을 유발한 entity 표시.
* atomic_read(guilty) == 1이면 이후 Job 즉시 실패 */
uint64_t fence_seq;
/* fence 시퀀스 번호 — Job마다 증가.
* drm_sched_fence의 seqno 생성에 사용 */
struct completion entity_idle;
/* entity 큐가 빌 때 signal. entity 해제 시
* wait_for_completion()으로 모든 Job 완료 대기 */
};
코드 설명
drm_sched_entity의 설계 핵심은 순서 보장과 부하 분산의 균형입니다:
- spsc_queue — lock-free 큐로 유저 스레드(producer)와 sched kthread(consumer) 간 경합을 최소화합니다.
- last_scheduled — 같은 entity의 Job은 항상 제출 순서대로 실행되어야 합니다. 이전 Job의 fence를 다음 Job의 암묵적 의존성으로 추가하여 GPU 내에서도 순서를 보장합니다.
- sched_list / num_sched_list — 복수 Compute 엔진이 있을 때, entity는 가장 부하가 낮은 스케줄러로 마이그레이션됩니다.
drm_sched_entity_select_rq()가_score기반으로 최적 스케줄러를 선택합니다. - guilty — TDR로 GPU 리셋이 발생하면, 원인 entity에
guilty를 설정합니다. guilty entity의 이후 Job은run_job()호출 없이 즉시 에러 fence를 signal합니다. - entity_idle —
drm_sched_entity_destroy()에서 모든 대기 중 Job이 완료될 때까지 차단하는 데 사용합니다.
struct drm_sched_job 필드별 해설
drm_sched_job은 GPU에 제출할 개별 작업을 나타냅니다.
드라이버는 이 구조체를 자체 Job 구조체에 임베딩하여 사용합니다 (예: struct amdgpu_job).
/* include/drm/gpu_scheduler.h — drm_sched_job 주요 필드 */
struct drm_sched_job {
struct spsc_node queue_node;
/* entity의 spsc_queue 내 노드.
* push_job()에서 큐에 연결, pop_job()에서 제거 */
struct list_head list;
/* scheduler의 pending_list 노드.
* run_job() 호출 전에 pending_list에 추가,
* fence signal 후 제거 */
struct drm_gpu_scheduler *sched;
/* 이 Job이 실행되는 스케줄러 인스턴스.
* entity에서 pop할 때 entity->rq->sched로 설정 */
struct drm_sched_fence *s_fence;
/* scheduled + finished 두 fence를 포함하는 구조체.
* drm_sched_job_arm()에서 할당 */
struct drm_sched_entity *entity;
/* 이 Job을 제출한 entity.
* drm_sched_job_init()에서 설정 */
uint64_t id;
/* 전역 Job ID (atomic64_inc).
* tracepoint, debugfs 로그에서 Job 식별용 */
struct list_head dependencies;
/* 외부 fence 의존성 목록.
* drm_sched_job_add_dependency()로 추가.
* 모든 fence가 signal되어야 run_job() 호출 */
union {
struct dma_fence_cb finish_cb;
struct work_struct work;
};
/* finish_cb: hw_fence signal → drm_sched_job_done() 콜백
* work: free_job()을 workqueue에서 비동기 실행 */
};
코드 설명
drm_sched_job은 두 개의 리스트에 동시에 연결됩니다:
- queue_node → entity.job_queue — entity 내부에서 FIFO 순서로 대기합니다. 스케줄러가 이 entity를 선택하면
spsc_queue_pop()으로 꺼냅니다. - list → scheduler.pending_list — GPU에 제출된 후 완료 대기 중인 Job을 시간순으로 추적합니다. TDR의 타임아웃 판정 기준이 됩니다.
s_fence는 drm_sched_fence 구조체로, scheduled와 finished 두 단계의 fence를 제공합니다. 이 이중 fence 설계를 통해 "GPU에 제출됨"과 "GPU에서 실행 완료"를 구분할 수 있습니다.
dependencies 리스트에는 다른 엔진이나 다른 프로세스의 fence가 들어갑니다. 예를 들어 3D 렌더링 결과를 비디오 인코딩하려면, 3D Job의 finished fence를 비디오 Job의 의존성으로 추가합니다.
struct drm_sched_fence — scheduled/finished fence 쌍
drm_sched_fence는 하나의 Job에 대해 두 단계의 동기화 포인트를 제공합니다.
이 이중 fence 설계가 drm_sched 동기화의 핵심입니다.
/* include/drm/gpu_scheduler.h — drm_sched_fence */
struct drm_sched_fence {
struct dma_fence scheduled;
/* run_job() 호출 직전에 signal.
* "이 Job이 GPU에 제출되었다"는 의미.
* 같은 entity의 다음 Job이 이 fence를 대기 */
struct dma_fence finished;
/* GPU 실행 완료 후 signal.
* hw_fence가 signal → finished도 signal.
* 유저스페이스에 반환하는 fence가 이것 */
struct dma_fence *parent;
/* run_job()이 반환한 hw_fence 포인터.
* hw_fence signal → finished signal 체이닝 */
struct drm_gpu_scheduler *sched;
/* 이 fence가 속한 스케줄러 */
struct drm_sched_fence *owner;
/* refcount 관리용 자기 참조 */
};
코드 설명
drm_sched_fence의 이중 fence 설계는 중요한 최적화를 가능하게 합니다:
- scheduled fence —
drm_sched_main()이run_job()을 호출하기 직전에 signal합니다. 같은 entity의 다음 Job은 이전 Job의scheduledfence가 signal되면 entity 큐에서 꺼낼 수 있습니다. GPU 실행이 끝나기 전에도 다음 Job을 GPU에 파이프라이닝할 수 있어 처리량이 향상됩니다. - finished fence — GPU가 실제로 작업을 완료하고 인터럽트를 발생시켜
hw_fence가 signal되면, 그에 연결된finishedfence도 signal됩니다. 유저스페이스에 반환하는 syncobj나 out_fence는 이finishedfence입니다. - parent (hw_fence) —
run_job()이 반환하는 드라이버별 fence입니다.drm_sched_fence_set_parent()로 연결하면 hw_fence signal 시 자동으로 finished가 signal됩니다.
drm_sched_main() 함수 구현 분석
drm_sched_main()은 drm_sched의 심장부입니다.
커널 스레드로 실행되며, Job을 entity에서 꺼내 GPU에 제출하는 무한 루프를 구현합니다.
drivers/gpu/drm/scheduler/sched_main.c의 핵심 로직을 단순화하여 분석합니다.
/* drivers/gpu/drm/scheduler/sched_main.c — 핵심 루프 (단순화) */
static int drm_sched_main(void *param)
{
struct drm_gpu_scheduler *sched = param;
struct drm_sched_entity *entity;
struct drm_sched_job *sched_job;
struct dma_fence *fence;
int r;
while (!kthread_should_stop()) {
/* ① hw_submission_limit에 여유가 생길 때까지 sleep */
wait_event(sched->wake_up_worker,
(drm_sched_can_queue(sched) &&
(entity = drm_sched_select_entity(sched))) ||
kthread_should_stop());
if (kthread_should_stop())
break;
/* ② entity에서 Job 꺼내기 */
sched_job = drm_sched_entity_pop_job(entity);
if (!sched_job)
continue;
/* ③ scheduled fence signal — "GPU에 제출 예정" */
drm_sched_fence_scheduled(sched_job->s_fence, entity);
/* ④ pending_list에 추가 (타임아웃 추적용) */
spin_lock(&sched->job_list_lock);
list_add_tail(&sched_job->list, &sched->pending_list);
spin_unlock(&sched->job_list_lock);
/* ⑤ 타임아웃 타이머 시작 (첫 Job인 경우) */
drm_sched_start_timeout(sched);
/* ⑥ 드라이버 run_job() 호출 → GPU에 커맨드 제출 */
fence = sched->ops->run_job(sched_job);
/* ⑦ hw_fence를 s_fence.finished에 연결 */
if (!IS_ERR_OR_NULL(fence)) {
drm_sched_fence_set_parent(sched_job->s_fence, fence);
r = dma_fence_add_callback(fence,
&sched_job->finish_cb,
drm_sched_job_done_cb);
if (r == -ENOENT)
/* fence 이미 signal됨 → 즉시 완료 처리 */
drm_sched_job_done(sched_job, fence->error);
dma_fence_put(fence);
} else {
/* fence 없음 또는 에러 → 즉시 완료 */
drm_sched_job_done(sched_job,
IS_ERR(fence) ? PTR_ERR(fence) : 0);
}
atomic_inc(&sched->hw_rq_count);
}
return 0;
}
코드 설명
drm_sched_main()의 각 단계를 상세히 분석합니다:
- ① wait_event() — 두 조건이 모두 만족할 때까지 sleep합니다:
drm_sched_can_queue():hw_rq_count < hw_submission_limit확인. GPU 파이프라인에 여유 슬롯이 있어야 합니다.drm_sched_select_entity(): 우선순위 높은 run-queue부터 순회하여 Job이 준비된 entity를 찾습니다. 라운드 로빈으로 같은 우선순위의 entity들에게 공정한 기회를 줍니다.
- ② pop_job() — 선택된 entity의 spsc_queue에서 head Job을 꺼냅니다. 이때 Job의 의존 fence가 모두 signal되었는지 확인합니다.
- ③ scheduled fence signal — 같은 entity의 다음 Job이 entity 큐에서 나올 수 있게 됩니다. GPU 파이프라이닝의 핵심입니다.
- ④ pending_list 추가 — spinlock으로 보호합니다. IRQ 컨텍스트의 fence signal에서도 이 리스트에 접근할 수 있기 때문입니다.
- ⑤ 타임아웃 시작 — pending_list에 처음 Job이 들어가면
schedule_delayed_work(&sched->work_tdr, timeout)을 호출합니다. - ⑥ run_job() — 드라이버가 GPU 링 버퍼에 커맨드를 기록하고, GPU가 완료 시 signal할 hw_fence를 반환합니다.
- ⑦ fence 연결 — hw_fence에 콜백을 등록합니다. GPU 완료 인터럽트 → hw_fence signal →
drm_sched_job_done_cb()→finishedfence signal →free_job()순으로 정리됩니다.
타임아웃 처리 — drm_sched_job_timedout() 구현 분석
GPU hang 발생 시 delayed_work로 트리거되는 drm_sched_job_timedout()의 내부 동작과
GPU 리셋 복구 흐름을 커널 소스 수준에서 분석합니다.
/* drivers/gpu/drm/scheduler/sched_main.c — 타임아웃 처리 (단순화) */
static void drm_sched_job_timedout(struct work_struct *work)
{
struct drm_gpu_scheduler *sched =
container_of(work, typeof(*sched), work_tdr.work);
struct drm_sched_job *job;
enum drm_gpu_sched_stat stat;
/* pending_list의 가장 오래된(첫 번째) Job을 가져옴 */
spin_lock(&sched->job_list_lock);
job = list_first_entry_or_null(&sched->pending_list,
struct drm_sched_job, list);
spin_unlock(&sched->job_list_lock);
if (!job)
return;
/* 스케줄러 중단 — kthread park, pending Job 수거 */
drm_sched_stop(sched, job);
/* 드라이버 timedout_job 콜백:
* - GPU 레지스터 덤프 수집
* - GPU 하드웨어 리셋
* - NOMINAL: 복구 성공 → start로 재개
* - ENODEV: HW 복구 불가 → 영구 중단 */
stat = sched->ops->timedout_job(job);
if (stat == DRM_GPU_SCHED_STAT_ENODEV) {
/* 하드웨어 불량 — 스케줄러 영구 중단 */
drm_sched_fault(sched);
return;
}
/* guilty 카운트 증가: hang_limit 초과 시 entity banned */
if (job->s_fence->parent &&
job->s_fence->parent->error == -ETIMEDOUT)
atomic_inc(job->entity->guilty);
/* 스케줄러 재개 — kthread unpark, pending Job 재제출 */
drm_sched_start(sched,
stat != DRM_GPU_SCHED_STAT_NOMINAL);
}
코드 설명
타임아웃 처리의 핵심 절차를 단계별로 분석합니다:
- 타임아웃 감지 —
delayed_work가timeoutjiffies(기본 10초) 후에 발화합니다.pending_list의 첫 번째 Job이 가장 오래 실행 중인 Job이므로 타임아웃 대상입니다. - drm_sched_stop() — kthread를 park하여 새 Job 제출을 차단하고,
pending_list의 모든 Job을 수거합니다. 이 시점에서 GPU에는 더 이상 새 커맨드가 전송되지 않습니다. - timedout_job() 콜백 — 드라이버가 GPU 하드웨어 리셋을 수행합니다. amdgpu의 경우
amdgpu_device_gpu_recover()가 ASIC 레벨 리셋을 실행하고, 링 버퍼를 재초기화합니다. - 복구 분기:
NOMINAL— 리셋 성공.drm_sched_start()가 kthread를 unpark하고, 수거한 Job들의 scheduled fence를 다시 signal하여 재제출 가능 상태로 만듭니다.ENODEV— 하드웨어 복구 불가. 스케줄러가 영구 중단되며, 대기 중인 모든 Job의 fence에 에러를 signal합니다.
- guilty entity — 같은 entity에서 반복적으로 hang이 발생하면(
hang_limit초과), 해당 entity를 guilty로 마킹합니다. guilty entity의 이후 Job은 GPU에 제출하지 않고 즉시 에러를 반환합니다.
일반적인 문제와 디버깅(Debugging) 가이드
drm_sched 관련 문제는 주로 GPU hang, fence 타임아웃, 메모리 부족의 형태로 나타납니다. dmesg와 debugfs를 활용한 체계적인 진단 방법을 정리합니다.
| 증상 | 가능한 원인 | dmesg 키워드 | 진단 방법 |
|---|---|---|---|
| 화면 멈춤 후 복구 | GPU hang → TDR 리셋 성공 | GPU reset succeeded |
drm_sched_job_timedout tracepoint 확인 |
| 화면 완전 멈춤 | GPU hang → TDR 리셋 실패 | amdgpu: GPU reset failed |
ENODEV 반환 확인, 전체 GPU 리셋 필요 |
| Job 제출 느림 | fence 의존성 대기, BO eviction | 없음 (성능 저하만) | ftrace drm_sched_job → drm_run_job 시간 측정 |
| VRAM 부족 에러 | BO 할당 실패, Job이 메모리를 해제 못 함 | TTM_PL_VRAM allocation failed |
amdgpu_gem_info, amdgpu_vram_mm debugfs 확인 |
| fence 순서 역전 | 드라이버 fence signaling 버그 | fence not signaled in time |
CONFIG_DMA_FENCE_TRACE=y 빌드 후 추적 |
# GPU hang 발생 시 종합 진단 스크립트
# 1. dmesg에서 GPU 관련 에러 수집
dmesg | grep -iE "drm|amdgpu|i915|gpu|fence|sched" | tail -50
# 2. 현재 스케줄러 상태 스냅샷
cat /sys/kernel/debug/dri/0/amdgpu_gpu_sched 2>/dev/null
# 3. GPU 레지스터 덤프 (hang 원인 분석)
cat /sys/kernel/debug/dri/0/amdgpu_wave_status 2>/dev/null
# 4. VRAM/GTT 사용 현황
cat /sys/kernel/debug/dri/0/amdgpu_vram_mm 2>/dev/null
# 5. GPU 온도 (열 스로틀링 가능성)
cat /sys/class/drm/card0/device/hwmon/hwmon*/temp1_input 2>/dev/null
dma_fence_wait()에서 영원히 대기하면 전체 디스플레이 파이프라인이 멈출 수 있습니다.
드라이버에서 fence를 생성했으면, 에러 경로에서도 반드시 dma_fence_signal()을 호출해야 합니다.
이 규칙 위반은 커널에서 WARN_ON을 발생시킵니다.
멀티 링(Multi-Ring) / 멀티 엔진(Multi-Engine) 스케줄링
현대 GPU는 단일 실행 엔진이 아닙니다. 그래픽스(GFX), 컴퓨트(Compute), DMA/SDMA, 비디오 인코딩(VCE/VCN), 비디오 디코딩(UVD/VCN) 등 복수의 하드웨어 엔진을 보유하며, 각 엔진마다 독립된 drm_gpu_scheduler 인스턴스가 할당됩니다.
엔진별 스케줄러 인스턴스 모델
amdgpu 드라이버를 기준으로, Navi 10 GPU는 다음과 같은 링 구성을 가집니다.
| 엔진 유형 | 링 개수 | 스케줄러 인스턴스 | 용도 | 타임아웃(ms) |
|---|---|---|---|---|
| GFX (Graphics) | 1 | adev->gfx.gfx_ring[0].sched | 3D 렌더링, 셰이더 실행 | 10,000 |
| Compute | 8 | adev->gfx.compute_ring[0..7].sched | GPGPU 컴퓨팅(CUDA/ROCm) | 60,000 |
| SDMA | 2 | adev->sdma.instance[0..1].ring.sched | BO 복사, 페이지 마이그레이션 | 10,000 |
| VCN Decode | 1 | adev->vcn.inst[0].ring_dec.sched | H.264/H.265 디코딩 | 10,000 |
| VCN Encode | 2 | adev->vcn.inst[0].ring_enc[0..1].sched | 비디오 인코딩 | 10,000 |
| JPEG | 1 | adev->jpeg.inst[0].ring_dec.sched | JPEG 하드웨어 디코드 | 10,000 |
엔진 간 동기화
서로 다른 엔진의 Job 사이에 의존성이 있으면 dma_fence를 통해 동기화합니다. 예를 들어, Compute 엔진에서 계산한 결과를 GFX 엔진에서 렌더링하려면, Compute Job의 완료 fence를 GFX Job의 의존 fence로 등록합니다.
/* 크로스 엔진 의존성 예시: Compute → GFX */
/* 1. Compute 엔진에 Job A 제출 → finished fence 획득 */
struct dma_fence *compute_fence = job_a->s_fence->finished.fence;
/* 2. GFX 엔진 Job B에 의존성 추가 */
int ret = drm_sched_job_add_dependency(&job_b->base, compute_fence);
if (ret)
return ret;
/* 3. GFX Job B 제출 — compute_fence가 signal될 때까지 대기 */
drm_sched_entity_push_job(&job_b->base);
/*
* 실행 순서:
* Compute ring: [Job A 실행] → fence signal
* GFX ring: [대기...........] → [Job B 실행]
*
* 두 엔진은 병렬로 다른 Job을 처리할 수 있지만,
* Job B는 반드시 Job A 완료 후에만 실행됨
*/
Entity의 멀티 스케줄러 바인딩
하나의 drm_sched_entity는 초기화 시 복수의 스케줄러 인스턴스를 후보로 받을 수 있습니다. 이를 통해 동일 유형의 여러 엔진(예: Compute ring 0~7) 사이에서 부하 분산(Load Balancing)이 가능합니다.
/* Compute entity를 8개 Compute ring에 분산 바인딩 */
struct drm_gpu_scheduler *sched_list[8];
for (int i = 0; i < 8; i++)
sched_list[i] = &adev->gfx.compute_ring[i].sched;
/* entity 초기화 — 8개 스케줄러 중 하나가 선택됨 */
drm_sched_entity_init(&entity, DRM_SCHED_PRIORITY_NORMAL,
sched_list, 8, NULL);
/*
* drm_sched_entity_init() 내부:
* - num_sched_list > 1이면 가장 적은 Job을 가진 스케줄러 선택
* - entity->rq = &sched->sched_rq[priority]
* - Job 제출 시 해당 스케줄러의 워커 스레드가 처리
*/
drm_gpu_scheduler 인스턴스는 자체 delayed_work로 타임아웃을 감시합니다.
Compute 엔진이 hang되더라도 GFX 엔진의 렌더링은 영향받지 않으며, Compute 엔진만 리셋할 수 있습니다(soft reset 지원 시).
그러나 하드웨어 제약으로 전체 GPU 리셋(full reset)이 필요한 경우, 모든 스케줄러가 일시 정지됩니다.
배치 제출(Batch Submission)과 파이프라이닝(Pipelining)
GPU 성능을 극대화하려면 GPU가 항상 바쁘도록(busy) 유지해야 합니다. CPU 측에서 Job을 준비하는 동안 GPU가 유휴(idle) 상태가 되면 전체 처리량(Throughput)이 감소합니다. hw_submission_limit과 backlog 큐를 활용한 파이프라이닝이 핵심입니다.
hw_submission_limit 동작
/* drm_sched_main() 워커 스레드의 Job 디스패치 루프 */
while (!kthread_should_stop()) {
/* 1. HW 큐에 여유가 있는지 확인 */
if (atomic_read(&sched->hw_rq_count) >= sched->hw_submission_limit)
wait_event(sched->wake_up_worker, ...);
/* 2. entity에서 다음 Job 선택 (라운드 로빈) */
entity = drm_sched_select_entity(sched);
if (!entity)
continue;
sched_job = drm_sched_entity_pop_job(entity);
/* 3. 의존 fence 대기 완료 확인 */
if (sched_job->s_fence->parent)
dma_fence_wait(sched_job->s_fence->parent);
/* 4. 드라이버의 run_job 콜백 호출 → HW 링에 제출 */
fence = sched->ops->run_job(sched_job);
atomic_inc(&sched->hw_rq_count);
/* 5. TDR 타이머 시작 */
drm_sched_start_timeout(sched);
}
| 파라미터 | 기본값 | 영향 | 조정 가이드 |
|---|---|---|---|
hw_submission_limit | 드라이버 결정 (amdgpu: 2) | HW 큐에 동시에 있을 수 있는 Job 수 | 높이면 GPU 유휴 감소, 낮추면 TDR 반응 빠름 |
timeout | 10,000 ms (GFX) | Job 완료 대기 최대 시간 | ML 워크로드는 60초 이상 필요 가능 |
hang_limit | 0 (무제한) | 연속 hang 허용 횟수, 초과 시 entity ban | 프로덕션 환경에서 3~5 설정 권장 |
num_rqs | DRM_SCHED_PRIORITY_COUNT (4) | 우선순위 큐 수 | 커널 API에 의해 고정 |
파이프라이닝 타임라인
이상적인 파이프라이닝에서는 CPU가 Job N+2를 준비하는 동안 GPU는 Job N을 실행하고, Job N+1은 HW 큐에서 대기합니다.
ftrace로 drm_sched_job과 drm_run_job 이벤트 사이의 시간 차이를 측정하면 병목(Bottleneck)을 식별할 수 있습니다.
드라이버별 drm_sched 구현
drm_sched는 공통 프레임워크이지만, 각 드라이버는 하드웨어 특성에 맞게 drm_sched_backend_ops 콜백을 다르게 구현합니다. 주요 드라이버의 구현 차이를 비교합니다.
amdgpu 구현 상세
static const struct drm_sched_backend_ops amdgpu_sched_ops = {
.run_job = amdgpu_job_run,
.timedout_job = amdgpu_job_timedout,
.free_job = amdgpu_job_free_cb,
};
/* amdgpu_job_run: IB(Indirect Buffer)를 HW 링에 emit */
static struct dma_fence *amdgpu_job_run(
struct drm_sched_job *sched_job)
{
struct amdgpu_job *job = to_amdgpu_job(sched_job);
struct amdgpu_ring *ring = to_amdgpu_ring(sched_job->sched);
/* VM 페이지 테이블 업데이트 (필요 시) */
amdgpu_vm_flush(ring, job);
/* IB를 링 버퍼에 기록 */
for (i = 0; i < job->num_ibs; i++)
amdgpu_ib_schedule(ring, job->num_ibs, job->ibs, ...);
/* 도어벨(doorbell)로 GPU에 알림 */
amdgpu_ring_commit(ring);
return fence;
}
/* amdgpu_job_timedout: GPU hang 시 리셋 절차 */
static enum drm_gpu_sched_stat amdgpu_job_timedout(
struct drm_sched_job *s_job)
{
/* 1. GPU 상태 스냅샷 저장 */
amdgpu_device_gpu_recover(adev, job);
/*
* 내부 절차:
* - 모든 스케줄러 정지 (drm_sched_stop)
* - ASIC 리셋 (mode1/mode2/BACO)
* - 모든 링 재초기화
* - guilty entity 마킹
* - 대기 중인 Job 재제출 (drm_sched_resubmit_jobs)
* - 스케줄러 재시작 (drm_sched_start)
*/
return DRM_GPU_SCHED_STAT_NOMINAL;
}
nouveau, Xe, panfrost 비교
| 특성 | amdgpu | i915/Xe | nouveau | panfrost | virtio-gpu |
|---|---|---|---|---|---|
| 스케줄러 소유권 | 드라이버(커널) | GuC (FW) | 드라이버(커널) | 드라이버(커널) | 호스트 위임 |
| HW 큐 수 | 엔진별 1 | GuC 내부 관리 | 채널별 1 | 엔진별 1 (JM/CSF) | 가상 큐 |
| 선점 지원 | Mid-wave (RDNA3+) | GuC 선점 | 채널 선점 | CSF 기반 (v10+) | 없음 |
| 리셋 범위 | Per-ring / Full | Per-engine | Per-channel / Full | Soft reset / Hard | 해당 없음 |
| 메모리 모델 | TTM (VRAM + GTT) | TTM + LMEM | TTM + VRAM | GEM + SHMEM | 호스트 GEM |
| 타임아웃(GFX) | 10,000 ms | GuC 내부 | 15,000 ms | 500 ms | 30,000 ms |
| GPU 페이지 폴트 | SVM (XNACK) | SVM (Xe2+) | 미지원 | 미지원 | 해당 없음 |
panfrost (ARM Mali) 구현 특징
panfrost는 ARM Mali GPU (Midgard/Bifrost/Valhall) 드라이버입니다. GPU 아키텍처가 x86 GPU와 크게 다르므로, drm_sched 사용 패턴도 다릅니다.
/* panfrost: Job Manager 기반 제출 */
static struct dma_fence *panfrost_job_run(
struct drm_sched_job *sched_job)
{
struct panfrost_job *job = to_panfrost_job(sched_job);
/* Job descriptor chain을 GPU Job Manager에 직접 기록 */
panfrost_mmu_flush_range(pfdev, ...);
gpu_write(pfdev, JS_HEAD_NEXT_LO(js), lower_32_bits(jc));
gpu_write(pfdev, JS_HEAD_NEXT_HI(js), upper_32_bits(jc));
gpu_write(pfdev, JS_COMMAND_NEXT(js), JS_COMMAND_START);
return fence;
}
/* 타임아웃: 짧은 500ms — 임베디드 환경 특성 */
drm_sched_init(&pfdev->js->queue[js].sched,
&panfrost_sched_ops, NULL,
DRM_SCHED_PRIORITY_COUNT,
1, /* hw_submission: 1 (Job Manager 제약) */
0, /* hang_limit */
msecs_to_jiffies(500), ...);
GPU 행(Hang) 덤프 심층 분석
GPU hang이 발생하면 커널은 자동으로 레지스터 덤프와 웨이브(wave) 상태를 수집합니다. 이 정보를 해석하여 hang의 근본 원인(Root Cause)을 찾는 방법을 설명합니다.
덤프 수집
# 1. GPU devcoredump 수집 (자동 생성)
ls /sys/class/devcoredump/devcd*/
cat /sys/class/devcoredump/devcd0/data > gpu_hang_dump.bin
# 2. umr 도구로 amdgpu 레지스터 덤프 해석
umr --read mmGRBM_STATUS
umr --read mmSRBM_STATUS
# 3. wave 상태 확인 (어떤 셰이더가 멈췄는지)
cat /sys/kernel/debug/dri/0/amdgpu_wave_status
# 4. fence 상태 확인 (어떤 Job이 미완료인지)
cat /sys/kernel/debug/dri/0/amdgpu_fence_info
# 5. ring 상태 확인 (HW 포인터 위치)
cat /sys/kernel/debug/dri/0/amdgpu_ring_gfx_0
hang 원인 분류
| GRBM_STATUS | 의미 | 가능한 원인 | 조치 |
|---|---|---|---|
GUI_ACTIVE=1, TA_BUSY=1 | Texture Array가 바쁨 | 무한 텍스처 샘플링 루프 | 셰이더 코드 검토 |
SPI_BUSY=1, SX_BUSY=1 | 셰이더 프로세서 멈춤 | ALU 무한 루프, 데드락 | wave dump 분석 |
CP_BUSY=1, CP_COHERENCY=0 | Command Processor 멈춤 | 잘못된 PM4 패킷 | IB 내용 검증 |
DB_BUSY=1 | Depth Buffer 유닛 멈춤 | Z-buffer 크기 초과 | 렌더 타겟 설정 확인 |
| 전부 0 | GPU 전원 꺼짐 | PCIe 링크 다운, BACO 진입 | PCIe AER 로그 확인 |
bpftrace를 이용한 실시간 스케줄링 분석
#!/usr/bin/env bpftrace
/* GPU Job 제출~완료 레이턴시 히스토그램 */
tracepoint:drm:drm_sched_job {
@submit[args->id] = nsecs;
}
tracepoint:drm:drm_run_job {
if (@submit[args->id]) {
@queue_latency_us = hist((nsecs - @submit[args->id]) / 1000);
delete(@submit[args->id]);
}
}
tracepoint:drm:drm_sched_process_job {
@complete[args->fence] = nsecs;
}
END {
printf("\n=== GPU Job 큐 대기 시간 (us) ===\n");
print(@queue_latency_us);
clear(@submit);
clear(@complete);
}
DRM 스케줄러 발전 방향
drm_sched는 지속적으로 발전하고 있으며, 최근 커널 버전에서 다음과 같은 변화가 진행 중입니다.
drm_exec — 새로운 실행 프레임워크
커널 6.6에서 도입된 drm_exec은 다중 GEM 오브젝트의 잠금 획득을 안전하게 처리하는 헬퍼입니다. 기존의 ww_mutex 기반 BO 잠금 로직을 단순화합니다.
/* drm_exec 사용 예시 — 다중 BO 잠금 */
struct drm_exec exec;
drm_exec_init(&exec, DRM_EXEC_INTERRUPTIBLE_WAIT, 0);
drm_exec_until_all_locked(&exec) {
/* 필요한 모든 BO를 잠금 시도 */
ret = drm_exec_prepare_obj(&exec, &bo1->base, 1);
drm_exec_retry_on_contention(&exec);
ret = drm_exec_prepare_obj(&exec, &bo2->base, 1);
drm_exec_retry_on_contention(&exec);
}
/* 모든 BO 잠금 획득 완료 — Job 제출 진행 */
...
drm_exec_fini(&exec);
향후 발전 동향
| 주제 | 현재 상태 | 진행 방향 | 관련 드라이버 |
|---|---|---|---|
| GPU 페이지 폴트 기반 SVM | amdgpu XNACK (실험적) | 모든 GPU에서 통일된 가상 주소 공간 | amdgpu, Xe |
| per-engine 리셋 | 일부 드라이버 지원 | hang 시 다른 엔진에 영향 없는 복구 | i915 GuC, Xe |
| CSF (Command Stream Frontend) | panfrost v10+ | GPU FW가 스케줄링 담당 (GuC 유사) | panfrost (Mali Valhall+) |
| Vulkan Timeline Semaphore | drm_syncobj timeline | 유저스페이스 세밀한 동기화 | 전 드라이버 |
| drm_sched 공유 메모리 eviction | TTM per-BO eviction | 스케줄러 인지 eviction 정책 | amdgpu, Xe |
| 에너지 효율 스케줄링 | DVFS만 사용 | 워크로드 기반 전력 상태 전환 | panfrost, amdgpu |
dri-devel@lists.freedesktop.org 메일링 리스트에서 논의됩니다.
drm/scheduler 태그로 Patchwork를 검색하면 최신 패치 시리즈를 추적할 수 있습니다.
참고 링크
- Kernel Docs — DRM GPU Scheduler — drm_sched 공식 커널 문서, 스케줄링 정책 및 API
- Kernel Docs — DRM Memory Management — GEM, TTM, GPUVA 등 GPU 메모리 관리 프레임워크
- Kernel Docs — DMA-BUF — dma_fence 시그널링 규칙, 버퍼 공유 API
- Kernel Docs — DRM Internals — DRM 코어 내부 구조, 파일 오퍼레이션
- dri-devel 메일링 리스트 아카이브 — DRM/GPU 커널 개발 토론
- IGT GPU Tools — DRM 드라이버 및 스케줄러 테스트 스위트
- amdgpu Driver — drm_sched를 가장 적극적으로 활용하는 드라이버
- xe Driver — Intel Xe GPU 드라이버의 drm_sched 활용
- DRM UAPI — 유저 공간에서 잡 제출(submit) 인터페이스
- LWN.net — GPU Articles — DRM/GPU 관련 심층 기술 기사 모음
- freedesktop.org Blog — Mesa, DRM, GPU 드라이버 개발 소식
- amd-gfx 메일링 리스트 아카이브 — AMD GPU 드라이버 개발 토론 (drm_sched 패치 다수)
drivers/gpu/drm/scheduler/— drm_sched 코어 (sched_main.c, sched_entity.c, sched_fence.c)include/drm/gpu_scheduler.h— drm_sched API 헤더, 구조체 정의drivers/gpu/drm/amd/amdgpu/amdgpu_job.c— amdgpu drm_sched 잡 구현 예시drivers/gpu/drm/xe/xe_sched_job.c— xe 드라이버의 drm_sched 잡 구현drivers/accel/ivpu/ivpu_job.c— NPU 드라이버의 drm_sched 활용 예시drivers/gpu/drm/panfrost/panfrost_job.c— ARM Mali GPU drm_sched 활용
관련 문서
- GPU 서브시스템 (DRM/KMS) — drm_sched가 속한 DRM 프레임워크 전체 구조
- NPU (Neural Processing Unit) — drm_sched를 사용하는 AI 가속기 드라이버
- ROCm / HIP — AMD GPU 컴퓨팅 스택과 amdgpu drm_sched 연동
- CPU 스케줄러 — drm_sched와 비교되는 CPU 스케줄링
- DMA — dma_fence와 DMA 완료 인터럽트 연동
- HMM (이기종 메모리 관리) — GPU Job 실행 시 메모리 마이그레이션
- Workqueue — drm_sched의 TDR delayed_work가 실행되는 메커니즘
- 인터럽트 — GPU 완료 인터럽트와 fence signaling
- 프레임버퍼 (fbdev) — DRM/KMS 이전의 디스플레이 프레임워크
- V4L2 — 비디오 디코딩 엔진(VCN/UVD)과 drm_sched 연동
- 동기화 기법 — dma_fence의 기반이 되는 커널 동기화 개념