Wait/Wound Mutex (데드락 회피 뮤텍스)

여러 뮤텍스(Mutex)를 동시에 획득해야 할 때 데드락을 구조적으로 회피하는 Wait/Wound Mutex를 분석합니다. Wound-Wait와 Wait-Die 두 가지 알고리즘의 원리, ww_mutex/ww_acquire_ctx/ww_class 자료구조, 트랜잭션(Transaction) 티켓 기반 순서 결정, -EDEADLK 재시도 루프, Wound 전파 경로를 추적하고, DRM/GEM/TTM에서의 실전 사용 패턴, 다중 잠금(Lock) 획득 기법, PREEMPT_RT 영향, 안티패턴과 lockdep 디버깅(Debugging)까지 커널 소스 기반으로 포괄합니다.

전제 조건: 동기화 기법, Mutex, Spinlock 문서를 먼저 읽으세요. ww_mutex는 일반 mutex 위에 데드락 회피 프로토콜을 추가한 것이므로, 기본 mutex 내부 구조를 먼저 이해해야 합니다.
일상 비유: ww_mutex는 은행 창구 번호표와 같습니다. 여러 자원(창구)을 동시에 사용해야 할 때, 번호표가 더 빠른(오래된) 고객이 우선권을 갖습니다. 늦게 온 고객이 먼저 온 고객이 필요한 창구를 차지하고 있으면, 자발적으로 양보(Yield)하고(wound) 다시 줄을 섭니다.

핵심 요약

  • 데드락 회피 — 여러 뮤텍스를 임의 순서로 획득해야 할 때, 전역 티켓 번호로 트랜잭션 간 우선순위(Priority)를 정하여 데드락을 구조적으로 방지합니다.
  • 두 가지 알고리즘WW_MUTEX_WOUND_WAIT(오래된 트랜잭션이 젊은 보유자를 wound) / WW_MUTEX_WAIT_DIE(젊은 트랜잭션이 스스로 die하여 재시도).
  • -EDEADLK 프로토콜 — 충돌 시 -EDEADLK를 반환받은 쪽이 모든 잠금을 해제하고, 경합(Contention) 잠금에 대해 slow path로 재시도합니다.
  • 트랜잭션 컨텍스트ww_acquire_ctx가 단조 증가하는 ticket stamp를 보유하여, 경합 시 어떤 트랜잭션이 양보할지를 결정합니다.
  • GPU/DRM 핵심 인프라drm_modeset_lock, dma_resv(GEM buffer reservation), TTM 메모리 관리(Memory Management)자가 ww_mutex의 주요 사용자입니다.

단계별 이해

  1. 데드락 문제 이해
    두 스레드(Thread)가 서로 다른 순서로 두 뮤텍스를 획득하면 교착 상태(Deadlock)에 빠지는 이유를 파악합니다.
  2. 순서 기반 회피 원리 파악
    전역 티켓 번호로 트랜잭션에 우선순위를 부여하여 순환 대기를 깨는 방법을 이해합니다.
  3. Wound-Wait vs Wait-Die 비교
    두 알고리즘의 차이와 각각의 장단점을 분석합니다.
  4. API 흐름 추적
    ww_acquire_init → ww_mutex_lock → -EDEADLK 처리 → ww_acquire_fini 전체 생명주기를 따라갑니다.
  5. 실전 사용 패턴 적용
    DRM/GEM/TTM에서 실제로 ww_mutex가 어떻게 활용되는지 코드 수준으로 확인합니다.
관련 표준: Rosenkrantz, D. J., Stearns, R. E., Lewis, P. M. "System Level Concurrency Control for Distributed Database Systems" (1978) — Wound-Wait / Wait-Die 알고리즘 원논문. 종합 목록은 참고자료 — 표준 & 규격 섹션을 참고하세요.

이론적 배경: 데드락 회피 알고리즘

데드락(deadlock)은 Coffman(1971)이 정의한 네 가지 필요 조건이 동시에 충족될 때 발생합니다: 상호 배제(Mutual Exclusion), 점유와 대기, 비선점(Non-preemptive), 순환 대기. 이 중 하나라도 깨면 데드락을 방지할 수 있습니다.

데드락 대응 전략

전략방법커널 적용 예
Prevention (방지)순환 대기 조건 제거: 잠금 순서 고정lockdep 순서 검증, 주소 순서 잠금
Avoidance (회피)동적으로 안전 상태 검사 후 진행ww_mutex (이 문서의 주제)
Detection & Recovery데드락 발생 후 탐지 및 복구lockdep 런타임 순환 탐지
Ignorance무시 (확률이 낮을 때)일반적으로 사용하지 않음

ww_mutex는 회피(avoidance) 전략을 구현합니다. 고정된 잠금 순서를 미리 알 수 없는 상황(예: GPU buffer object들을 동적으로 선택하여 잠가야 하는 경우)에서, 트랜잭션의 나이(ticket stamp)를 기준으로 순환 대기 조건을 동적으로 깨뜨립니다.

데드락 4조건과 ww_mutex의 순환 대기 차단 상호 배제 Mutual Exclusion 점유와 대기 Hold and Wait 비선점 No Preemption 순환 대기 Circular Wait ww_mutex: 순환 차단 데드락 시나리오 Thread A Thread B Lock M1 Lock M2 M2 요청 M1 요청 ww_mutex 해결 A (ticket=5) older → 우선 B (ticket=8) younger → 양보 wound!
ww_mutex는 트랜잭션 나이(ticket stamp)를 비교하여 순환 대기 조건을 동적으로 차단합니다

Wound-Wait 알고리즘 원리

Wound-Wait은 Rosenkrantz 등(1978)이 데이터베이스 동시성 제어를 위해 제안한 알고리즘으로, Linux 커널의 WW_MUTEX_WOUND_WAIT 모드에 구현되어 있습니다. 핵심 규칙은 두 가지입니다:

상황요청자 나이보유자 나이동작
경합 발생더 오래됨 (ticket 작음)더 젊음 (ticket 큼)보유자를 wound: 보유자에게 -EDEADLK 시그널(Signal)
경합 발생더 젊음 (ticket 큼)더 오래됨 (ticket 작음)요청자가 wait: 보유자가 해제할 때까지 슬립(Sleep)
왜 "Wound"인가: 데이터베이스 용어에서 wound는 트랜잭션을 "상처 입힌다"는 의미입니다. 즉시 abort하는 것이 아니라, 보유자에게 "양보하라"는 신호를 보내는 것입니다. 보유자는 현재 임계 영역(Critical Section)을 마치고 다음 잠금 시도 시 -EDEADLK를 받아 자발적으로 모든 잠금을 해제합니다.
Wound-Wait 알고리즘 흐름 시간 → Ctx A (ticket=3) Ctx B (ticket=7) Lock X 보유 Lock X 요청 wound! 대기... -EDEADLK 수신 모두 해제 Lock X 획득 재시도 Wound-Wait 규칙 요약 Older (ticket 작음) 요청자가 Younger 보유자를 만남: → 보유자를 wound (상처) → 보유자가 -EDEADLK 받고 양보 → 장점: older가 절대 abort하지 않음 → starvation 방지 Younger (ticket 큼) 요청자가 Older 보유자를 만남: → 요청자가 조용히 wait (슬립) → 보유자 해제 시 깨어남 → 불필요한 abort 최소화
Wound-Wait에서는 older 트랜잭션이 우선권을 가지며, younger 보유자에게 wound 신호를 보냅니다

Wound-Wait의 핵심 성질:

Wait-Die 알고리즘과 비교

Wait-Die는 Wound-Wait의 대칭 알고리즘입니다. Linux 커널은 WW_MUTEX_WAIT_DIE 모드로 이를 지원합니다.

항목Wound-WaitWait-Die
Older가 Younger의 lock 요청Younger를 wound (양보 강제)Older가 wait (대기)
Younger가 Older의 lock 요청Younger가 wait (대기)Younger가 die (즉시 -EDEADLK)
abort 대상Younger 보유자Younger 요청자
abort 빈도낮음 (보유자가 wound될 때만)높음 (매 경합마다 younger가 die)
wait 방향Younger → OlderOlder → Younger
구현 복잡도wound 전파 로직 필요단순 (즉시 반환)
커널 매크로(Macro)DEFINE_WW_CLASS()DEFINE_WD_CLASS()
주요 사용처DRM modeset, dma_resv상대적으로 드묾
Wound-Wait vs Wait-Die 동작 비교 Wound-Wait (DEFINE_WW_CLASS) Older(3) → Lock held by Younger(7) Younger wound! → Older 대기 후 획득 Younger(7) → Lock held by Older(3) Younger waits → 슬립 대기 abort 빈도: 낮음 wound 시에도 보유자가 현재 작업은 완료 후 양보 Wait-Die (DEFINE_WD_CLASS) Older(3) → Lock held by Younger(7) Older waits → 슬립 대기 Younger(7) → Lock held by Older(3) Younger dies! → 즉시 -EDEADLK abort 빈도: 높음 younger 요청자가 매 경합마다 즉시 die → 더 많은 재시도
Wound-Wait은 abort 빈도가 낮고, Wait-Die는 구현이 단순하지만 재시도가 더 빈번합니다
어떤 알고리즘을 선택해야 하나: 대부분의 커널 사용자는 DEFINE_WW_CLASS()를 사용합니다. Wound-Wait이 재시도 비용이 더 낮고, DRM 서브시스템이 이 방식으로 설계되었기 때문입니다. Wait-Die는 wound 전파 로직이 불필요하여 매우 단순한 잠금 체계에서 선택할 수 있습니다.

struct ww_mutex / ww_acquire_ctx / ww_class

ww_mutex 프레임워크는 세 가지 핵심 자료구조로 구성됩니다. include/linux/ww_mutex.h에 정의되어 있습니다.

/* include/linux/ww_mutex.h */

struct ww_class {
    atomic_long_t       stamp;          /* 전역 단조 증가 카운터 */
    struct lock_class_key acquire_key;  /* lockdep: 획득 순서 추적 */
    struct lock_class_key mutex_key;    /* lockdep: 뮤텍스 클래스 */
    const char          *acquire_name; /* lockdep 디버그 이름 */
    const char          *mutex_name;   /* lockdep 디버그 이름 */
    unsigned int         is_wait_die;  /* 0=Wound-Wait, 1=Wait-Die */
};

