에듀테크 프로젝트 대시보드
우선순위 역전 (Priority Inversion)
낮은 우선순위 태스크가 잡고 있는 자물쇠(뮤텍스) 하나 때문에, 가장 급한 태스크가 하염없이 기다리게 되는 스케줄링의 함정 — 그리고 우선순위 상속으로 푸는 법.
선점형 우선순위 스케줄러에서, 고순위 H가 원하는 공유 자원을 저순위 L이 이미 잠갔다. L이 자원을 풀어야 H가 진행되는데, 그 사이 중순위 M이 끼어들어 L을 계속 밀어낸다. 결과적으로 H는 자기보다 낮은 M 때문에 무한정 밀린다.
같은 시나리오를 상속 없음과 우선순위 상속 두 모드로 비교해 보자. ▶︎를 눌러 시간이 흐르는 것을 지켜보고, 각 태스크가 언제 자원을 쥐는지(초록 테두리), 언제 막히는지(빗금) 확인한다.
| 조건 | 설명 |
|---|---|
| 공유 자원 + 상호 배제 | H와 L이 같은 뮤텍스를 두고 경쟁한다. 한 번에 하나만 들어갈 수 있다. |
| 선점형 우선순위 스케줄링 | 항상 준비 상태인 태스크 중 우선순위가 가장 높은 것이 CPU를 가져간다. |
| 중간 우선순위 태스크의 존재 | H도 L도 아닌 M이 끼어들어 L을 밀어내면, L이 자원을 못 푼다 → H도 못 푼다. |
M이 없다면? L은 계속 돌아 임계 구역을 곧 빠져나오고 H는 짧게만 기다린다. 이 짧고 유한한 대기는 우선순위 역전이 아니라 정상적인 bounded blocking이다. 문제는 M 때문에 그 대기가 경계 없이 길어질 때다.
1997년 NASA Mars Pathfinder 탐사선이 화성 표면에서 반복적으로 리셋됐다. 원인은 VxWorks 실시간 OS에서의 전형적인 우선순위 역전이었다:
bc_dist(정보 버스 관리) 태스크가 뮤텍스로 보호된 공유 버스에 접근하려 함ASI/MET(기상 데이터) 태스크가 그 뮤텍스를 쥔 채 중순위 통신 태스크에 선점당함
JPL 엔지니어들은 지구에서 원격으로 pthread_mutexattr의
우선순위 상속 플래그를 켜서(1로) 문제를 해결했다.
디버깅이 가능했던 건 같은 코드가 지상 테스트 장비에서도 돌고 있었기 때문이다.
| 기법 | 아이디어 | 비고 |
|---|---|---|
| 우선순위 상속 (Priority Inheritance) |
H가 L이 쥔 뮤텍스에서 블로킹되면, L이 임시로 H의 우선순위를 물려받는다. L이 뮤텍스를 풀면 원래 우선순위로 복귀. | 가장 널리 쓰임(PTHREAD_PRIO_INHERIT).
연쇄·중첩 락에서 상속 전이 처리가 까다롭다. |
| 우선순위 실링 (Priority Ceiling) |
각 뮤텍스에 "그 락을 쓸 수 있는 태스크 중 최고 우선순위"를 천장값으로 미리 지정. 락을 잡는 순간 태스크가 그 천장으로 올라간다. | 데드락·연쇄 블로킹을 원천 차단. 천장을 정적으로 알아야 함(PTHREAD_PRIO_PROTECT). |
| 인터럽트/선점 비활성화 | 임계 구역 동안 아예 스케줄러를 멈춘다. | 구현은 간단하지만 짧은 구역에만. 길면 실시간성 전체를 해친다. |
| 락 없는 설계 | 공유 자원 자체를 없애거나(태스크별 사본), 원자 연산·메시지 큐로 대체. | 가능하면 최선. 항상 가능하진 않음. |
// 우선순위 상속을 켠 뮤텍스 초기화 pthread_mutexattr_t attr; pthread_mutexattr_init(&attr); // 기본값 PTHREAD_PRIO_NONE → 역전 방어 없음 pthread_mutexattr_setprotocol(&attr, PTHREAD_PRIO_INHERIT); pthread_mutex_t bus_lock; pthread_mutex_init(&bus_lock, &attr); // 이제 저순위 태스크가 bus_lock 을 쥔 채 // 고순위 태스크에게 블로킹을 유발하면, // 커널이 저순위 태스크의 우선순위를 자동으로 끌어올린다.