본문으로 건너뛰기
Practical RTOS Internals · 26/53

Priority Inheritance 구현 — Inherit·Disinherit·Chain

· Hawk · 4분 읽기

#한 줄 요약

“PI는 take 시 boost하고 give 시 복원한다”uxBasePriority가 원래 값을 보존합니다.

이번 글에서는 FreeRTOS의 PI 구현을 코드 수준에서 살펴봅니다. inherit과 disinherit, 그리고 chained 상황까지 따라가 봅니다.

#TCB의 PI Field

typedef struct {
UBaseType_t uxPriority; // 현재 priority (boost 시 변경)
UBaseType_t uxBasePriority; // 원래 priority (PI 해제 시 복원)
UBaseType_t uxMutexesHeld; // 보유 mutex 수
/* ... */
} TCB_t;

uxBasePriority가 PI 구현의 핵심입니다. 이 값은 영구적인 우선순위이고, uxPriority임시로 변경되는 값입니다.

#Inherit — Boost

void vTaskPriorityInherit(TaskHandle_t pxMutexHolder) {
TCB_t *holder = (TCB_t *)pxMutexHolder;
/* Caller (current task)가 더 높은 priority면 */
if (holder->uxPriority < pxCurrentTCB->uxPriority) {
/* Ready list에서 제거 */
if (listIS_CONTAINED_WITHIN(
&(pxReadyTasksLists[holder->uxPriority]),
&(holder->xStateListItem))) {
uxListRemove(&(holder->xStateListItem));
/* Bitmap 정리 */
taskRESET_READY_PRIORITY(holder->uxPriority);
}
/* Priority boost */
holder->uxPriority = pxCurrentTCB->uxPriority;
/* New priority list로 재삽입 */
prvAddTaskToReadyList(holder);
}
}

호출 시점은 Mutex take 실패 직후, waiter 추가 시점입니다. Wait list에 들어가기 직전에 boost가 일어납니다.

#Disinherit — 복원

BaseType_t xTaskPriorityDisinherit(TaskHandle_t pxMutexHolder) {
TCB_t *holder = (TCB_t *)pxMutexHolder;
BaseType_t xReturn = pdFALSE;
/* uxMutexesHeld 감소 */
holder->uxMutexesHeld--;
/* 모든 mutex 해제됐는가? */
if (holder->uxMutexesHeld == 0) {
/* 원래 priority 복원 */
if (holder->uxPriority != holder->uxBasePriority) {
uxListRemove(&(holder->xStateListItem));
taskRESET_READY_PRIORITY(holder->uxPriority);
holder->uxPriority = holder->uxBasePriority;
prvAddTaskToReadyList(holder);
xReturn = pdTRUE;
}
}
return xReturn;
}

Mutex give 시점에 호출됩니다. 모든 mutex가 해제된 후에만 priority가 복원된다는 점이 중요합니다. 여러 mutex를 동시에 보유한 경우 단계적으로만 복원됩니다.

시간축 위에서 보면 boost의 의미가 분명해집니다. High가 mutex M을 기다리는 순간 Low의 priority가 일시적으로 High level까지 올라가, 그 사이 Mid가 끼어들지 못합니다.

Priority inheritance timeline

#Multi-Mutex 시나리오

  • T_low가 mutex A와 B를 동시 보유 중.
  • T_high1이 A를 대기하면 → T_low의 priority가 high1 level로 boost.
  • T_high2가 B를 대기하면 → 이미 boost된 상태이므로 변화 없음.
  • T_low가 A를 release하면 uxMutexesHeld = 1이 되어 priority는 B 때문에 유지됩니다.
  • T_low가 B를 release하면 uxMutexesHeld = 0이 되어 priority가 복원됩니다.

#Chained Inheritance

  • T_high가 mutex X를 대기, T_med가 보유 중.
  • T_med는 mutex Y를 대기, T_low가 보유 중.

전파 순서

  1. T_high가 X를 대기할 때 T_med의 priority가 high level로 boost.
  2. T_med가 Y를 대기할 때 T_low의 priority도 high level로 boost (boost된 T_med의 level).