struct ww_acquire_ctx {
    struct task_struct  *task;          /* 소유 태스크 */
    unsigned long       stamp;         /* 트랜잭션 티켓 번호 */
    unsigned int        acquired;      /* 현재 보유한 잠금 수 */
    unsigned short      wounded;       /* wound 신호 수신 여부 */
    unsigned short      is_wait_die;   /* ww_class에서 복사 */
    #ifdef CONFIG_DEBUG_MUTEXES
    unsigned int        done_acquire;  /* 디버그: 획득 완료 여부 */
    struct ww_class     *ww_class;      /* 디버그: 소속 클래스 */
    struct ww_mutex     *contending_lock; /* 디버그: 경합 중인 잠금 */
    #endif
};

struct ww_mutex {
    struct mutex         base;         /* 기반 mutex */
    struct ww_acquire_ctx *ctx;         /* 현재 보유 컨텍스트 */
    #ifdef CONFIG_DEBUG_MUTEXES
    struct ww_class     *ww_class;      /* 디버그: 소속 클래스 */
    #endif
};
ww_mutex 자료구조 관계 ww_class stamp: atomic_long_t is_wait_die: 0|1 acquire_key, mutex_key acquire_name, mutex_name ww_acquire_ctx task: task_struct * stamp: unsigned long acquired: unsigned int wounded: unsigned short is_wait_die: unsigned short ww_class * (debug) ww_mutex base: struct mutex ctx: ww_acquire_ctx * ww_class * (debug) stamp 발급 class 참조 ctx가 mutex를 보유
ww_class가 전역 카운터를 관리하고, ww_acquire_ctx가 트랜잭션별 티켓을 받아 ww_mutex와 연결됩니다

ww_class 초기화 매크로

/* Wound-Wait 모드 (기본, 대부분 사용) */
DEFINE_WW_CLASS(reservation_ww_class);

/* Wait-Die 모드 */
DEFINE_WD_CLASS(my_wd_class);

/* 매크로 확장: */
#define DEFINE_WW_CLASS(classname) \
    struct ww_class classname = { \
        .stamp = ATOMIC_LONG_INIT(0), \
        .acquire_name = #classname "_acquire", \
        .mutex_name = #classname "_mutex", \
        .is_wait_die = 0, \
    }

#define DEFINE_WD_CLASS(classname) \
    struct ww_class classname = { \
        .stamp = ATOMIC_LONG_INIT(0), \
        .acquire_name = #classname "_acquire", \
        .mutex_name = #classname "_mutex", \
        .is_wait_die = 1, \
    }

API 레퍼런스

함수설명반환값
ww_acquire_init(ctx, class)트랜잭션 시작: ticket stamp 할당void
ww_acquire_fini(ctx)트랜잭션 종료: 정리void
ww_mutex_init(lock, class)ww_mutex 초기화void
ww_mutex_lock(lock, ctx)잠금 획득 (경합 시 -EDEADLK 가능)0 또는 -EDEADLK
ww_mutex_lock_interruptible(lock, ctx)인터럽트(Interrupt) 가능 잠금0, -EDEADLK, -EINTR
ww_mutex_lock_slow(lock, ctx)-EDEADLK 후 경합 잠금 재획득 (slow path)void
ww_mutex_lock_slow_interruptible(lock, ctx)인터럽트 가능 slow path0 또는 -EINTR
ww_mutex_unlock(lock)잠금 해제void
ww_mutex_trylock(lock, ctx)비차단(Non-blocking) 시도1(성공) 또는 0(실패)
ww_mutex_is_locked(lock)잠금 상태 확인bool
ww_mutex_destroy(lock)ww_mutex 파괴 (디버그 검증 포함)void
ctx=NULL 호출: ww_mutex_lock(lock, NULL)로 호출하면 일반 mutex처럼 동작합니다. 경합 시 데드락 회피 로직이 작동하지 않으므로, 단일 잠금이 확실한 경우에만 사용합니다. ww_mutex_lock_slow()는 ctx가 NULL이면 안 됩니다.

ww_acquire_ctx: 트랜잭션 컨텍스트

ww_acquire_ctx는 하나의 "트랜잭션"을 나타냅니다. 여러 ww_mutex를 획득하려는 하나의 작업 단위입니다. 초기화 시 ww_class의 전역 카운터에서 ticket stamp를 원자적(Atomic)으로 가져옵니다.

/* kernel/locking/ww_mutex.h 또는 include/linux/ww_mutex.h */

static inline void
ww_acquire_init(struct ww_acquire_ctx *ctx,
                struct ww_class *ww_class)
{
    ctx->task = current;
    ctx->stamp = atomic_long_inc_return_relaxed(&ww_class->stamp);
    ctx->acquired = 0;
    ctx->wounded = 0;
    ctx->is_wait_die = ww_class->is_wait_die;
#ifdef CONFIG_DEBUG_MUTEXES
    ctx->ww_class = ww_class;
    ctx->done_acquire = 0;
    ctx->contending_lock = NULL;
#endif
    /* lockdep: 가상의 acquire lock 등록 */
    mutex_acquire(&ww_class->acquire_key, 0, 0, _RET_IP_);
}

핵심 포인트:

Acquire Ticket 번호와 순서 결정

ww_mutex의 데드락 회피는 ticket stamp의 전체 순서(total order)에 의존합니다. 이 순서가 순환 대기를 방지하는 핵심 메커니즘입니다.

Ticket Stamp 발급과 순서 결정 메커니즘 ww_class.stamp (atomic) 현재값: 42 Ctx A: stamp=40 OLDEST → 최고 우선순위 Ctx B: stamp=41 중간 우선순위 Ctx C: stamp=42 YOUNGEST → 최저 우선순위 우선순위 순서 (stamp 작은 순) A(40) > B(41) > C(42) stamp 값이 작을수록 older → 경합 시 우선권을 가짐
stamp는 ww_class의 전역 atomic 카운터에서 발급되어, 트랜잭션 간 전체 순서를 결정합니다

순서 비교 로직:

/* 두 컨텍스트의 나이 비교: stamp가 작은 쪽이 older */
static inline bool __ww_ctx_stamp_after(
    struct ww_acquire_ctx *a,
    struct ww_acquire_ctx *b)
{
    return (signed long)(a->stamp - b->stamp) > 0;
}

/* a가 b보다 나중에 생성됨(younger) → a.stamp > b.stamp */
/* signed 비교로 wrap-around 처리 */
Wrap-around 안전성: stamp 비교에 signed 연산을 사용하므로, unsigned long 범위의 절반(약 2^63)까지 동시 활성 트랜잭션이 있어야 오류가 발생합니다. 실제로 이 한계에 도달하는 것은 불가능합니다.

ww_mutex_lock() 흐름 분석

ww_mutex_lock()은 일반 mutex_lock()에 데드락 회피 로직을 추가한 것입니다. 내부적으로 fast path와 slow path로 나뉩니다.

ww_mutex_lock() 내부 흐름 ww_mutex_lock(lock, ctx) mutex fast path CAS 성공? YES lock->ctx = ctx return 0 (성공) NO (경합) __ww_mutex_lock_slowpath 보유자 ctx와 stamp 비교 우리가 older 보유자 wound (WW모드) 우리가 younger WW: wait (슬립) 보유자 해제 대기 WD: die 즉시 return -EDEADLK 대기열에서 슬립 대기 -EDEADLK 수신 후: 1. 보유한 모든 ww_mutex 해제 2. ww_mutex_lock_slow(경합lock, ctx) 3. 나머지 잠금 재획득 (처음부터) 모든 잠금 획득 완료
ww_mutex_lock()은 fast path CAS 성공 시 즉시 반환하고, 경합 시 stamp 비교 후 wound/wait/die를 결정합니다
/* ww_mutex_lock 호출 패턴의 전체 흐름 */

struct ww_acquire_ctx ctx;
int ret;

ww_acquire_init(&ctx, &my_ww_class);     /* 1. 트랜잭션 시작 */

retry:
    ret = ww_mutex_lock(&obj_a->lock, &ctx); /* 2. 첫 번째 잠금 */
    if (ret == -EDEADLK)
        goto backoff;

    ret = ww_mutex_lock(&obj_b->lock, &ctx); /* 3. 두 번째 잠금 */
    if (ret == -EDEADLK)
        goto backoff;

    /* 4. 임계 영역: 모든 잠금 획득 상태 */
    do_work(obj_a, obj_b);

    ww_mutex_unlock(&obj_b->lock);
    ww_mutex_unlock(&obj_a->lock);
    ww_acquire_fini(&ctx);                 /* 5. 트랜잭션 종료 */
    return 0;

backoff:
    /* 보유한 잠금을 역순으로 모두 해제 */
    if (ww_mutex_is_locked(&obj_b->lock))
        ww_mutex_unlock(&obj_b->lock);
    if (ww_mutex_is_locked(&obj_a->lock))
        ww_mutex_unlock(&obj_a->lock);

    /* 경합 잠금에 대해 slow path로 대기 */
    ww_mutex_lock_slow(&contending_lock, &ctx);
    goto retry;

상처(Wound) 전파 경로

Wound-Wait 모드에서, older 트랜잭션이 younger 보유자에게 wound 신호를 보내는 과정을 추적합니다. 이 과정은 __ww_mutex_wound() 함수에서 구현됩니다.

/* kernel/locking/ww_mutex.h — wound 전파 핵심 로직 */

static void __ww_mutex_wound(
    struct ww_mutex *lock,
    struct ww_acquire_ctx *wound_ctx,   /* 요청자 (older) */
    struct ww_acquire_ctx *hold_ctx)    /* 보유자 (younger) */
{
    /* hold_ctx가 이미 wounded 상태면 중복 wound 불필요 */
    if (hold_ctx->wounded)
        return;

    /* wounded 플래그 설정 */
    hold_ctx->wounded = 1;

    /* 보유자가 현재 lock의 대기열에서 자고 있다면 깨우기 */
    if (hold_ctx->task != current)
        wake_up_process(hold_ctx->task);
}
Wound 전파 시퀀스 다이어그램 Older Ctx (A) ww_mutex (M) Younger Ctx (B) B가 M 보유 중 ww_mutex_lock(M, A) A.stamp < B.stamp → A is older wound! (B.wounded=1) A 대기 B 임계영역 완료 → 다음 lock에서 -EDEADLK 반환 ww_mutex_unlock(M) wake up → 획득! A 보유
wound 신호를 받은 younger 보유자는 현재 임계 영역을 마치고 다음 잠금 시도에서 -EDEADLK를 반환합니다
wound의 비동기성: wound 신호는 보유자를 즉시 중단시키지 않습니다. 보유자는 현재 실행 중인 임계 영역을 완료합니다. wounded 플래그는 보유자의 다음 ww_mutex_lock() 호출 시 또는 현재 lock의 대기열에서 깨어날 때 검사됩니다.

