센서 태스크가 10ms마다 데이터를 보내는데 간헐적으로 샘플이 사라진다면 큐 길이와 소비 태스크의 지연을 함께 살펴봐야 합니다. 평균 처리 속도가 충분해도 플래시 기록이나 높은 우선순위 작업이 소비를 잠깐 막으면 큐가 가득 찰 수 있습니다.
이 글은 FreeRTOS에서 고정 크기 센서 메시지를 전달하는 개발자를 위한 가이드입니다. 공식 큐 API의 의미를 확인하고, 독립 실행 C 모델로 버스트와 소비 지연이 필요한 큐 길이에 미치는 영향을 계산합니다. 모델은 RTOS 스케줄러를 실행하지 않으며 실제 보드의 타이밍 측정을 대신하지 않습니다.
큐 길이는 바이트 수가 아니라 메시지 개수
xQueueCreate(length, sizeof(Sample))의 첫 인수는 저장할 항목 수입니다. 항목은 생성 시 지정한 크기만큼 복사되므로 구조체 큐와 포인터 큐의 메모리·수명 조건이 다릅니다. 이 동작과 생성 실패 시 NULL 반환은 FreeRTOS Kernel V11.2.0 공식 queue.h에 명시되어 있습니다.
가령 sizeof(Sample)이 해당 타깃에서 12바이트이고 길이가 11이면 항목 저장 공간은 132바이트입니다. 여기에 큐 관리 구조와 할당 방식에 따른 추가 공간이 필요합니다. 구조체 패딩을 고려해 실제 빌드의 sizeof를 사용하세요. 포인터 크기로 큐를 만들면 복사되는 것은 주소뿐이므로 원본 버퍼의 소유권과 재사용 시점을 따로 정해야 합니다.
평균 속도와 최악의 대기열을 분리해서 계산하기
주기 10ms인 생산자는 초당 100개를 생성합니다. 소비자가 지속적으로 5ms마다 하나를 꺼낼 수 있다면 초당 200개를 처리할 수 있어 밀린 항목을 줄일 여유가 있습니다. 하지만 이 평균만으로는 시작 시점의 정체나 순간 버스트를 설명할 수 없습니다.
계산 순서는 다음과 같습니다.
- 최장 소비 중단 시간 동안 도착할 항목 수를 계산합니다. 이미 큐에 있던 항목도 더합니다.
- 같은 구간에 겹칠 수 있는 버스트를 더합니다. 여러 생산자의 버스트가 동시에 발생할 수 있는지도 확인합니다.
- 같은 시각의 도착과 소비 중 무엇이 먼저 실행될지 명시합니다. 불확실하면 도착이 먼저인 경우도 검사합니다.
- 소비 재개 뒤 큐가 실제로 줄어드는지 확인하고, 지터·다른 지연·측정 오차에 대한 여유를 별도로 잡습니다.
다음은 이 글에서 직접 정의한 예제입니다. 초기 큐는 비어 있고, 생산자는 0·10·20ms처럼 10ms마다 하나를 넣습니다. 40ms에는 별도로 5개가 한꺼번에 추가됩니다. 소비자는 50ms부터 5ms마다 하나를 꺼내며, 시각이 같으면 도착을 먼저 처리합니다. 보낼 공간이 없으면 새 항목을 버리고 재시도하지 않습니다.
| 시각 | 도착 항목 수 | 길이가 충분할 때 소비 직전 점유량 | 해당 시각 소비 |
|---|---|---|---|
| 0ms | 1 | 1 | 없음 |
| 10ms | 1 | 2 | 없음 |
| 20ms | 1 | 3 | 없음 |
| 30ms | 1 | 4 | 없음 |
| 40ms | 정기 1 + 버스트 5 | 10 | 없음 |
| 50ms | 1 | 11 | 1개 |
50ms에 소비가 시작된다는 이유로 길이를 10으로 정하면 경계에서 한 항목을 잃습니다. 이 조건의 최소 길이는 11이지만, 11은 이 모델의 결과이지 모든 제품의 권장값은 아닙니다. 이후 또 소비가 멈추거나 버스트가 반복된다면 전체 반복 구간을 다시 분석해야 합니다.
PC에서 재현하는 큐 수용량 모델
아래 전체 코드를 queue_budget.c로 저장합니다. 동시성·우선순위·틱 양자화를 제외하고 도착과 소비 순서만 고정한 모델입니다. RTOS를 설치하지 않아도 GCC 또는 호환 C11 컴파일러로 실행할 수 있습니다.
빌드 명령은 gcc -std=c11 -Wall -Wextra -Werror queue_budget.c -o queue_budget입니다. Linux에서는 ./queue_budget, Windows PowerShell에서는 ./queue_budget.exe를 실행합니다. 검증 중에는 -DNDEBUG를 넣지 않아야 assert 검사가 유지됩니다.
실제 실행 결과는 다음과 같습니다.
0ms 이상 200ms 미만에 총 25개가 들어옵니다. 길이 10은 1개를 잃고 24개를 소비하며, 길이 11은 전부 소비합니다. 1~32 길이를 순회해 손실 경계와 항목 보존 관계도 검사했습니다.
소비 주기를 20ms로 바꾸면 소비 능력이 초당 50개로 떨어집니다. 손실이 나지 않도록 임시로 길이를 1000으로 두어도 남은 항목은 200ms 시점 17개, 2000ms 시점 107개로 늘어납니다. 지속적인 생산량이 소비량보다 많을 때는 유한한 큐를 늘리는 방법만으로 장시간 무손실을 보장할 수 없습니다.
FreeRTOS에 적용할 때의 실패 정책
태스크에서 xQueueSend(queue, &sample, 0)을 사용하면 공간을 기다리지 않고 결과를 반환합니다. pdPASS를 확인하고 실패 시 드롭 수를 집계하세요. 대기 시간을 주면 큐가 찬 경우 생산 태스크가 기다릴 수 있으므로 샘플링 주기와 상위 시간 제한까지 함께 검토해야 합니다. API 반환값과 대기 조건은 공식 큐 헤더의 xQueueSend 설명을 참고하세요.
| 데이터의 요구 | 선택할 수 있는 정책 | 점검할 항목 |
|---|---|---|
| 모든 이벤트를 순서대로 보존 | 처리 속도 개선, 상위 흐름 제어, 제한된 생산자 대기 | 최악 지연과 외부 버퍼 한도 |
| 일부 센서 샘플 손실 허용 | 새 항목 드롭 및 손실 카운터 | 시퀀스 번호로 손실 구간 식별 |
| 최신 상태 한 개만 필요 | 길이 1 큐와 overwrite 계열 검토 | 중간 변화가 사라져도 되는지 |
xQueueOverwrite는 길이 1인 큐 용도입니다. 일반 다중 항목 큐에서 오래된 항목을 제거하는 기능으로 사용하지 마세요. 알람·제어 명령과 최신 온도 표시처럼 보존 요구가 다른 메시지를 같은 손실 정책에 묶지 않는 편이 좋습니다.
ISR에서는 태스크용 호출 대신 xQueueSendFromISR 계열을 사용하고, 반환값·깨워진 높은 우선순위 태스크·포트의 ISR 종료 처리를 확인합니다. 인터럽트 우선순위 제한과 SMP 차이는 타깃 포트 문서를 따라야 합니다. ESP32 사용자는 Espressif의 FreeRTOS 공식 가이드를 함께 확인하세요.
보드에서 남길 관측값
모델을 통과한 다음에는 최대 큐 점유량, 전송 실패 횟수, 메시지 시퀀스 번호, 생성에서 소비까지의 최장 지연을 기록합니다. 태스크와 ISR이 같은 카운터를 갱신한다면 타깃에 맞는 동기화가 필요합니다. 매 샘플마다 무거운 로그를 찍으면 측정 자체가 타이밍을 바꿀 수 있으므로 집계값을 낮은 빈도로 출력합니다.
큐의 현재 여유 공간을 조회한 뒤 보낸다고 해서 경쟁이 사라지지는 않습니다. 다른 생산자가 그 사이에 공간을 차지할 수 있으므로 최종 전송 결과가 성공 여부의 기준입니다. 점유량이 낮아도 메시지 나이가 길다면 소비 우선순위나 처리 시간이 문제일 수 있습니다.
검증 범위: 2026년 9월 22일 Windows 호스트에서 C11 경고를 오류로 처리해 빌드·실행했습니다. 실제 MCU, FreeRTOS 커널 실행, ISR 지연과 하드웨어 버스트 시험은 수행하지 않았습니다. 공식 API 근거는 FreeRTOS Kernel V11.2.0 헤더와 Espressif 문서이며, 시간표·C 모델·기대 결과는 이 글의 독자적인 예제입니다.