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