Wait/Wound Mutex (데드락 회피 뮤텍스(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 벤치마크 시나리오:

시나리오스레드잠금 수경합 수준
122최소 경합
244중간 경합
3816높은 경합
41632극심한 경합

측정 항목:

시나리오알고리즘평균 획득 시간-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는 컴파일러 최적화(Compiler Optimization)(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입니다.

ww_mutex의 코어 알고리즘은 변화가 거의 없으나, DRM 서브시스템의 주력 사용처가 계속 확장되고 있습니다. 특히 Intel Xe 드라이버 본선 병합(6.8)과 이후 성숙, AMDGPU의 GPU reset 경로 재설계, DRM scheduler v2 논의 등이 6.12~6.16 구간에 반영되었습니다. drm_exec API는 기존 ttm_eu_* 경로를 대체하며 ww_mutex 사용을 간접화합니다.

커널릴리스변경 사항실무 시사점
6.12 (LTS)2024-11PREEMPT_RT 메인라인 병합 — ww_mutex는 rt_mutex 기반이므로 RT 환경에서 PI 상속 자동 적용RT 커널에서 GPU 드라이버 사용 시 RT 태스크(Task)의 GPU 제출 latency 개선
6.132025-01drm_exec 기반 AMDGPU/XE 경로 안정화 — 각 드라이버의 ww_acquire_ctx 초기화 패턴 정리-EDEADLK 재시도 루프 구현을 DRM_EXEC_INTERRUPTIBLE_WAIT에 위임 권장
6.142025-03DRM scheduler 개선과 연계된 reservation object 잠금 경로 정돈GPU 제출 fence/dependency 관리가 더 예측 가능 — 신규 드라이버는 dma_fence_chain과 조합
6.152025-05per-VMA lock refcount 전환으로 GPU 드라이버의 VMA 변경 관찰 경로(PPGTT/userptr) 부담 감소userptr GEM BO의 page fault 경로 지연(Latency) 감소
6.162025-07ww_mutex 경로의 DEFINE_CLASS_IS_COND_GUARD 호환성 검토 — guard 스타일 접근은 명시적으로 지원되지 않음(ww_acquire_ctx 필요)ww_mutex는 guard() 매크로 적용 불가함을 리뷰어에게 공지

drm_exec로의 전환

drm_exec는 ww_mutex를 직접 쓰는 대신, 여러 reservation object를 한 번에 획득하는 고수준 헬퍼입니다. 6.10 이후 Intel Xe, AMDGPU, Nouveau 등에서 기본 API로 채택되었습니다. 전통적인 ww_mutex 재시도 루프를 수동으로 구현할 필요가 줄어듭니다.

/* drm_exec: ww_mutex 재시도 루프 자동화 */
struct drm_exec exec;
drm_exec_init(&exec, DRM_EXEC_INTERRUPTIBLE_WAIT | DRM_EXEC_IGNORE_DUPLICATES, 0);
drm_exec_until_all_locked(&exec) {
    drm_exec_lock_obj(&exec, obj1);
    drm_exec_retry_on_contention(&exec);
    drm_exec_lock_obj(&exec, obj2);
    drm_exec_retry_on_contention(&exec);
}
/* ... critical section ... */
drm_exec_fini(&exec);
핵심 요약: (1) ww_mutex API 자체는 변화 없으나, GPU 드라이버 작성 시 drm_exec를 기본으로 사용하세요. (2) ww_mutex에는 guard() 매크로를 적용할 수 없습니다(ww_acquire_ctx 생명주기가 필요). (3) RT 병합 후에도 DRM 경로는 그대로 동작 — 다만 RT 태스크가 GPU 제출에 관여하는 경우 PI 상속으로 우선순위 역전(Priority Inversion)이 자동 해결됩니다.

참고 자료

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

커널 공식 문서

LWN.net 심층 기사

학술 자료 및 외부 참고

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