xTaskCheckForChainedInheritance()recursive boost를 담당합니다.

#Disinherit After Timeout

void vTaskPriorityDisinheritAfterTimeout(TaskHandle_t pxMutexHolder,
UBaseType_t uxHighestPriorityWaitingTask) {
/* Waiter timeout 시 — 더 이상 그 priority로 boost할 이유 없음 */
/* 남은 waiters 중 가장 높은 priority로 *부분 복원* */
}

Timeout이 발생하면 해당 waiter의 priority 영향만 제거합니다.

#Time Slicing과 Boost 상호작용

Boost된 task는 원래 같은 priority에 있던 다른 task와 같은 레벨이 되어 round-robin 대상에 포함됩니다. 이는 의도된 동작입니다.

#Implementation Overhead

  • Inherit — ~50 cycle (list remove + insert + bitmap update)
  • Disinherit — ~50 cycle (역방향)
  • Chain — depth × inherit cost

Cortex-M4 @ 168 MHz 기준 0.3 µs/op입니다. 무시 가능한 수준입니다.

#Zephyr — k_mutex의 PI

struct k_mutex {
_wait_q_t wait_q;
struct k_thread *owner;
uint32_t lock_count;
int owner_orig_prio; // ← Zephyr의 base priority
};

구조는 비슷합니다. PI 활성/비활성을 옵션으로 고를 수 있습니다.

#Linux PREEMPT_RT rtmutex

struct rt_mutex_base {
raw_spinlock_t wait_lock;
struct rb_root_cached waiters; // RB tree
struct task_struct *owner;
};

Waiters를 RB tree로 정렬해 O(log N)으로 priority 조작이 가능합니다. Linux의 다른 sleeping lock(예: futex)보다 한층 정교합니다.

#자주 하는 실수

⚠️ Inherit 후 manual priority 변경

vTaskPrioritySet()로 priority를 바꾸면 uxBasePriority도 함께 갱신해야 합니다. FreeRTOS API가 이 처리를 대신해 줍니다.

⚠️ Multiple mutex deep nesting

3개 이상의 mutex를 동시에 보유하면 chain 깊이가 증가합니다. Lock order와 단순한 설계로 회피하는 것이 정답입니다.

⚠️ ISR에서 PI 가정

ISR은 task가 아니므로 PI 자체가 무의미합니다. ISR과 task 사이 통신은 task notification을 씁니다.

#정리

  • PI는 take 시 boost하고 give 시 복원합니다.
  • uxBasePriority가 원래 값을 보존합니다.
  • Multi-mutex 상황에서는 uxMutexesHeld == 0일 때만 복원됩니다.
  • Chained inheritance도 recursive boost로 처리합니다.
  • Linux PREEMPT_RT의 rtmutex는 RB tree로 더 정교하게 구현돼 있습니다.

다음 편은 Priority Ceiling Protocol 구현입니다.

#관련 항목