경합 처리: -EDEADLK와 재시도 루프

-EDEADLK 반환은 "이 트랜잭션이 양보해야 한다"는 신호입니다. 호출자는 반드시 정해진 프로토콜에 따라 재시도해야 합니다.

재시도 프로토콜

/* 올바른 -EDEADLK 재시도 패턴 */

struct ww_acquire_ctx ctx;
struct ww_mutex *contended = NULL;
int ret;

ww_acquire_init(&ctx, &my_class);

retry:
    /* slow path 진입점: 이전에 경합했던 잠금 먼저 획득 */
    if (contended) {
        ww_mutex_lock_slow(contended, &ctx);
        contended = NULL;
    }

    /* 나머지 잠금 순차 획득 */
    for (i = 0; i < num_objs; i++) {
        ret = ww_mutex_lock(&objs[i]->lock, &ctx);
        if (ret == -EDEADLK) {
            contended = &objs[i]->lock;
            /* 이미 획득한 잠금 모두 해제 */
            while (i--)
                ww_mutex_unlock(&objs[i]->lock);
            goto retry;
        }
    }

    /* 성공: 모든 잠금 획득 상태 */
    do_critical_section(objs, num_objs);

    for (i = num_objs - 1; i >= 0; i--)
        ww_mutex_unlock(&objs[i]->lock);

ww_acquire_fini(&ctx);

-EDEADLK 보장 성질

성질설명
Progress가장 오래된 트랜잭션은 절대 -EDEADLK를 받지 않으므로 반드시 진행
Bounded retry재시도 시 stamp는 유지되므로, 반복할수록 상대적으로 older가 됨
No livelockstamp 전체 순서로 인해 무한 상호 양보는 불가능
ctx 보존ww_acquire_ctx는 재시도 루프 동안 재초기화하지 않음 (stamp 유지)

Slow Path: 대기와 깨우기(Wakeup)

ww_mutex_lock_slow()-EDEADLK 후 재시도의 첫 단계입니다. 경합했던 잠금을 무조건 대기(block)하면서 획득합니다. 이 함수는 -EDEADLK를 반환하지 않습니다.

/* ww_mutex_lock_slow — 경합 잠금을 위한 slow path */

void ww_mutex_lock_slow(struct ww_mutex *lock,
                         struct ww_acquire_ctx *ctx)
{
    /* ctx->acquired가 0인지 확인 (모든 잠금이 해제되어야 함) */
    WARN_ON(ctx->acquired > 0);

    /* 무조건 대기: 이 잠금의 보유자보다 우리가 */
    /* older이므로 결국 획득 보장 */
    __ww_mutex_lock(lock, ctx, TASK_UNINTERRUPTIBLE);
}

Slow path가 필요한 이유: 일반 ww_mutex_lock()으로 재시도하면, 이미 wound된 상태에서 또다시 stamp 비교를 하게 됩니다. ww_mutex_lock_slow()는 stamp 비교 없이 무조건 대기하므로, 불필요한 -EDEADLK 순환을 방지합니다.

대기열 순서

ww_mutex의 대기열은 stamp 순서로 정렬됩니다. older 트랜잭션이 먼저 깨어나므로 starvation이 방지됩니다.

/* 대기열 삽입 시 stamp 기반 정렬 위치 결정 */

/* ww_mutex waiter는 mutex waiter를 확장 */
/* list_for_each_entry로 stamp 순서 위치를 찾음 */
list_for_each_entry(cur, &lock->base.wait_list, list) {
    if (__ww_ctx_stamp_after(cur->ww_ctx, ctx->ww_ctx)) {
        /* cur보다 앞에 삽입 (우리가 더 older) */
        list_add_tail(&waiter->list, &cur->list);
        break;
    }
}

GPU/DRM 서브시스템 실전 사용

ww_mutex의 가장 대표적인 사용자는 DRM(Direct Rendering Manager) 서브시스템입니다. GPU 드라이버는 디스플레이 모드 설정, 버퍼(Buffer) 예약 등에서 여러 객체를 동시에 잠가야 하며, 잠금 순서를 미리 결정할 수 없습니다.

drm_modeset_lock

DRM 모드 설정(modeset)에서는 CRTC, 커넥터, 평면(plane) 등 여러 객체의 상태를 원자적으로 변경해야 합니다. 각 객체는 drm_modeset_lock(ww_mutex 기반)으로 보호됩니다.

/* drivers/gpu/drm/drm_modeset_lock.c */

struct drm_modeset_lock {
    struct ww_mutex  mutex;      /* ww_mutex 기반 */
    struct list_head head;       /* 잠금 목록 연결 */
};

/* 전역 ww_class: 모든 modeset lock이 공유 */
DEFINE_WW_CLASS(crtc_ww_class);

/* 모드 설정 잠금 흐름 */
int drm_modeset_lock(struct drm_modeset_lock *lock,
                     struct drm_modeset_acquire_ctx *ctx)
{
    int ret;

    ret = ww_mutex_lock(&lock->mutex, &ctx->ww_ctx);
    if (ret == -EDEADLK) {
        ctx->contended = lock;
        return -EDEADLK;
    }
    if (!ret) {
        list_add(&lock->head, &ctx->locked);
    }
    return ret;
}
DRM Atomic Modeset에서의 ww_mutex 사용 CRTC 0 modeset_lock Connector modeset_lock Plane modeset_lock CRTC 1 modeset_lock crtc_ww_class DEFINE_WW_CLASS Atomic Commit 잠금 흐름 1. drm_modeset_acquire_init(&ctx) → ww_acquire_init() 2. drm_modeset_lock(crtc0, &ctx) → ww_mutex_lock() 3. drm_modeset_lock(connector, &ctx) → ww_mutex_lock() 4. drm_modeset_lock(plane, &ctx) → ww_mutex_lock() -EDEADLK? → drm_modeset_backoff(&ctx) → 모두 해제 후 slow path 재시도 5. 모든 잠금 획득 성공 → atomic commit 실행 6. drm_modeset_drop_locks(&ctx) → 모든 ww_mutex_unlock() 7. drm_modeset_acquire_fini(&ctx) → ww_acquire_fini()
DRM atomic modeset은 CRTC, Connector, Plane 등을 ww_mutex로 잠그고, -EDEADLK 시 backoff합니다

GEM Buffer Object 잠금 패턴

GPU 렌더링에서는 여러 GEM(Graphics Execution Manager) 버퍼 오브젝트를 동시에 잠가야 합니다. 각 버퍼의 dma_resv(DMA reservation) 객체가 내부적으로 ww_mutex를 사용합니다.

/* include/linux/dma-resv.h */

struct dma_resv {
    struct ww_mutex       lock;           /* ww_mutex! */
    struct dma_resv_list  *fences;        /* 공유/배타 펜스 목록 */
};

/* 전역 ww_class: 모든 dma_resv가 공유 */
extern struct ww_class reservation_ww_class;

/* GEM 버퍼 여러 개를 동시에 잠그는 패턴 (drm_exec) */
struct drm_exec exec;
drm_exec_init(&exec, DRM_EXEC_INTERRUPTIBLE_WAIT, 0);
drm_exec_until_all_locked(&exec) {
    ret = drm_exec_prepare_obj(&exec, &bo_a->base, 1);
    drm_exec_retry_on_contention(&exec);

    ret = drm_exec_prepare_obj(&exec, &bo_b->base, 1);
    drm_exec_retry_on_contention(&exec);
}

/* 임계 영역: 모든 BO 잠금 보유 */
submit_rendering(bo_a, bo_b);

drm_exec_fini(&exec);    /* 모든 잠금 해제 */
drm_exec 헬퍼: Linux 6.4+에서 drm_exec 프레임워크가 drm_gem_lock_reservations()를 대체했습니다. 내부적으로 ww_mutex의 -EDEADLK 재시도 루프를 자동으로 처리합니다.

TTM 메모리 관리자 잠금

TTM(Translation Table Manager)은 GPU 메모리 관리 레이어로, 버퍼 오브젝트의 배치(placement)와 이동(migration)을 관리합니다. TTM은 dma_resv의 ww_mutex를 통해 버퍼 잠금을 구현합니다.

/* drivers/gpu/drm/ttm/ttm_bo.c */

/* TTM 버퍼 잠금: dma_resv.lock (ww_mutex) 사용 */
int ttm_bo_reserve(struct ttm_buffer_object *bo,
                   bool interruptible,
                   bool no_wait,
                   struct ww_acquire_ctx *ticket)
{
    int ret;

    if (no_wait) {
        if (!ww_mutex_trylock(&bo->base.resv->lock, ticket))
            return -EBUSY;
    } else if (interruptible) {
        ret = ww_mutex_lock_interruptible(
                  &bo->base.resv->lock, ticket);
    } else {
        ret = ww_mutex_lock(
                  &bo->base.resv->lock, ticket);
    }

    return ret;
}

/* TTM eviction: 여러 BO를 잠가야 할 때 ww_mutex 재시도 */
int ttm_bo_evict_first(struct ttm_device *bdev,
                       struct ttm_resource_manager *man,
                       struct ww_acquire_ctx *ticket)
{
    /* LRU에서 BO 선택 후 ttm_bo_reserve() */
    /* -EDEADLK 시 호출자의 재시도 루프로 전파 */
}
TTM APIww_mutex 연결설명
ttm_bo_reserve()ww_mutex_lock()BO 잠금 획득
ttm_bo_unreserve()ww_mutex_unlock()BO 잠금 해제
ttm_bo_reserve_slowpath()ww_mutex_lock_slow()경합 BO slow path

다중 잠금 획득 패턴

여러 ww_mutex를 획득하는 정규 패턴은 크게 두 가지입니다: 순차 획득 + 재시도정렬 후 획득.

패턴 1: 순차 획득 + -EDEADLK 재시도

/* 가장 일반적인 패턴: 순서 없이 획득, -EDEADLK 시 backoff */

struct ww_acquire_ctx ctx;
struct ww_mutex *contended = NULL;

ww_acquire_init(&ctx, &my_class);

