circular buffer. 생산자와 소비자의 속도 차이를 흡수한다.
static volatile uint8_t buf[64];
static volatile uint8_t head, tail; /* 크기를 2의 거듭제곱으로 두면 & 로 감쌀 수 있다 */
void ring_put(uint8_t c) { /* ISR 에서 호출 */
uint8_t next = (head + 1u) & 63u;
if (next != tail) { buf[head] = c; head = next; } /* 가득 차면 버린다 */
}
int ring_get(void) { /* 메인에서 호출 */
if (head == tail) return -1;
uint8_t c = buf[tail];
tail = (tail + 1u) & 63u;
return c;
}
왜 락이 필요 없나
생산자는 head 만, 소비자는 tail 만 쓴다. 각각 자기 인덱스만 갱신하므로 단일 생산자·단일 소비자 구조에서는 인터럽트를 끄지 않아도 안전하다. 인덱스 갱신이 원자적이어야 하므로 인덱스 타입을 CPU 워드보다 크게 잡지 않는다.
넘칠 때의 정책을 정해 둔다
버린다 최신 데이터가 중요할 때
덮어쓴다 로그처럼 최근 이력만 남기면 될 때
플래그를 세운다 놓쳤다는 사실 자체를 알려야 할 때 ← 대부분 이게 필요하다