Practical RTOS Internals · 27 of 53

  1. 1Practical RTOS Internals — 실시간 커널 내부 분석 시리즈 소개
  2. 2RTOS가 필요한 이유 — 일반 OS와의 결정적 차이
  3. 3Task와 Thread 개념 — TCB·상태 머신·생명 주기 분석
  4. 4실시간 스케줄링 알고리즘 비교 — RR·Priority·EDF·RMS
  5. 5Preemption과 Cooperation — 강제 전환 vs 자발 양보
  6. 6인터럽트와 RTOS — ISR Context·Deferred Processing·FromISR API
  7. 7동기화 기초 분석 — Critical Section·Mutual Exclusion·Race Condition
  8. 8Semaphore 개념 분해 — Counting·Binary·P/V 연산
  9. 9Mutex 개념 분해 — Ownership·Recursive·Priority Inheritance
  10. 10큐와 메시지 패싱 — Producer-Consumer·Ring Buffer·전달 의미
  11. 11실시간성 분석 — Latency·Jitter·Deadline·WCET·RMA
  12. 12Ready List 자료구조 분석 — Linked List·Bitmap·O(1) Scheduler
  13. 13Blocked List 자료구조 — Timeout 정렬·Delta List·Two-List Scheme
  14. 14Scheduler 알고리즘 구현 추적 — Next-Task Selection 로직
  15. 15Context Switch 원리 분석 — 레지스터 저장·복원·Stack Frame
  16. 16ARM Cortex-M Context Switch — PendSV·MSP/PSP 어셈블리 추적
  17. 17ARM Cortex-A Context Switch — Mode 전환·SVC·Banked Registers
  18. 18RISC-V Context Switch 분석 — ECALL·mret·CSR
  19. 19RTOS Tick과 타이머 — SysTick·Generic Timer·configTICK_RATE_HZ
  20. 20Tickless 모드 구현 — Idle Tick Suppression·Sleep·Wake 보정
  21. 21Scheduler Latency 측정 기법 — GPIO Toggle·DWT·ftrace·cyclictest
  22. 22RTOS Tracing과 Observability — Tracealyzer·SystemView·ITM/ETM
  23. 23Critical Section 구현 비교 — IRQ Disable·BASEPRI·Spinlock
  24. 24Semaphore 내부 구현 추적 — Counter·Wait List·ISR-Safe Variant
  25. 25Mutex 내부 구현 추적 — Owner·Recursion Count·ISR 금지
  26. 26Priority Inversion 문제 — Mars Pathfinder 사례·Bounded vs Unbounded
  27. 27Priority Inheritance 구현 — Inherit·Disinherit·Chain
  28. 28Priority Ceiling Protocol — Immediate vs Original 비교
  29. 29Queue 내부 구현 추적 — Ring Buffer·2 Wait Lists·Atomic Send/Receive
  30. 30Event Group 분석 — Bit Flag·AND/OR Wait·Sync Barrier
  31. 31ISR-Safe API 설계 — FromISR 패턴·Higher Priority Wake·Deferred Work
  32. 32Deadlock 분석 — 4 조건·Wait-for Graph·Lock Ordering·Timeout
  33. 33Stream Buffer와 Message Buffer — FreeRTOS 10의 Lock-Free SPSC
  34. 34실시간 메모리 요구사항 — Determinism·Fragmentation·WCET
  35. 35FreeRTOS Heap_1~5 분석 — 5종 Allocator의 구조와 트레이드오프
  36. 36TLSF Allocator 분석 — Two-Level Segregated Fit O(1)
  37. 37Static Allocation — 컴파일 타임으로 동적 위험 제거하기
  38. 38Memory Pool — Fixed-Size Block Allocator의 단순함과 강력함
  39. 39Stack Overflow 탐지 — Canary·MPU·Watermark 3중 방어
  40. 40SMP RTOS 설계 — Ready List·Affinity·IPI·Load Balancing
  41. 41SMP Spinlock 구현 — LDREX/STREX·Ticket Lock·MCS·WFE/SEV
  42. 42Software Timer 분석 — Daemon Task·자료구조·ISR-Safe API
  43. 43RTOS System Call — SVC·ECALL·User/Kernel 분리·FreeRTOS-MPU
  44. 44TrustZone과 TF-M — Secure/Non-Secure·NSC Veneer·PSA
  45. 45AMP와 OpenAMP — Heterogeneous SoC·RPMsg·remoteproc
  46. 46C++ in RTOS — RAII·std::thread·ETL·Coroutine
  47. 47FreeRTOS 소스 분석 — tasks.c·queue.c·port.c 추적
  48. 48Zephyr 커널 분석 — k_thread·k_sem·Driver Model
  49. 49RT-Thread 분석 — Object 모델·Components·Smart·Studio
  50. 50RTOS 포팅 가이드 — 새 아키텍처에 옮기는 절차
  51. 51RTOS 선택 가이드 — Footprint·License·Certification·Ecosystem
  52. 52Apache NuttX 분석 — POSIX·PX4·NASA Ingenuity
  53. 53PREEMPT_RT Linux — Mainline 6.12·Xenomai 4·EVL