우선순위 역행: 실시간 시스템의 뮤텍스 대기 문제
Priority Inversion의 발생 조건과 실시간 시스템 영향, Priority Inheritance·Priority Ceiling 기반 대응 방식을 정리합니다.
2026-08-14 · 최초 발행 2026-01-03
높은 우선순위 작업이 멈추는 조건
Priority Inversion(우선순위 역행 또는 우선순위 역전)은 낮은 우선순위 작업이 뮤텍스, 세마포어, 임계 구역 같은 공유 자원을 보유한 상태에서 높은 우선순위 작업의 실행을 막는 문제다.
선점형 우선순위 스케줄링에서는 원래 높은 우선순위 작업이 낮은 우선순위 작업을 앞서야 한다. 그러나 고우선순위 작업이 잠긴 뮤텍스를 기다리는 동안 중우선순위 작업이 CPU를 가져가면, 자원을 쥔 저우선순위 작업은 실행 기회를 얻지 못한다. 그 결과 우선순위 정책이 의도한 순서가 뒤집힌다.
뮤텍스 하나로 생기는 대기 사슬
다음 상황을 가정한다.
- Task H: 우선순위 10
- Task M: 우선순위 5
- Task L: 우선순위 1
- 공유 자원: 뮤텍스 M
처음 t0에는 Task L이 실행되어 뮤텍스 M을 획득하고 임계 구역에 들어간다. t1에 Task H가 Ready 상태가 되면 Task L을 선점하고 실행을 시작한다.
하지만 t2에 Task H가 뮤텍스 M을 요청하면, 이미 Task L이 이를 보유하고 있으므로 Task H는 Sleep 상태로 대기한다. CPU는 다시 Task L에 할당될 수 있다.
문제는 t3에 Task M이 Ready 상태가 되는 순간이다. 우선순위 5인 Task M은 Task L을 선점한다. 이때 Task L은 뮤텍스를 쥐고 있지만 CPU를 받지 못하고, Task H는 그 뮤텍스를 기다린 채 잠든다. 우선순위 10인 Task H가 우선순위 5인 Task M보다 늦게 실행되는 상태가 만들어진다.
Task M이 끝난 뒤 t4에 Task L이 다시 실행되어 임계 구역을 벗어나고 뮤텍스 M을 해제해야 한다. 그제야 t5에 Task H가 뮤텍스 M을 획득해 실행을 이어갈 수 있다.
이 상태에서는 Task H의 응답 시간을 예측하기 어렵다. 최악의 경우 중우선순위 작업의 실행 시간만큼 지연되며, Task M이 장시간 실행하면 Task H가 무한정 기다릴 가능성도 있다.
실시간 시스템에서는 이 지연이 곧 데드라인 미스가 될 수 있다.
Task H 데드라인: 10ms
Priority Inversion 지연: 15ms
→ 데드라인 미스!
Hard Real-Time 환경에서는 데드라인 미스를 허용할 수 없으므로 시스템 실패로 이어질 수 있다. 항공기 제어와 의료 기기가 여기에 해당한다. Soft Real-Time 환경에서는 일부 데드라인 미스가 허용되지만, 멀티미디어 스트리밍처럼 성능 저하로 드러날 수 있다.
Mars Pathfinder에서 드러난 문제
1997년 Mars Pathfinder에서는 Priority Inversion이 원인이 된 시스템 리셋이 발생했다. 고우선순위 Bus Management Task는 Information Bus를 기다렸고, 저우선순위 Meteorological Task가 해당 뮤텍스를 보유하고 있었다. 그 사이 중우선순위 작업들이 CPU를 선점하면서 고우선순위 작업은 데드라인을 놓쳤고, 시스템 Watchdog이 리셋을 발생시켰다.
VxWorks RTOS에서 Priority Inheritance를 활성화하고 패치를 업로드해 문제를 해결했다.
자원을 가진 작업을 먼저 끝내게 하는 방법
Priority Inheritance는 고우선순위 작업이 자원을 기다릴 때, 그 자원을 점유한 저우선순위 작업의 우선순위를 일시적으로 올리는 방식이다.
Task L이 뮤텍스 M을 가진 상태에서 Task H가 이를 요청하면, Task L의 우선순위는 Task H와 같은 10으로 상승한다. 그러면 우선순위 5인 Task M은 Task L을 선점할 수 없다. Task L이 뮤텍스를 해제하면 우선순위는 원래의 1로 돌아가고, Task H가 뮤텍스를 얻는다.
상속은 대기 관계를 따라 이어질 수도 있다. 우선순위 10의 Task A가 우선순위 5의 Task B를 기다리고, Task B가 다시 우선순위 1의 Task C를 기다린다면 Task C의 우선순위도 10으로 상승한다.
POSIX Threads에서는 뮤텍스 속성으로 Priority Inheritance를 지정할 수 있다.
pthread_mutex_t mutex;
pthread_mutexattr_t attr;
// Priority Inheritance 설정
pthread_mutexattr_init(&attr);
pthread_mutexattr_setprotocol(&attr, PTHREAD_PRIO_INHERIT);
pthread_mutex_init(&mutex, &attr);
// 사용
pthread_mutex_lock(&mutex);
// 임계 구역
pthread_mutex_unlock(&mutex);
VxWorks에서는 SEM_INVERSION_SAFE 옵션을 사용한다.
SEM_ID semMutex;
// Priority Inheritance 뮤텍스 생성
semMutex = semMCreate(SEM_Q_PRIORITY | SEM_INVERSION_SAFE);
// 사용
semTake(semMutex, WAIT_FOREVER);
// 임계 구역
semGive(semMutex);
이 방식은 코드 변경을 최소화하면서 Priority Inversion을 막을 수 있다. 반면 우선순위 변경 비용이 발생하고, 여러 뮤텍스가 얽히면 데드락 가능성과 복잡성이 커진다.
뮤텍스에 우선순위 상한을 두는 방식
Priority Ceiling은 뮤텍스마다 그것을 사용하는 작업 가운데 가장 높은 우선순위를 Ceiling으로 설정하는 방법이다. 작업이 뮤텍스를 획득하는 즉시 Ceiling 우선순위로 올라가므로, Priority Inversion이 발생하기 전에 막는다.
뮤텍스 M의 Ceiling이 10이고 Task L이 이를 획득했다면, Task L의 우선순위는 10이 된다. 따라서 우선순위 5인 Task M은 Task L을 선점할 수 없다. 뮤텍스를 해제하면 Task L은 원래 우선순위 1로 복원된다.
POSIX Threads의 Priority Ceiling 설정은 다음과 같다.
pthread_mutex_t mutex;
pthread_mutexattr_t attr;
// Priority Ceiling 설정
pthread_mutexattr_init(&attr);
pthread_mutexattr_setprotocol(&attr, PTHREAD_PRIO_PROTECT);
pthread_mutexattr_setprioceiling(&attr, 10); // Ceiling = 10
pthread_mutex_init(&mutex, &attr);
// 사용
pthread_mutex_lock(&mutex);
// 임계 구역
pthread_mutex_unlock(&mutex);
FreeRTOS에서는 다음처럼 Mutex를 생성해 사용한다.
// Mutex 생성 시 Priority Ceiling 자동 적용
SemaphoreHandle_t xMutex = xSemaphoreCreateMutex();
// 사용
xSemaphoreTake(xMutex, portMAX_DELAY);
// 임계 구역
xSemaphoreGive(xMutex);
Priority Ceiling은 예측 가능한 성능과 데드락 방지 효과를 제공한다. 다만 Ceiling을 정하려면 사전 분석이 필요하고, 필요하지 않은 우선순위 상승에 따른 오버헤드가 생길 수 있다.
임계 구역에서 선점을 비활성화하는 방법도 있다.
disable_preemption();
// 임계 구역
critical_section();
enable_preemption();
이 접근은 임계 구역의 선점을 막지만 모든 작업의 응답 시간을 늘릴 수 있어 실시간 시스템에는 적합하지 않다.
RTOS와 설계 단계의 판단
VxWorks는 SEM_INVERSION_SAFE 옵션을 통해 Priority Inheritance를 지원한다. FreeRTOS에서는 Mutex에 Priority Inheritance가 자동 적용되며 Binary Semaphore는 지원하지 않는다. QNX는 Priority Inheritance와 Priority Ceiling, Adaptive Partitioning을 지원한다. Linux RT Patch에서는 CONFIG_RT_MUTEXES 옵션으로 Priority Inheritance를 지원한다.
동기화 설계에서는 공유 자원을 줄이고, 임계 구역의 실행 시간을 짧게 유지해야 한다. 복잡한 연산은 임계 구역 밖으로 옮기고 Lock-free 알고리즘을 고려할 수 있다. 우선순위 레벨을 과도하게 늘리지 않으며 Ceiling 우선순위는 사전에 분석한다.
메시지 큐, 파이프, 공유 메모리와 Lock-free 조합도 자원 공유와 락 경합을 줄이는 대안이 된다.