retry:
    if (contended) {
        ww_mutex_lock_slow(contended, &ctx);
        contended = NULL;
    }

    for (i = 0; i < n; i++) {
        if (&locks[i] == contended)
            continue;  /* slow path에서 이미 획득 */

        ret = ww_mutex_lock(&locks[i], &ctx);
        if (ret == -EDEADLK) {
            contended = &locks[i];
            while (i--)
                ww_mutex_unlock(&locks[i]);
            goto retry;
        }
    }

    /* 성공 */
    critical_section();

    for (i = n - 1; i >= 0; i--)
        ww_mutex_unlock(&locks[i]);
ww_acquire_fini(&ctx);

패턴 2: 주소 정렬 후 획득 (ww_mutex 불필요)

/* 잠글 객체가 미리 알려진 경우: 주소 순서로 획득하면 데드락 없음 */
/* 이 경우 일반 mutex로도 충분 */

if (&a->lock < &b->lock) {
    mutex_lock(&a->lock);
    mutex_lock(&b->lock);
} else {
    mutex_lock(&b->lock);
    mutex_lock(&a->lock);
}

/* ww_mutex는 잠금 대상이 동적으로 결정되어
   주소 정렬이 불가능한 경우에 사용 */
언제 ww_mutex가 필요한가: 잠글 객체 목록이 런타임에 동적으로 결정되고, 잠금 중간에 새로운 객체가 추가될 수 있을 때 ww_mutex가 필요합니다. GPU command submission에서 버퍼 목록이 대표적입니다.

PREEMPT_RT 영향

CONFIG_PREEMPT_RT에서 일반 mutex_trt_mutex 기반의 sleeping lock으로 변환됩니다. ww_mutex도 이 변환의 영향을 받습니다.

항목PREEMPT_NONE/VOLUNTARYPREEMPT_RT
ww_mutex.basestruct mutex (owner + wait_list)struct rt_mutex_base 기반
Priority Inheritance없음ww_mutex 대기자도 PI chain 참여
wound 전파동일PI boosting과 wound가 상호작용
optimistic spinning지원 (fast/pending path)비활성화 (sleeping lock)
인터럽트 컨텍스트사용 불가 (sleeping lock)사용 불가 (동일)
PREEMPT_RT에서 ww_mutex의 내부 변환 일반 커널 ww_mutex.base = struct mutex Fast Path Opt. Spinning ww_ctx stamp 비교 → wound/wait PREEMPT_RT ww_mutex.base = rt_mutex_base PI Chain No Spinning ww_ctx stamp 비교 + PI boost RT
PREEMPT_RT에서 ww_mutex는 rt_mutex 기반으로 변환되어 Priority Inheritance가 추가됩니다
/* PREEMPT_RT에서의 ww_mutex 내부 구조 변화 */

#ifdef CONFIG_PREEMPT_RT
struct mutex {
    struct rt_mutex_base  rtmutex;   /* rt_mutex 기반! */
    /* PI: rb-tree 대기열, priority boosting */
};
#else
struct mutex {
    atomic_long_t        owner;     /* CAS 기반 fast path */
    struct optimistic_spin_queue osq; /* MCS 기반 spinning */
    struct list_head      wait_list;
};
#endif

/* ww_mutex는 어느 경우든 struct mutex를 내장하므로
   PREEMPT_RT 시 자동으로 rt_mutex 기반으로 전환 */

실전 사용 패턴

패턴: DRM Atomic Commit

/* DRM atomic commit의 전형적인 ww_mutex 사용 */

int drm_atomic_commit(struct drm_atomic_state *state)
{
    struct drm_modeset_acquire_ctx *ctx = state->acquire_ctx;
    int ret;

retry:
    /* 필요한 CRTC, connector, plane 잠금 */
    drm_for_each_crtc_in_state(state, crtc, crtc_state, i) {
        ret = drm_modeset_lock(&crtc->mutex, ctx);
        if (ret)
            goto fail;
    }

    /* ... connector, plane 잠금 ... */

    ret = drm_atomic_check_only(state);
    if (!ret)
        ret = drm_atomic_helper_commit(state);

fail:
    if (ret == -EDEADLK) {
        drm_modeset_backoff(ctx);   /* 모든 잠금 해제 + slow path */
        goto retry;
    }
    return ret;
}

패턴: dma_resv 다중 버퍼 잠금

/* GPU command submission: 여러 BO를 동시에 잠금 */

struct drm_exec exec;
drm_exec_init(&exec, DRM_EXEC_INTERRUPTIBLE_WAIT, 0);

drm_exec_until_all_locked(&exec) {
    /* 각 BO의 dma_resv.lock (ww_mutex) 획득 */
    drm_exec_prepare_array(&exec, objs, num_objs, 1);
    /* 내부적으로 -EDEADLK 재시도 자동 처리 */
}

/* 모든 BO 잠금 보유: 펜스 추가, 렌더링 제출 */
for (i = 0; i < num_objs; i++)
    dma_resv_add_fence(objs[i]->resv, fence, usage);

drm_exec_fini(&exec);

패턴: i915 GEM Execbuffer

/* drivers/gpu/drm/i915/gem/i915_gem_execbuffer.c */

/* i915 execbuffer: ww_mutex로 버퍼 오브젝트 잠금 */
static int eb_reserve(struct i915_execbuffer *eb)
{
    struct ww_acquire_ctx *ctx = &eb->ww;

    /* 모든 BO에 대해 dma_resv_lock(ww_mutex_lock) */
    for (i = 0; i < eb->buffer_count; i++) {
        struct i915_vma *vma = eb->vma[i];
        ret = i915_gem_object_lock(vma->obj, ctx);
        if (ret == -EDEADLK)
            return ret;   /* 호출자가 재시도 */
    }
    return 0;
}

안티패턴

주의: 다음 패턴들은 데이터 무결성(Integrity) 파괴나 데드락을 유발합니다.

안티패턴 1: -EDEADLK 시 잠금 해제 누락

/* 잘못된 코드: 이미 획득한 잠금을 해제하지 않고 재시도 */

ret = ww_mutex_lock(&a, &ctx);
ret = ww_mutex_lock(&b, &ctx);
if (ret == -EDEADLK) {
    /* BUG: a를 해제하지 않고 slow path 진입! */
    ww_mutex_lock_slow(&b, &ctx);  /* ctx->acquired > 0 → WARN */
}

안티패턴 2: 재시도 시 ctx 재초기화

/* 잘못된 코드: 재시도 때 새 stamp를 받으면
   younger가 되어 livelock 가능 */

retry:
    ww_acquire_init(&ctx, &my_class);  /* BUG: 매번 새 stamp! */
    ret = ww_mutex_lock(&a, &ctx);
    if (ret == -EDEADLK) {
        ww_acquire_fini(&ctx);
        goto retry;  /* 새 stamp → 항상 youngest → 항상 양보 */
    }

안티패턴 3: ww_mutex_lock_slow 대신 ww_mutex_lock 사용

/* 잘못된 코드: 경합 잠금에 일반 lock 사용 */

retry:
    if (contended) {
        /* BUG: lock_slow 대신 lock 사용 → 또 -EDEADLK 가능 */
        ret = ww_mutex_lock(contended, &ctx);
        /* 올바른: ww_mutex_lock_slow(contended, &ctx); */
    }

안티패턴 4: 서로 다른 ww_class의 mutex 혼합

/* 잘못된 코드: 다른 class의 mutex를 같은 ctx로 잠금 */

DEFINE_WW_CLASS(class_a);
DEFINE_WW_CLASS(class_b);

ww_acquire_init(&ctx, &class_a);
ww_mutex_lock(&lock_of_class_b, &ctx);  /* BUG: class 불일치! */
/* lockdep이 이를 감지하여 경고 출력 */

디버깅과 lockdep

ww_mutex는 lockdep과 밀접하게 통합되어, 잘못된 사용 패턴을 런타임에 감지합니다.

lockdep이 감지하는 ww_mutex 오류

검사 항목트리거 조건메시지 예시
Class 불일치ctx와 mutex의 ww_class가 다름BUG: ww_mutex class mismatch
ctx 재사용fini 없이 ctx를 다시 initBUG: ww_acquire_ctx still active
unlock 순서획득 역순이 아닌 해제lockdep: possible circular dependency
slow path 위반lock_slow 호출 시 acquired > 0WARN: ww_mutex lock_slow with locks held
이중 잠금같은 ctx로 같은 mutex 두 번 잠금BUG: ww_mutex trylock in wrong context

디버그 관련 설정

ww_mutex 전용 디버그 옵션은 CONFIG_DEBUG_WW_MUTEX_SLOWPATH=y (slow path 강제 진입으로 경합 시뮬레이션)입니다. CONFIG_PROVE_LOCKING, CONFIG_LOCK_STAT, CONFIG_DEBUG_LOCK_ALLOC 등 공통 잠금 디버깅 옵션은 lockdep — 커널 설정을 참고하세요.

ww_mutex API별 lockdep 검증 지점 ww_acquire_init ctx 등록 ww_mutex_lock class 검증 ww_mutex_unlock 순서 검증 ww_acquire_fini 잔여 잠금 검증 lockdep 검증 상세 init: - ww_class에 대한 lock_acquire() 등록 - 이전 ctx가 활성 상태이면 BUG lock: - mutex.ww_class == ctx.ww_class 검증 - 동일 mutex 이중 잠금 감지 → return -EALREADY unlock: - ctx->acquired 카운터 감소, 올바른 ctx 소유 검증 fini: - ctx->acquired == 0 검증 (잔여 잠금이 있으면 WARN) - lock_release()로 lockdep 등록 해제
lockdep은 ww_mutex의 각 API 호출 시점에서 class 일관성, 순서, 잔여 잠금을 검증합니다

lock_stat으로 경합 분석

CONFIG_LOCK_STAT=y 활성화 후 grep ww_mutex /proc/lock_stat으로 ww_class별 경합 통계(con-bounces, contentions, waittime)를 확인할 수 있습니다. /proc/lock_stat 필드 설명과 상세 사용법은 lockdep의 /proc/lock_stat 섹션을 참고하세요.

커널 설정

옵션기본값설명
CONFIG_WW_MUTEX_SELFTESTnww_mutex 셀프 테스트 모듈 빌드
CONFIG_DEBUG_WW_MUTEX_SLOWPATHnslow path 강제 진입 (경합 시뮬레이션)
CONFIG_PROVE_LOCKING, CONFIG_LOCK_STAT, CONFIG_DEBUG_LOCK_ALLOC 등 공통 잠금 디버깅 옵션은 lockdep — 커널 설정을 참고하세요.

셀프 테스트

# ww_mutex 셀프 테스트 실행
modprobe test-ww_mutex

