Mutex 내부 구현 추적 — Owner·Recursion Count·ISR 금지
#한 줄 요약
“Mutex는 Semaphore에
pxMutexHolder만 추가한 형태입니다.” owner를 추적할 수 있게 되면 priority inheritance까지 자연스럽게 구현됩니다.
#FreeRTOS Mutex 구조
typedef Queue_t Mutex_t; // 같은 자료구조 재활용
// 추가 활용union { /* Queue 일반 사용 */ struct { TaskHandle_t xMutexHolder; // ← Mutex 전용 UBaseType_t uxRecursiveCallCount; } xSemaphore;
struct { /* Queue 일반 fields */ } xQueue;} u;Queue의 공용 영역을 mutex 모드에서는 owner와 recursion count로 재해석합니다.
#Take 흐름
BaseType_t xQueueTakeMutexRecursive(QueueHandle_t mutex, TickType_t xTicksToWait) { BaseType_t result;
portENTER_CRITICAL();
if (mutex->pxMutexHolder == pxCurrentTCB) { /* Same task — recursive take */ mutex->u.xSemaphore.uxRecursiveCallCount++; portEXIT_CRITICAL(); return pdPASS; } portEXIT_CRITICAL();
/* 다른 task 또는 처음 take */ result = xQueueGenericReceive(mutex, NULL, xTicksToWait, pdFALSE); if (result == pdPASS) { mutex->pxMutexHolder = pxCurrentTCB; mutex->u.xSemaphore.uxRecursiveCallCount = 1; } return result;}#Give 흐름 — Owner 검증
BaseType_t xQueueGenericSend(QueueHandle_t mutex, ...) { portENTER_CRITICAL();
/* Owner 확인 */ if (mutex->pxMutexHolder != NULL && mutex->pxMutexHolder != pxCurrentTCB) { portEXIT_CRITICAL(); return pdFAIL; // Non-owner는 give 못 함 }
/* Recursive count 감소 */ if (--mutex->u.xSemaphore.uxRecursiveCallCount > 0) { portEXIT_CRITICAL(); return pdPASS; }
/* Count = 0 — 실제 release */ mutex->pxMutexHolder = NULL; /* Priority Inheritance 복원 */ vTaskPriorityDisinheritAfterTimeout(...);
/* Wake waiter */ if (!list_empty(&xTasksWaitingToReceive)) { wake_highest_priority_waiter(); }
portEXIT_CRITICAL(); return pdPASS;}#Priority Inheritance — Owner의 priority 동적 boost
void vTaskPriorityInherit(TaskHandle_t pxMutexHolder) { TCB_t *holder = (TCB_t *)pxMutexHolder;
if (holder->uxPriority < pxCurrentTCB->uxPriority) { /* Boost — base priority 저장 후 변경 */ if (holder->uxBasePriority == 0) holder->uxBasePriority = holder->uxPriority; holder->uxPriority = pxCurrentTCB->uxPriority; /* Ready list에서 위치 조정 */ rebalance_ready_list(holder); }}대기 task가 owner의 priority를 일시적으로 상속하도록 만듭니다. take 시점에 이 함수가 호출됩니다.
#Priority Disinheritance — 복원
BaseType_t vTaskPriorityDisinherit(TaskHandle_t pxMutexHolder) { TCB_t *holder = (TCB_t *)pxMutexHolder;
if (holder->uxBasePriority != holder->uxPriority) { /* Restore original */ holder->uxPriority = holder->uxBasePriority; holder->uxBasePriority = 0; rebalance_ready_list(holder); return pdTRUE; } return pdFALSE;}Mutex를 give할 때 원래 priority로 복원합니다. mutex가 여러 개 얽힌 chain inheritance도 정확하게 처리합니다.
#Recursive Mutex
SemaphoreHandle_t mtx = xSemaphoreCreateRecursiveMutex();
xSemaphoreTakeRecursive(mtx, ...); // count = 1xSemaphoreTakeRecursive(mtx, ...); // count = 2 (same task)xSemaphoreGiveRecursive(mtx); // count = 1xSemaphoreGiveRecursive(mtx); // count = 0 → releaseuxRecursiveCallCount로 재진입 횟수를 추적합니다. owner check와 count를 함께 검증하므로 안전합니다.
#ISR 금지 이유
xSemaphoreTakeFromISR(mutex, ...); // ✗ 컴파일 에러ISR은 task가 아니므로 owner가 될 수 없습니다. pxMutexHolder가 의미를 잃어버리기 때문에 ISR에서 사용하면 logic이 깨지고 priority inheritance가 동작하지 않습니다.
#Deadlock — Lock Ordering
// Task AxSemaphoreTake(mtx_X, ...);xSemaphoreTake(mtx_Y, ...);
// Task BxSemaphoreTake(mtx_Y, ...);xSemaphoreTake(mtx_X, ...);circular wait가 발생해 deadlock으로 이어집니다. 해결 방법은 글로벌 lock order를 강제하는 것입니다. 예를 들어 항상 X를 먼저 take하도록 규칙을 정합니다.
#Timeout 활용
if (xSemaphoreTake(mtx, pdMS_TO_TICKS(100)) != pdTRUE) { log_warning("mutex timeout — possible deadlock"); return ERROR;}portMAX_DELAY 대신 유한한 timeout을 두면 deadlock을 감지하고 복구할 수 있습니다.
#Mutex Hold Time — 짧게
Mutex를 보유한 task의 priority는 boost된 상태에 머무릅니다. Hold time이 길어질수록 다른 task에 미치는 영향이 커지고 priority inheritance 효과도 오래 지속됩니다.
목표는 수 µs 이하로 유지하는 것입니다.
#Static Allocation
StaticSemaphore_t mtx_buf;SemaphoreHandle_t mtx = xSemaphoreCreateMutexStatic(&mtx_buf);자동차나 항공처럼 safety-critical 영역에서는 정적 할당이 표준입니다.
#Zephyr — k_mutex
struct k_mutex { _wait_q_t wait_q; struct k_thread *owner; uint32_t lock_count; int owner_orig_prio;};기본 구조는 비슷하고 priority inheritance가 내장되어 있습니다.
#자주 하는 실수
⚠️ Non-owner가 give를 호출합니다
pdFAIL이 반환되는데, 이를 무시하면 logic이 그대로 깨집니다. 항상 return 값을 확인합니다.
⚠️ Recursive mutex를 잘못 사용합니다
xSemaphoreTake와 xSemaphoreTakeRecursive를 섞어 쓰면 미정의 동작으로 이어집니다. 시작 시 한 종류로 정해 두는 편이 좋습니다.
⚠️ ISR에서 mutex를 사용합니다
ISR과 task 사이의 신호 전달에는 semaphore나 task notification을 사용합니다. Mutex는 ISR에서 사용하면 안 됩니다.
⚠️ Mutex를 잡은 채 long blocking을 합니다
다른 mutex take나 queue receive를 infinite timeout으로 호출하면 cascading wait가 발생하고 deadlock으로 이어지기 쉽습니다.
#정리
- Mutex는 Queue에
pxMutexHolder와uxRecursiveCallCount를 추가한 구조입니다. - Owner를 검증하여 non-owner가 give를 호출하는 경우를 차단합니다.
- Priority inheritance는 take 시 priority를 boost하고 give 시 복원합니다.
- Recursive variant는 count로 재진입을 추적합니다.
- ISR에서는 사용할 수 없으며 semaphore나 task notification으로 대체합니다.
다음 편에서는 Priority Inversion 문제를 Mars Pathfinder 사례와 함께 자세히 살펴봅니다.
#관련 항목
Practical RTOS Internals · 25 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
관련 글
FreeRTOS 소스 분석 — tasks.c·queue.c·port.c 추적
FreeRTOS-Kernel 저장소의 핵심 파일 셋을 따라가며 xTaskCreate부터 PendSV까지의 흐름을 정리합니다. TCB·ready list·port 계층 사이의 경계가 어떻게 그어져 있는지 source 수준에서 살펴봅니다.
Queue 내부 구현 추적 — Ring Buffer·2 Wait Lists·Atomic Send/Receive
FreeRTOS Queue 코드 — pcWriteTo·pcReadFrom·uxMessagesWaiting + xTasksWaitingToSend/Receive.
Semaphore 내부 구현 추적 — Counter·Wait List·ISR-Safe Variant
FreeRTOS semaphore = Queue wrapper. Counter + priority-sorted wait list.