Queue 내부 구현 추적 — Ring Buffer·2 Wait Lists·Atomic Send/Receive
#한 줄 요약
“Queue는 Ring buffer와 2개의 wait list로 구성된다” — Send와 Receive 양쪽 모두 blocking이 가능합니다.
이번 글에서는 FreeRTOS Queue의 내부 구조를 따라가 봅니다. Ring buffer, 두 wait list, ISR-safe 처리까지 한 번에 다룹니다.
#Queue Structure
typedef struct QueueDefinition { int8_t *pcHead; // buffer 시작 int8_t *pcWriteTo; // 다음 write 위치 union { int8_t *pcReadFrom; // Queue 일반 UBaseType_t uxRecursiveCallCount; // Mutex 시 } u;
List_t xTasksWaitingToSend; // queue full 대기 List_t xTasksWaitingToReceive; // queue empty 대기
volatile UBaseType_t uxMessagesWaiting; UBaseType_t uxLength; // max items UBaseType_t uxItemSize; // bytes/item
volatile int8_t cRxLock; // ISR 안전성용 volatile int8_t cTxLock;
uint8_t ucStaticallyAllocated; uint8_t ucQueueType;} Queue_t;#Send 흐름
BaseType_t xQueueGenericSend(QueueHandle_t q, const void *item, TickType_t xTicksToWait, BaseType_t xCopyPosition) { portENTER_CRITICAL();
if (q->uxMessagesWaiting < q->uxLength || xCopyPosition == queueOVERWRITE) { /* Space available — copy item */ prvCopyDataToQueue(q, item, xCopyPosition);
/* Wake highest-priority receiver if any */ if (listLIST_IS_EMPTY(&q->xTasksWaitingToReceive) == pdFALSE) { if (xTaskRemoveFromEventList(&q->xTasksWaitingToReceive) != pdFALSE) { /* Higher priority — yield */ queueYIELD_IF_USING_PREEMPTION(); } } portEXIT_CRITICAL(); return pdPASS; }
/* Queue full */ if (xTicksToWait == 0) { portEXIT_CRITICAL(); return errQUEUE_FULL; }
/* Block on send */ vTaskPlaceOnEventList(&q->xTasksWaitingToSend, xTicksToWait); portEXIT_CRITICAL(); portYIELD_WITHIN_API(); /* ... 깨어난 후 재시도 ... */}#Ring Buffer Wrap
static void prvCopyDataToQueue(Queue_t *q, const void *src, BaseType_t pos) { if (q->uxItemSize == 0) { /* Semaphore mode — counter만 사용 */ if (q->ucQueueType == queueQUEUE_TYPE_MUTEX) { /* Mutex give */ xTaskPriorityDisinherit(q->pxMutexHolder); q->pxMutexHolder = NULL; } } else if (pos == queueSEND_TO_BACK) { memcpy(q->pcWriteTo, src, q->uxItemSize); q->pcWriteTo += q->uxItemSize; if (q->pcWriteTo >= q->pcTail) { q->pcWriteTo = q->pcHead; // wrap } } else if (pos == queueSEND_TO_FRONT) { q->u.pcReadFrom -= q->uxItemSize; if (q->u.pcReadFrom < q->pcHead) { q->u.pcReadFrom = q->pcTail - q->uxItemSize; // wrap } memcpy(q->u.pcReadFrom, src, q->uxItemSize); } else if (pos == queueOVERWRITE) { /* Mailbox mode */ memcpy(q->pcWriteTo, src, q->uxItemSize); /* uxMessagesWaiting 증가시키지 않음 */ return; } q->uxMessagesWaiting++;}#Receive — 대칭 구조
BaseType_t xQueueReceive(QueueHandle_t q, void *buffer, TickType_t xTicksToWait) { portENTER_CRITICAL();
if (q->uxMessagesWaiting > 0) { prvCopyDataFromQueue(q, buffer); q->uxMessagesWaiting--;
/* Sender waiter wake */ if (!list_empty(&q->xTasksWaitingToSend)) { xTaskRemoveFromEventList(&q->xTasksWaitingToSend); } portEXIT_CRITICAL(); return pdPASS; }
/* Empty */ if (xTicksToWait == 0) { portEXIT_CRITICAL(); return errQUEUE_EMPTY; }
vTaskPlaceOnEventList(&q->xTasksWaitingToReceive, xTicksToWait); portEXIT_CRITICAL(); portYIELD_WITHIN_API(); /* ... */}#cRxLock·cTxLock — ISR Safety
volatile int8_t cRxLock; // -1 = unlocked, 0+ = ISR queued sends countvolatile int8_t cTxLock;ISR이 FromISR send를 호출하면 task를 깨워야 합니다. 그런데 ISR 중에 task list를 직접 수정하는 것은 위험합니다. 그래서 lock 카운터를 사용합니다.
xQueueSendFromISR(...) { /* ... copy item ... */ if (q->cTxLock == queueUNLOCKED) { /* Scheduler running — wake 직접 수행 */ wake_receiver(); } else { /* Locked — count up */ q->cTxLock++; }}Unlock 시점에 누적된 wake가 한꺼번에 처리됩니다.
#Generic Send — xCopyPosition
queueSEND_TO_BACK // 일반 FIFOqueueSEND_TO_FRONT // LIFO 동작queueOVERWRITE // Mailbox (1 slot, 덮어쓰기)다양한 동작이 같은 API에 인자만 다르게 들어가 표현됩니다.
#Performance
Send / Receive (empty wait list):- portENTER_CRITICAL: 5 cycle- check queue state: 10 cycle- memcpy item: itemSize / 4 × 1 cycle- wake check: 10 cycle- portEXIT_CRITICAL: 5 cycle
Total ≈ 30 + itemSize/4 cycle작은 item(약 16 byte)이면 34 cycle, 약 0.2 µs입니다. 매우 빠릅니다.
#Stream Buffer — Byte 단위
StreamBufferHandle_t sb = xStreamBufferCreate(256, 1);xStreamBufferSend(sb, data, len, timeout);가변 길이 byte stream을 다룹니다. 1
producer/consumer를 전제로 하므로 lock-free 구현이 가능합니다.#Message Buffer — 가변 길이
xMessageBufferSend(mb, msg, msg_len, timeout);각 message에 length prefix가 붙어, 수신 시 정확한 size를 알 수 있습니다.
#자주 하는 실수
⚠️ Item size mismatch
xQueueCreate(10, sizeof(int))로 만든 큐에 struct를 send하면 메모리가 깨집니다.
⚠️ Static 잘못 사용
Static queue도 storage buffer를 별도로 할당해야 합니다. xQueueCreateStatic을 씁니다.
⚠️ Pointer queue 후 free
수신자가 read하기 전에 sender가 free하면 dangling pointer가 됩니다. Memory pool로 관리하는 것이 좋습니다.
⚠️ Queue 크기 underestimate
10개로 설정했는데 burst로 50개가 들어오면 drop이 발생합니다. 크기 모니터링이 필요합니다.
#정리
- Queue는 Ring buffer와 2개의 wait list 조합입니다.
- uxMessagesWaiting이 상태를 표현하고, pcWriteTo·pcReadFrom이 ring 내 위치를 관리합니다.
- cTxLock·cRxLock으로 ISR 안전성을 확보합니다.
- Send 시 가장 높은 priority의 receiver를 wake합니다.
- Send to front, overwrite, by-pointer 같은 variant가 있습니다.
다음 편은 Event Group입니다.
#관련 항목
Practical RTOS Internals · 29 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
관련 글
Semaphore 내부 구현 추적 — Counter·Wait List·ISR-Safe Variant
FreeRTOS semaphore = Queue wrapper. Counter + priority-sorted wait list.
큐와 메시지 패싱 — Producer-Consumer·Ring Buffer·전달 의미
Task 간 데이터 전달의 표준입니다. FreeRTOS는 by-value copy이며, 대용량은 pointer queue로 처리합니다.
FreeRTOS 소스 분석 — tasks.c·queue.c·port.c 추적
FreeRTOS-Kernel 저장소의 핵심 파일 셋을 따라가며 xTaskCreate부터 PendSV까지의 흐름을 정리합니다. TCB·ready list·port 계층 사이의 경계가 어떻게 그어져 있는지 source 수준에서 살펴봅니다.
이 글을 참조하는 글 (6)
- FreeRTOS 소스 분석 — tasks.c·queue.c·port.c 추적— Practical RTOS Internals
- Stream Buffer와 Message Buffer — FreeRTOS 10의 Lock-Free SPSC— Practical RTOS Internals
- Event Group 분석 — Bit Flag·AND/OR Wait·Sync Barrier— Practical RTOS Internals
- Priority Ceiling Protocol — Immediate vs Original 비교— Practical RTOS Internals
- 큐와 메시지 패싱 — Producer-Consumer·Ring Buffer·전달 의미— Practical RTOS Internals
- RTOS Queue 활용 — By-Value·By-Reference·Timeout 패턴— Modern Embedded Recipes