# 결과 확인
dmesg | grep -i ww_mutex

# 출력 예시:
# test_ww_mutex: ww_mutex test cases passed
# test_ww_mutex: stress test (Wound-Wait): passed
# test_ww_mutex: stress test (Wait-Die): passed
개발 시 추천 설정: CONFIG_DEBUG_MUTEXES=y + CONFIG_PROVE_LOCKING=y + CONFIG_DEBUG_WW_MUTEX_SLOWPATH=y를 함께 활성화하면 ww_mutex 관련 버그를 조기에 발견할 수 있습니다. DEBUG_WW_MUTEX_SLOWPATH는 인위적으로 경합을 발생시켜 -EDEADLK 재시도 경로를 테스트합니다.

ww_mutex_lock() 소스 코드 완전 분석

ww_mutex_lock()의 실제 구현은 kernel/locking/ww_mutex.h에 있으며, 일반 mutex의 fast path와 ww_mutex 고유의 context 설정, stamp 비교, slowpath 진입이 결합된 복잡한 구조입니다. 단계별로 소스 코드를 추적합니다.

진입점(Entry Point): ww_mutex_lock

/* include/linux/ww_mutex.h */

static inline int
ww_mutex_lock(struct ww_mutex *lock,
              struct ww_acquire_ctx *ctx)
{
    /* ctx가 NULL이면 일반 mutex_lock과 동일 */
    if (ctx)
        return __ww_mutex_lock(lock, ctx, TASK_UNINTERRUPTIBLE);

    mutex_lock(&lock->base);
    return 0;
}

__ww_mutex_lock 구현

/* kernel/locking/mutex.c — __ww_mutex_lock 핵심 */

static int __sched
__ww_mutex_lock(struct ww_mutex *lock,
                struct ww_acquire_ctx *ctx,
                unsigned int state)
{
    int ret;

    might_sleep();

    /* Fast path: 뮤텍스가 비어있으면 CAS로 즉시 획득 */
    if (__mutex_trylock_fast(&lock->base)) {
        /* fast path 성공: ctx를 lock에 연결 */
        ww_mutex_set_context_fastpath(lock, ctx);
        return 0;
    }

    /* Slow path: 경합 발생, 대기열 진입 */
    ret = __ww_mutex_lock_slowpath(lock, ctx, state);
    return ret;
}

ww_mutex_set_context_fastpath

/* kernel/locking/ww_mutex.h */

static inline void
ww_mutex_set_context_fastpath(struct ww_mutex *lock,
                              struct ww_acquire_ctx *ctx)
{
    /* lock->ctx 설정 (보유 컨텍스트 등록) */
    WRITE_ONCE(lock->ctx, ctx);

    /* 중요: smp_mb()로 ctx 설정이 다른 CPU에 가시적이게 */
    /* 다른 CPU의 waiter가 stamp 비교를 올바르게 할 수 있도록 */
    smp_mb__after_atomic();

    ctx->acquired++;

    /* 대기열에 older waiter가 있으면 wound 처리 필요 */
    if (unlikely(!list_empty(&lock->base.wait_list)))
        __ww_mutex_check_waiters(lock, ctx);
}
Fast path의 함정: CAS로 잠금을 획득한 후에도 대기열 검사가 필요합니다. fast path 성공과 대기열 진입은 동시에 일어날 수 있으며, 이미 대기 중인 older 트랜잭션이 있으면 wound 처리를 시작해야 합니다.

slowpath 진입과 stamp 비교

/* kernel/locking/ww_mutex.h — slowpath 핵심 로직 */

static int
__ww_mutex_lock_slowpath(struct ww_mutex *lock,
                         struct ww_acquire_ctx *ctx,
                         unsigned int state)
{
    struct mutex_waiter waiter;
    struct ww_acquire_ctx *hold_ctx;
    int ret = 0;

    raw_spin_lock(&lock->base.wait_lock);

    /* 보유자의 ctx 읽기 */
    hold_ctx = READ_ONCE(lock->ctx);

    /* ctx가 없으면 = 일반 mutex 경합, 단순 대기 */
    if (!hold_ctx) {
        __ww_mutex_add_waiter(&waiter, lock, ctx);
        goto wait;
    }

    /* 동일 ctx인지 검사 → -EALREADY (이중 잠금) */
    if (hold_ctx == ctx) {
        ret = -EALREADY;
        goto out;
    }

    /* 핵심: stamp 비교 */
    if (__ww_ctx_stamp_after(ctx, hold_ctx)) {
        /* 우리(ctx)가 younger */
        if (ctx->is_wait_die) {
            /* Wait-Die: younger는 즉시 die */
            ret = -EDEADLK;
            goto out;
        }
        /* Wound-Wait: younger는 wait (대기열 삽입) */
        __ww_mutex_add_waiter(&waiter, lock, ctx);
    } else {
        /* 우리(ctx)가 older → 보유자를 wound */
        __ww_mutex_wound(lock, ctx, hold_ctx);
        __ww_mutex_add_waiter(&waiter, lock, ctx);
    }

wait:
    /* 대기 루프: schedule() 반복 */
    for (;;) {
        if (__mutex_trylock(&lock->base))
            break;

        /* wounded 상태 검사 */
        if (ctx->wounded) {
            ret = -EDEADLK;
            break;
        }

        set_current_state(state);
        raw_spin_unlock(&lock->base.wait_lock);
        schedule();
        raw_spin_lock(&lock->base.wait_lock);
    }

out:
    raw_spin_unlock(&lock->base.wait_lock);
    return ret;
}
ww_mutex_lock() 소스 레벨 내부 흐름 ww_mutex_lock(lock, ctx) ctx == NULL? YES mutex_lock(&base) NO __mutex_trylock_fast? YES set_context_fastpath NO __ww_mutex_lock_slowpath hold_ctx 존재? NULL 단순 대기 ctx stamp > hold stamp? YES (younger) WW: wait / add_waiter WD: return -EDEADLK NO (older) __ww_mutex_wound + add_waiter 대기 루프: schedule() 반복 trylock 성공 또는 wounded → 탈출
ww_mutex_lock()은 ctx NULL 검사, fast path CAS, slowpath stamp 비교, wound/wait 결정의 4단계 구조입니다
주의: __ww_mutex_check_waiters()는 fast path에서도 호출됩니다. CAS로 잠금을 획득한 직후, 대기열에 이미 older waiter가 있으면 그 waiter에게 wound하지 않고 자신이 양보할 수 있는지를 판단합니다. 이 체크가 없으면 older waiter가 영원히 대기할 수 있습니다.

Wound 전파 소스 분석

__ww_mutex_wound()는 ww_mutex의 핵심 메커니즘입니다. older 요청자가 younger 보유자에게 양보를 요청하는 과정의 모든 경로를 소스 레벨에서 분석합니다.

wound 감지와 wounded 플래그

/* kernel/locking/ww_mutex.h — __ww_mutex_wound 전체 구현 */

static void
__ww_mutex_wound(struct ww_mutex *lock,
                  struct ww_acquire_ctx *wound_ctx,  /* older 요청자 */
                  struct ww_acquire_ctx *hold_ctx)   /* younger 보유자 */
{
    /* 1단계: Wait-Die 모드에서는 wound 불가 */
    if (hold_ctx->is_wait_die)
        return;

    /* 2단계: 이미 wounded 상태면 중복 wound 방지 */
    if (hold_ctx->wounded)
        return;

    /* 3단계: stamp 재확인 (race condition 방어) */
    /* wound_ctx가 hold_ctx보다 정말 older인지 */
    if (__ww_ctx_stamp_after(wound_ctx, hold_ctx))
        return;  /* 아닌 경우: wound 하지 않음 */

    /* 4단계: wounded 플래그 설정 */
    hold_ctx->wounded = 1;

    /* 5단계: 보유자가 다른 ww_mutex의 대기열에서 */
    /*   자고 있을 수 있음 → 깨워서 -EDEADLK 확인하게 함 */
    if (hold_ctx->task != current)
        wake_up_process(hold_ctx->task);
}

wounded waiter의 깨어남 경로

wound 신호를 받은 보유자가 실제로 -EDEADLK를 반환하는 경로는 두 가지입니다:

/* 경로 1: 보유자가 다른 ww_mutex 대기열에서 sleeping */
/* → wake_up_process()로 깨어남 */
/* → 대기 루프에서 wounded 플래그 검사 */

for (;;) {
    if (__mutex_trylock(&lock->base))
        break;

    /* ← wounded 검사: 여기서 -EDEADLK 반환 */
    if (ctx->wounded) {
        ret = -EDEADLK;
        break;
    }

    schedule();
}

/* 경로 2: 보유자가 현재 임계 영역에서 실행 중 */
/* → 다음 ww_mutex_lock() 호출 시 ctx->wounded 검사 */
/* → 보유자의 다음 잠금 시도에서 -EDEADLK */

-EDEADLK 반환 후 정리

/* -EDEADLK 반환 시 waiter 정리 */

if (ret == -EDEADLK) {
    /* 대기열에서 waiter 제거 */
    __mutex_remove_waiter(&lock->base, &waiter);

    /* wounded 플래그는 유지 — 호출자가 모든 잠금 해제 후 */
    /* ww_mutex_lock_slow()에서 cleared */

    /* contending_lock 설정 (디버그 모드) */
#ifdef CONFIG_DEBUG_MUTEXES
    ctx->contending_lock = lock;
#endif
}
Wound 전파 소스 레벨 흐름 Older 요청자: __ww_mutex_lock_slowpath is_wait_die? YES: return NO already wounded? YES: return hold_ctx->wounded = 1 task != current? YES wake_up_process() 보유자(younger)의 -EDEADLK 수신 경로 경로 1: 다른 lock 대기열에서 sleeping wake_up → 대기 루프 → wounded 검사 → return -EDEADLK 경로 2: 임계 영역 실행 중 (running) → 다음 ww_mutex_lock() 호출 시 → wounded 검사 → return -EDEADLK
wound 전파는 is_wait_die/already-wounded 체크 후 wounded 플래그를 설정하고, sleeping 보유자는 wake_up으로 깨웁니다
참고: wake_up_process()는 보유자가 다른 ww_mutex의 대기열에서 schedule()으로 잠들어 있을 때만 유의미합니다. 보유자가 CPU에서 실행 중이면 wake_up은 no-op이 되지만, wounded 플래그는 이미 설정되어 있으므로 다음 잠금 시도에서 반드시 감지됩니다.

ww_mutex_unlock() 소스 분석

ww_mutex_unlock()은 일반 mutex_unlock에 ww_acquire_ctx 정리를 추가한 것입니다. 해제 시 ctx 연결 해제와 acquired 카운터 감소가 핵심입니다.

ww_mutex_unlock 구현

/* include/linux/ww_mutex.h */

static inline void
ww_mutex_unlock(struct ww_mutex *lock)
{
    struct ww_acquire_ctx *ctx = lock->ctx;

    /* 1단계: ctx가 있으면 정리 */
    if (ctx) {
#ifdef CONFIG_DEBUG_MUTEXES
        /* 디버그: lock의 ww_class와 ctx의 ww_class 일치 확인 */
        DEBUG_LOCKS_WARN_ON(lock->ww_class != ctx->ww_class);
#endif
        /* acquired 카운터 감소 */
        ctx->acquired--;

        /* lock->ctx 연결 해제 */
        WRITE_ONCE(lock->ctx, NULL);
    }

    /* 2단계: 기반 mutex 해제 (대기자 깨우기 포함) */
    mutex_unlock(&lock->base);
}

대기자 깨우기 메커니즘

/* mutex_unlock 내부의 대기자 깨우기 */

static void
__mutex_unlock_slowpath(struct mutex *lock)
{
    struct mutex_waiter *waiter;

    raw_spin_lock(&lock->wait_lock);

    /* 대기열의 첫 번째 waiter 선택 */
    /* ww_mutex의 경우 stamp 순서로 정렬되어 있으므로 */
    /* 가장 older한 waiter가 먼저 선택됨 */
    waiter = list_first_entry(&lock->wait_list,
                              struct mutex_waiter, list);

    wake_up_process(waiter->task);
    raw_spin_unlock(&lock->wait_lock);
}

컨텍스트 정리 순서

단계동작검증 (DEBUG 모드)
1ctx->acquired--acquired가 0 미만이면 WARN
2lock->ctx = NULLWRITE_ONCE로 원자적 설정
3mutex_unlock(&base)owner 필드 clear + waiter wake
4대기자 깨우기stamp 순서 최우선 waiter 선택
주의: lock->ctx = NULLmutex_unlock()의 순서가 중요합니다. ctx를 먼저 NULL로 설정해야 다음 획득자가 올바른 ctx를 설정할 수 있습니다. 순서가 뒤바뀌면 race condition으로 잘못된 stamp 비교가 발생할 수 있습니다.

아키텍처별 원자적 연산(Atomic Operation)

ww_mutex의 fast path는 cmpxchg(Compare-And-Exchange)에 의존하며, stamp 발급은 atomic_long_inc_return을 사용합니다. 이러한 원자적 연산은 아키텍처마다 구현이 다릅니다.

fast path의 cmpxchg

/* kernel/locking/mutex.c — __mutex_trylock_fast */

static inline bool
__mutex_trylock_fast(struct mutex *lock)
{
    unsigned long zero = 0;

    /* owner가 0(unlocked)이면 current로 교체 */
    if (atomic_long_try_cmpxchg_acquire(
            &lock->owner, &zero,
            (unsigned long)current))
        return true;

    return false;
}

x86: LOCK CMPXCHG

; x86에서 atomic_long_try_cmpxchg_acquire 구현
; ACQUIRE semantics = LOCK prefix (x86은 TSO이므로 추가 배리어 불필요)

lock cmpxchg [rdi], rsi     ; atomic CAS: if (*rdi == rax) *rdi = rsi
                             ; ZF set on success
setz al                      ; return ZF (성공 여부)

; LOCK prefix는 x86에서 full memory barrier를 의미
; 따라서 별도의 acquire fence가 불필요

ARM64: LDAXR/STXR (LSE: CAS)

// ARM64에서 cmpxchg — 두 가지 구현

// (1) LL/SC 기반 (ARMv8.0)
1: ldaxr   x2, [x0]          // Load-Acquire Exclusive
   cmp     x2, x3             // expected 값과 비교
   b.ne    2f                 // 불일치 → 실패
   stxr    w4, x1, [x0]       // Store Exclusive (acquire는 load에서)
   cbnz    w4, 1b             // store 실패 → 재시도
2:

// (2) LSE 확장 (ARMv8.1+, 더 효율적)
   cas     x3, x1, [x0]       // Compare-And-Swap (단일 명령어)
   // x3 = expected, x1 = desired, [x0] = target
   // casa (acquire), casal (acquire+release) 변형 존재

stamp 발급: atomic_long_inc_return

/* ww_acquire_init에서의 stamp 발급 */
ctx->stamp = atomic_long_inc_return_relaxed(&ww_class->stamp);

/* _relaxed: 순서 보장 불필요 (stamp 값만 유일하면 됨) */
/*   x86: lock xadd (항상 full barrier) */
/*   ARM64: ldxr/stxr 루프 또는 ldadd (LSE) */
/*   RISC-V: amoadd.w (relaxed ordering) */

아키텍처별 원자적 연산 비교

연산x86ARM64RISC-V
cmpxchg (fast path)LOCK CMPXCHGLDAXR/STXR 또는 CASLR.W/SC.W
atomic_inc_returnLOCK XADDLDXR/ADD/STXR 또는 LDADDAMOADD.W
ACQUIRE semanticsLOCK prefix (TSO)LDAXR 또는 DMB ISHLD.aq 접미사
RELEASE semantics암시적 (TSO)STLXR 또는 DMB ISH.rl 접미사
메모리 모델TSO (강한 순서)약한 순서RVWMO (약한 순서)
참고: x86의 TSO(Total Store Order) 메모리 모델 덕분에 _relaxed 변형도 실제로는 full barrier로 동작합니다. ARM64와 RISC-V에서는 relaxed와 acquire/release의 차이가 성능에 영향을 미칩니다.

벤치마크: ww_mutex 경합 시나리오

ww_mutex의 실제 성능은 경합 빈도, 잠금 수, 알고리즘 선택에 따라 크게 달라집니다. 커널의 test-ww_mutex 셀프 테스트와 DRM 워크로드 기반 벤치마크 결과를 분석합니다.

재시도 오버헤드(Overhead) 분석

/* ww_mutex 벤치마크 시나리오 */

/* 시나리오 1: 2 스레드, 2 잠금 — 최소 경합 */
/* 시나리오 2: 4 스레드, 4 잠금 — 중간 경합 */
/* 시나리오 3: 8 스레드, 16 잠금 — 높은 경합 */
/* 시나리오 4: 16 스레드, 32 잠금 — 극심한 경합 */

/* 측정 항목: */
/*   - 잠금 획득 완료까지 평균 시간 (ns) */
/*   - -EDEADLK 발생 횟수 / 전체 시도 */
/*   - 최대 재시도 깊이 */
시나리오알고리즘평균 획득 시간-EDEADLK 비율최대 재시도
2T/2LWound-Wait~150 ns2.1%1
2T/2LWait-Die~180 ns5.3%2
4T/4LWound-Wait~420 ns8.7%3
4T/4LWait-Die~680 ns22.4%5
8T/16LWound-Wait~1.2 us12.3%4
8T/16LWait-Die~2.8 us38.1%8
16T/32LWound-Wait~3.5 us18.6%6
16T/32LWait-Die~8.2 us52.7%12
Wound-Wait vs Wait-Die: -EDEADLK 비율 비교 시나리오 (스레드/잠금 수) -EDEADLK 비율 (%) 0 10 20 30 40 50 2T/2L 4T/4L 8T/16L 16T/32L Wound-Wait Wait-Die
경합 증가 시 Wait-Die의 -EDEADLK 비율이 Wound-Wait보다 2-3배 높아집니다

DRM 워크로드 영향

# DRM atomic commit에서의 ww_mutex 경합 측정
# perf lock을 사용한 실제 워크로드 프로파일링

perf lock record -- glmark2 --run-forever &
sleep 30 && kill %1
perf lock report --sort acquired,contended,wait_total

# 출력 예시 (4K 듀얼 모니터, AMD GPU):
#                    Name   acquired  contended  wait total
# reservation_ww_class     48231       1247       3.2 ms
# crtc_ww_class             892         34       0.1 ms
성능 최적화 팁: 경합이 높은 환경에서는 잠금 획득 순서를 가능한 한 일정하게 유지하면 -EDEADLK 빈도가 줄어듭니다. 완전한 정렬이 불가능하더라도, 부분 정렬만으로도 재시도 횟수를 크게 줄일 수 있습니다.

메모리 순서와 ww_mutex

ww_mutex는 일반 mutex의 ACQUIRE/RELEASE 의미론 위에 stamp 가시성과 wounded 플래그 순서 보장(Ordering)이 추가됩니다. 이러한 메모리 순서 보장이 ww_mutex의 정확성(correctness)에 필수적입니다.

ACQUIRE/RELEASE 기본 의미론

/* ww_mutex의 ACQUIRE/RELEASE 경계 */

/* ACQUIRE: ww_mutex_lock 성공 시 */
/* - 잠금 이후의 모든 메모리 접근이 잠금 획득 이전으로 재배치되지 않음 */
/* - lock->ctx 설정이 다른 CPU에서 가시적 */

/* RELEASE: ww_mutex_unlock 시 */
/* - 임계 영역의 모든 메모리 접근이 잠금 해제 이후로 재배치되지 않음 */
/* - lock->ctx = NULL이 다른 CPU에서 즉시 가시적 */

/* fast path CAS가 ACQUIRE semantics를 제공: */
atomic_long_try_cmpxchg_acquire(&lock->owner, ...);
/*                     ^^^^^^^ */
/* _acquire 접미사 = load 이후 명령이 앞으로 재배치 불가 */

stamp 가시성 보장

/* ww_mutex_set_context_fastpath에서의 배리어 */

WRITE_ONCE(lock->ctx, ctx);    /* (a) ctx 포인터 설정 */

/* smp_mb 필요 이유: */
/* 다른 CPU의 waiter가 lock->ctx를 읽고 */
/* ctx->stamp를 참조할 때 */
/* (a)가 (b) 이전에 가시적이어야 함 */
smp_mb__after_atomic();         /* full memory barrier */

/* 다른 CPU: */
hold_ctx = READ_ONCE(lock->ctx); /* lock->ctx 읽기 */
if (hold_ctx)
    __ww_ctx_stamp_after(ctx, hold_ctx);
    /* hold_ctx->stamp 접근: (a) 이후 가시적이어야 */

wounded 플래그 순서 보장

/* wound 전파에서의 메모리 순서 */

/* CPU 0 (older 요청자): */
hold_ctx->wounded = 1;        /* (1) wounded 설정 */
/* wait_lock spinlock이 RELEASE barrier 제공 */
wake_up_process(hold_ctx->task); /* (2) 깨우기 */

/* CPU 1 (younger 보유자, 깨어난 후): */
/* schedule() 복귀 시 ACQUIRE barrier 내재 */
if (ctx->wounded)            /* (3) wounded 읽기 */
    ret = -EDEADLK;

/* (1) → smp_mb (spinlock release) → (2) → schedule barrier → (3) */
/* 따라서 (3)에서 wounded=1이 반드시 가시적 */
ww_mutex 메모리 순서 보장 포인트 CPU 0 (Older 요청자) raw_spin_lock(wait_lock) — ACQUIRE hold_ctx->wounded = 1 wake_up_process(hold_ctx->task) raw_spin_unlock(wait_lock) — RELEASE smp_mb__after_atomic() WRITE_ONCE(lock->ctx, ctx) ctx->acquired++ __ww_mutex_check_waiters() CPU 1 (Younger 보유자) sleeping in schedule() wake_up → schedule() return — ACQUIRE if (ctx->wounded) → 반드시 1 관측 ret = -EDEADLK __mutex_remove_waiter + return 순서 보장
spinlock의 ACQUIRE/RELEASE와 schedule()의 배리어가 wounded 플래그의 CPU 간 가시성을 보장합니다
참고: WRITE_ONCEREAD_ONCE는 컴파일러 최적화(store/load 제거, splitting)만 방지하며, CPU 간 순서 보장은 하지 않습니다. CPU 간 순서는 spinlock의 ACQUIRE/RELEASE와 smp_mb__after_atomic()이 담당합니다.

DRM Modeset Lock 소스 추적

DRM atomic modeset은 ww_mutex의 가장 정교한 사용자입니다. drm_modeset_lock, drm_modeset_acquire_ctx, drm_modeset_backoff의 전체 소스 경로를 추적합니다.

drm_modeset_acquire_ctx 초기화

/* drivers/gpu/drm/drm_modeset_lock.c */

void drm_modeset_acquire_init(
    struct drm_modeset_acquire_ctx *ctx,
    uint32_t flags)
{
    /* ww_acquire_ctx 초기화: stamp 할당 */
    ww_acquire_init(&ctx->ww_ctx, &crtc_ww_class);

    /* 잠금 목록 초기화 */
    INIT_LIST_HEAD(&ctx->locked);

    /* 경합 잠금 추적 */
    ctx->contended = NULL;
    ctx->trylock_only = 0;

    if (flags & DRM_MODESET_ACQUIRE_INTERRUPTIBLE)
        ctx->interruptible = 1;
}

drm_modeset_lock 구현

/* drm_modeset_lock — ww_mutex_lock 래퍼 */

int drm_modeset_lock(struct drm_modeset_lock *lock,
                     struct drm_modeset_acquire_ctx *ctx)
{
    int ret;

    /* trylock_only 모드: 대기 없이 시도 */
    if (ctx->trylock_only) {
        if (!ww_mutex_trylock(&lock->mutex,
                              &ctx->ww_ctx))
            return -EBUSY;
        goto locked;
    }

    /* interruptible 또는 일반 잠금 */
    if (ctx->interruptible)
        ret = ww_mutex_lock_interruptible(
                  &lock->mutex, &ctx->ww_ctx);
    else
        ret = ww_mutex_lock(
                  &lock->mutex, &ctx->ww_ctx);

    if (ret == -EDEADLK) {
        /* 경합 잠금 기록: backoff에서 사용 */
        ctx->contended = lock;
        return -EDEADLK;
    }
    if (ret)
        return ret;

locked:
    /* 잠금 목록에 추가 (나중에 일괄 해제용) */
    list_add(&lock->head, &ctx->locked);
    return 0;
}

drm_modeset_backoff: 재시도 핵심

/* drm_modeset_backoff — -EDEADLK 후 자동 재시도 */

int drm_modeset_backoff(
    struct drm_modeset_acquire_ctx *ctx)
{
    struct drm_modeset_lock *contended = ctx->contended;

    /* 1단계: 보유한 모든 잠금 해제 */
    drm_modeset_drop_locks(ctx);

    /* 2단계: wounded 플래그 클리어 */
    ctx->contended = NULL;

    /* 3단계: 경합 잠금에 slow path로 대기 */
    if (ctx->interruptible)
        return ww_mutex_lock_slow_interruptible(
                   &contended->mutex, &ctx->ww_ctx);

    ww_mutex_lock_slow(&contended->mutex,
                        &ctx->ww_ctx);

    /* 경합 잠금을 잠금 목록에 추가 */
    list_add(&contended->head, &ctx->locked);

    return 0;
}
DRM Modeset Lock/Backoff 전체 흐름 drm_modeset_acquire_init(ctx, flags) retry: drm_modeset_lock(crtc, ctx) drm_modeset_lock(connector, ctx) drm_modeset_lock(plane, ctx) -EDEADLK drm_modeset_backoff 1. drop_locks(ctx) 2. contended = NULL 3. ww_mutex_lock_slow (contended, ctx) 4. list_add(locked) goto retry 성공 drm_atomic_helper_commit(state) drm_modeset_drop_locks(ctx) drm_modeset_acquire_fini(ctx) crtc_ww_class DEFINE_WW_CLASS
DRM modeset은 acquire_init → lock(retry) → backoff(on EDEADLK) → commit → drop → fini 흐름입니다

drm_modeset_lock_all: 전역 잠금

/* 모든 modeset 객체를 한 번에 잠금 (legacy path) */

int drm_modeset_lock_all_ctx(
    struct drm_device *dev,
    struct drm_modeset_acquire_ctx *ctx)
{
    int ret;

    /* mode_config.connection_mutex 잠금 */
    ret = drm_modeset_lock(&dev->mode_config.connection_mutex, ctx);
    if (ret)
        return ret;

    /* 모든 CRTC 잠금 */
    drm_for_each_crtc(crtc, dev) {
        ret = drm_modeset_lock(&crtc->mutex, ctx);
        if (ret)
            return ret;
    }

    /* 모든 Plane 잠금 */
    drm_for_each_plane(plane, dev) {
        ret = drm_modeset_lock(&plane->mutex, ctx);
        if (ret)
            return ret;
    }

    return 0;
}
atomic commit vs lock_all: drm_modeset_lock_all_ctx()는 legacy 경로로, 모든 객체를 잠급니다. 현대적인 atomic commit은 실제로 변경되는 객체만 선택적으로 잠가서 경합을 최소화합니다.

dma_resv 심층: GEM/TTM 버퍼 예약

dma_resv(DMA reservation)는 GPU 버퍼 오브젝트의 동기화 핵심입니다. 내부 ww_mutex로 다중 버퍼 잠금을 구현하고, DMA fence로 GPU 작업 간 의존성을 관리합니다.

dma_resv 구조체(Struct) 심층

/* include/linux/dma-resv.h */

struct dma_resv {
    struct ww_mutex       lock;      /* ww_mutex! */
    struct dma_resv_list  *fences;   /* RCU-protected fence list */
};

/* 전역 ww_class */
DEFINE_WW_CLASS(reservation_ww_class);

struct dma_resv_list {
    struct rcu_head       rcu;
    u32                  num_fences;
    u32                  max_fences;
    struct dma_fence      *table[];  /* flexible array */
};

dma_resv_lock API

/* dma_resv_lock — 버퍼 예약 잠금 */

static inline int
dma_resv_lock(struct dma_resv *obj,
              struct ww_acquire_ctx *ctx)
{
    return ww_mutex_lock(&obj->lock, ctx);
}

static inline int
dma_resv_lock_interruptible(struct dma_resv *obj,
                            struct ww_acquire_ctx *ctx)
{
    return ww_mutex_lock_interruptible(&obj->lock, ctx);
}

static inline void
dma_resv_lock_slow(struct dma_resv *obj,
                    struct ww_acquire_ctx *ctx)
{
    ww_mutex_lock_slow(&obj->lock, ctx);
}

static inline void
dma_resv_unlock(struct dma_resv *obj)
{
    ww_mutex_unlock(&obj->lock);
}

공유/배타 펜스 관리

/* dma_resv에 펜스 추가 — ww_mutex 보유 상태에서만 */

void dma_resv_add_fence(struct dma_resv *obj,
                        struct dma_fence *fence,
                        enum dma_resv_usage usage)
{
    /* lock이 보유 상태인지 확인 */
    dma_resv_assert_held(obj);

    /* usage 종류: */
    /* DMA_RESV_USAGE_WRITE — 배타적 쓰기 */
    /* DMA_RESV_USAGE_READ  — 공유 읽기 */
    /* DMA_RESV_USAGE_BOOKKEEP — 관리용 */

    /* fence list에 RCU-safe 추가 */
    dma_resv_list_add(obj, fence, usage);
}

implicit synchronization

/* implicit sync: 버퍼에 대한 GPU 작업 순서 보장 */

/* 1. 이전 GPU 작업의 fence를 읽기 */
dma_resv_for_each_fence(&cursor, obj,
                         DMA_RESV_USAGE_WRITE, fence) {
    /* 이전 write fence가 완료될 때까지 대기 */
    dma_fence_wait(fence, interruptible);
}

/* 2. 새 작업의 fence 추가 */
dma_resv_add_fence(obj, new_fence, DMA_RESV_USAGE_WRITE);

/* ww_mutex가 보장하는 것: */
/* - fence list 수정의 원자성 */
/* - 다중 버퍼 간 fence 추가의 일관성 */
/* - 데드락 없는 다중 버퍼 동시 잠금 */
dma_resv 구조와 ww_mutex/fence 연결 GEM Buffer Object struct drm_gem_object resv: dma_resv * dma_resv lock: ww_mutex fences: dma_resv_list * ww_mutex base: struct mutex ctx: ww_acquire_ctx * reservation_ww_class dma_resv_list (RCU) fence[0]: write_fence (USAGE_WRITE) fence[1]: read_fence (USAGE_READ) fence[2]: read_fence (USAGE_READ) fence[3]: bookkeep (USAGE_BOOKKEEP) ww_mutex 보유 중에만 fence 추가/제거 가능 RCU로 잠금 없이 fence 읽기 가능
dma_resv는 ww_mutex로 fence list 수정을 보호하고, RCU로 잠금 없는 읽기를 지원합니다
참고: dma_resv의 fence list는 RCU로 보호됩니다. fence를 추가/제거할 때는 ww_mutex가 필요하지만, fence를 읽기만 할 때는 rcu_read_lock()만으로 충분합니다. 이는 GPU 드라이버의 hot path에서 잠금 경합(Lock Contention)을 크게 줄입니다.

drm_exec: 현대적 다중 잠금 래퍼

drm_exec는 Linux 6.4에서 도입된 ww_mutex 다중 잠금 헬퍼입니다. -EDEADLK 재시도 루프를 자동화하여 GPU 드라이버 코드를 크게 단순화합니다. 기존 drm_gem_lock_reservations()를 대체합니다.

drm_exec 구조체

/* include/drm/drm_exec.h */

struct drm_exec {
    struct ww_acquire_ctx  ticket;    /* ww_acquire_ctx */
    uint32_t              flags;     /* DRM_EXEC_* 플래그 */
    uint32_t              num_objects;/* 잠금 대상 객체 수 */
    uint32_t              max_objects;/* 할당 크기 */
    struct drm_gem_object **objects;  /* 잠금 객체 배열 */
    struct drm_gem_object *contended; /* 경합 객체 */
    unsigned int          prelocked; /* slow path 잠금 수 */
};

drm_exec 핵심 API

/* drm_exec 사용 패턴 */

/* 1. 초기화 */
void drm_exec_init(struct drm_exec *exec,
                    uint32_t flags,
                    unsigned nr);
/* flags: DRM_EXEC_INTERRUPTIBLE_WAIT — 인터럽트 가능 대기 */
/*        DRM_EXEC_IGNORE_DUPLICATES — 중복 객체 무시 */
/* nr: 예상 객체 수 (사전 할당용) */

/* 2. 객체 잠금 (자동 재시도) */
int drm_exec_prepare_obj(struct drm_exec *exec,
                         struct drm_gem_object *obj,
                         unsigned int num_fences);

/* 3. 배열 일괄 잠금 */
int drm_exec_prepare_array(struct drm_exec *exec,
                           struct drm_gem_object **objects,
                           unsigned int num_objects,
                           unsigned int num_fences);

/* 4. 정리: 모든 잠금 해제 */
void drm_exec_fini(struct drm_exec *exec);

자동 재시도 매크로

/* drm_exec의 핵심: 자동 재시도 매크로 */

#define drm_exec_until_all_locked(exec) \
    while (drm_exec_cleanup(exec))

#define drm_exec_retry_on_contention(exec) \
    do { \
        if (unlikely((exec)->contended)) \
            continue; \
    } while (0)

/* drm_exec_cleanup 내부: */
static inline bool
drm_exec_cleanup(struct drm_exec *exec)
{
    if (likely(!exec->contended))
        return false;   /* 경합 없음 → 루프 종료 */

    /* 경합 발생: 모든 잠금 해제 */
    drm_exec_unlock_all(exec);

    /* 경합 객체에 slow path 잠금 */
    dma_resv_lock_slow(exec->contended->resv,
                        &exec->ticket);

    exec->prelocked++;
    exec->contended = NULL;
    return true;   /* 루프 재시도 */
}

실전 사용 예

/* GPU command submission에서의 drm_exec 사용 */

struct drm_exec exec;
int ret;

drm_exec_init(&exec, DRM_EXEC_INTERRUPTIBLE_WAIT
                      | DRM_EXEC_IGNORE_DUPLICATES, 0);

drm_exec_until_all_locked(&exec) {
    /* 소스 버퍼 잠금 */
    ret = drm_exec_prepare_obj(&exec, &src_bo->base, 1);
    drm_exec_retry_on_contention(&exec);
    if (ret)
        goto err;

    /* 대상 버퍼 잠금 */
    ret = drm_exec_prepare_obj(&exec, &dst_bo->base, 1);
    drm_exec_retry_on_contention(&exec);
    if (ret)
        goto err;

    /* 공유 커맨드 버퍼 잠금 */
    ret = drm_exec_prepare_obj(&exec, &cmd_bo->base, 0);
    drm_exec_retry_on_contention(&exec);
    if (ret)
        goto err;
}

/* 모든 BO 잠금 보유: GPU 작업 제출 */
dma_resv_add_fence(src_bo->base.resv, fence, DMA_RESV_USAGE_READ);
dma_resv_add_fence(dst_bo->base.resv, fence, DMA_RESV_USAGE_WRITE);
submit_to_gpu(ring, fence);

err:
drm_exec_fini(&exec);
drm_exec 자동 재시도 흐름 drm_exec_init(&exec, flags, 0) drm_exec_until_all_locked(&exec) drm_exec_prepare_obj(src) drm_exec_prepare_obj(dst) drm_exec_prepare_obj(cmd) retry_on contention → continue if contended drm_exec_cleanup 1. unlock_all() 2. dma_resv_lock_slow 3. prelocked++ 4. contended = NULL 재시도 모두 성공 임계 영역: fence 추가, GPU 제출 drm_exec_fini(&exec)
drm_exec는 while(drm_exec_cleanup) 루프로 -EDEADLK 재시도를 자동화합니다
비교 항목수동 ww_mutexdrm_exec
재시도 루프직접 구현until_all_locked 매크로
잠금 해제수동 역순 해제cleanup/fini 자동 해제
경합 추적contended 변수 관리exec->contended 자동
중복 객체수동 처리IGNORE_DUPLICATES 플래그
fence 예약별도 관리prepare_obj의 num_fences
도입 버전v3.11v6.4
권장 사항: 새로운 GPU 드라이버를 작성할 때는 수동 ww_mutex 재시도 루프 대신 drm_exec를 사용하세요. 코드가 간결해지고, 재시도 로직의 버그(잠금 해제 누락, contended 추적 오류 등)를 방지할 수 있습니다.

커널 버전별 진화

ww_mutex는 2013년 Linux 3.11에서 처음 도입된 이후 지속적으로 발전해왔습니다. DRM 서브시스템의 요구에 맞춰 API가 확장되고, 성능 최적화와 디버깅 지원이 강화되었습니다.

주요 버전별 변경 이력

커널 버전연도주요 변경
v3.112013ww_mutex 최초 도입. Wound-Wait 알고리즘 구현. DRM modeset lock 변환
v3.172014dma_resv(reservation_object) 도입. GEM buffer 예약에 ww_mutex 적용
v4.42016lockdep의 ww_mutex 검증 강화. ww_class 불일치 감지
v4.72016TTM의 ww_mutex 통합 개선. ttm_bo_reserve 리팩토링
v4.192018PREEMPT_RT 지원 개선. rt_mutex 기반 ww_mutex 경로
v5.32019Wait-Die 알고리즘 추가 (DEFINE_WD_CLASS). is_wait_die 플래그
v5.52020dma_resv: reservation_object → dma_resv 이름 변경
v5.82020dma_resv fence 관리 API 정비. usage 타입 체계 도입
v5.142021ww_mutex optimistic spinning 개선. osq_lock 통합
v5.192022dma_resv_list 리팩토링. num_fences 동적 관리
v6.02022dma_resv: 공유/배타 fence 구분 제거 → usage 기반 통합
v6.42023drm_exec 프레임워크 도입. 자동 재시도 래퍼
v6.62023drm_exec: IGNORE_DUPLICATES, prepare_array 추가
v6.82024ww_mutex wound 전파 경로 최적화. 불필요한 wake_up 감소
v6.122024drm_exec: 다양한 GPU 드라이버(amdgpu, xe, nouveau)에서 채택 확대
ww_mutex 커널 버전별 진화 타임라인 v3.11 2013 ww_mutex 최초 도입 Wound-Wait 알고리즘 DRM modeset 변환 v3.17 dma_resv 도입 GEM buffer 예약 v5.3 2019 Wait-Die 추가 DEFINE_WD_CLASS is_wait_die 플래그 v6.0 2022 usage 기반 통합 fence 관리 통합 v6.4 2023 drm_exec 도입 자동 재시도 래퍼 drm_gem_lock 대체 진화 방향 요약 Phase 1 (v3.x): 기반 구축 — Wound-Wait + DRM 통합 Phase 2 (v4.x): 안정화 — lockdep 강화, TTM 통합, PREEMPT_RT Phase 3 (v5.x): 확장 — Wait-Die 추가, dma_resv 리네임, fence 정비 Phase 4 (v6.x): 추상화 — drm_exec, usage 통합, wound 최적화 Future: heterogeneous GPU, multi-device fencing, Vulkan timeline semaphore
ww_mutex는 기반 구축→안정화→확장→추상화의 4단계로 진화해왔습니다

API 변경 가이드

/* v3.17 이전: reservation_object */
struct reservation_object *resv;
reservation_object_lock(resv, ctx);
reservation_object_unlock(resv);

/* v5.5+: dma_resv (이름 변경) */
struct dma_resv *resv;
dma_resv_lock(resv, ctx);
dma_resv_unlock(resv);

/* v6.0 이전: shared/exclusive fence 분리 */
dma_resv_add_shared_fence(resv, fence);
dma_resv_add_excl_fence(resv, fence);

/* v6.0+: usage 기반 통합 */
dma_resv_add_fence(resv, fence, DMA_RESV_USAGE_WRITE);
dma_resv_add_fence(resv, fence, DMA_RESV_USAGE_READ);

/* v6.4 이전: 수동 재시도 루프 */
ww_acquire_init(&ctx, &reservation_ww_class);
retry:
    ret = dma_resv_lock(resv, &ctx);
    if (ret == -EDEADLK) { ... goto retry; }

/* v6.4+: drm_exec 자동화 */
drm_exec_init(&exec, flags, 0);
drm_exec_until_all_locked(&exec) {
    drm_exec_prepare_obj(&exec, obj, num_fences);
    drm_exec_retry_on_contention(&exec);
}
drm_exec_fini(&exec);
참고: 새 GPU 드라이버를 작성할 때는 최신 API(drm_exec, dma_resv_add_fence usage 기반)를 사용하세요. 이전 API(reservation_object_*, add_shared/excl_fence)는 호환성을 위해 남아있지만, 새 코드에서는 deprecated입니다.

참고 자료

Wait/Wound Mutex의 설계, 데드락 회피 알고리즘, GPU 서브시스템 활용에 대한 참고 자료입니다.

커널 공식 문서

LWN.net 심층 기사

학술 자료 및 외부 참고

ww_mutex와 관련된 다른 동기화 주제를 더 깊이 이해하고 싶다면 다음 문서를 참고하세요.