36940db938a8f4a1e919496793ed439850a221c2의 데이터 전달 계약을 비교한다. 기존 IPC / Synchronization 글의 semaphore·mutex·message queue 입문과 달리 payload 저장 방식, 버퍼 수명, 대기·ISR 조건을 중심으로 읽는다. 그림은 새로 작성한 설명용 도식이다.1. 먼저 무엇이 이동하는지 고른다
생산자와 소비자를 잇는 객체를 고를 때 크기보다 먼저 소비자가 어떤 메모리를 언제 읽는가를 따져야 한다. FIFO/LIFO는 원래 항목을 연결하고, message queue는 정해진 크기만큼 복사한다. mailbox에는 상대 스레드와 메시지 처리 완료까지의 계약이 있다. pipe는 항목이 아닌 바이트 스트림이다.
| 객체 | 전달 단위 | payload와 저장 | 자연스러운 사용 |
|---|---|---|---|
| FIFO | 가변 크기 항목의 주소 | 항목 자체 연결, payload 복사 없음 | 들어온 순서대로 소비 |
| LIFO | 가변 크기 항목의 주소 | 항목 자체 연결, payload 복사 없음 | 가장 최근 항목 먼저 소비 |
| Message queue | 고정 크기 레코드 | 대기 수신자 또는 고정 용량 ring buffer로 복사 | 작은 독립 값, 용량 상한 |
| Mailbox | 스레드 간 메시지 설명자·가변 payload | 매칭 뒤 수신/후속 호출 때 송신 버퍼에서 복사 가능 | 상대 선택·처리 완료·지연 수신 |
| Pipe | 바이트 스트림의 일부 또는 전부 | 대기 독자에게 직접 또는 선택적 ring buffer로 복사 | 길이가 달라지는 연속 바이트 |
FIFO/LIFO에서 ‘주소 전달’은 메모리를 자동 할당하거나 복제한다는 뜻이 아니다. 누가 항목의 수명을 책임질지 애플리케이션이 정해야 한다. 반대로 message queue에서 큰 구조체를 통째로 복사하면 잠금 중 복사 시간이 길어질 수 있다. 필요하면 메시지 본체 대신 포인터 값을 고정 크기 레코드로 보낼 수 있지만, 그 포인터가 가리키는 데이터의 수명은 여전히 별도 계약이다.
근거: 3.7.0 Kernel Services 비교표, Message Queues, 고정 문서 mailboxes.rst, Pipes.
2. FIFO와 LIFO는 같은 항목을 다른 끝에서 꺼낸다
세 항목 A·B·C가 모두 큐에 쌓인 뒤 소비한다면 FIFO는 A→B→C, LIFO는 C→B→A를 돌려준다. 다만 이미 기다리는 소비자가 있으면 새 항목은 큐에 쌓이기 전에 그 스레드로 바로 전달될 수 있다. 그러므로 아래 순서는 항목이 저장된 경우의 그림이다.
↔ 좁은 화면에서는 항목과 소비 결과를 좌우로 이동해 읽으세요.
Zephyr의 k_fifo_put/k_lifo_put은 각각 내부 queue append/prepend로 연결한다. 항목 payload를 별도 버퍼에 복사하지 않는다.
| 경로 | 항목 배치 | 소유권·실패 경계 |
|---|---|---|
k_fifo_put / k_lifo_put | 항목 첫 word를 커널 연결용으로 예약하고 word 정렬 | 같은 항목을 꺼내기 전에 다시 넣으면 정의되지 않은 동작. 생산자는 소비 전 항목 내용을 재사용하지 않음 |
k_fifo_alloc_put / k_lifo_alloc_put | 항목 첫 word 예약은 불필요. 기다리는 소비자가 있으면 주소를 직접 넘기고, 없어서 큐에 저장할 때만 호출 스레드 resource pool의 임시 연결 노드를 할당 | payload 자체는 복사되지 않음. 노드 할당 경로에서는 실패 가능(-ENOMEM) |
get(timeout) | 같은 항목의 주소를 반환 | 빈 큐라면 thread는 대기 가능; ISR은 K_NO_WAIT만 사용 |
무한 큐가 아니다. 고정된 내부 슬롯 수는 없지만 항목 메모리와 임시 연결 노드의 자원은 유한하다. 소비 속도보다 생산 속도가 빠르면 애플리케이션이 항목 풀·폐기·역압 정책을 별도로 둬야 한다.
근거: 3.7.0 FIFOs, 고정 문서 LIFOs, kernel.h FIFO 매크로와 LIFO 매크로, kernel/queue.c 124–238행.
3. Message queue는 한 칸의 크기와 칸 수를 고정한다
k_msgq는 msg_size 바이트짜리 칸을 max_msgs개 둔 ring buffer다. put()은 데이터 바이트를 복사한다. 기다리는 수신자가 있으면 그 수신 버퍼로 바로 복사하고, 없으면 ring에 복사한다. get()도 수신자 버퍼에 복사한다. 성공한 put 뒤에는 생산자의 원본 값 변수가 큐의 저장소가 아니다.
↔ 좁은 화면에서 생산자·ring·소비자 단계를 좌우로 이동해 읽으세요.
msg_size 바이트max_msgs칸이 가득 찬 경우에는 성공한 복사가 아직 일어나지 않았다. thread의 대기형 put은 송신 원본 주소를 대기 상태에서 보유하므로 호출 완료 전까지 그 버퍼가 유효해야 한다.
| 상황 | 동작 | 확인할 결과 |
|---|---|---|
| 빈 큐에 put | 고정 크기 복사. 기다리는 수신자면 직접, 아니면 ring으로 | 성공 뒤 원본 변수는 재사용 가능 |
| 가득 찬 큐에 put | K_NO_WAIT면 즉시 실패, thread의 대기형 timeout이면 공간을 기다림 | 즉시 -ENOMSG, 대기 시간 초과 -EAGAIN |
| 빈 큐에서 get | K_NO_WAIT면 즉시 실패, thread는 메시지를 기다릴 수 있음 | 반환값 확인 후 수신 버퍼 사용 |
| 큰 레코드 | payload 전체를 잠금 경로에서 복사 | 복사 시간·인터럽트 지연 검토. 큰 항목은 별도 수명 계약의 포인터 전달 고려 |
근거: 3.7.0 Message Queues, kernel/msg_q.c 124–179행과 216–277행.
4. Mailbox는 설명자 매칭과 데이터 소비 완료를 나눈다
메일박스는 thread 간 객체다. 송신자는 tx_target_thread, 수신자는 rx_source_thread를 지정하거나 K_ANY를 쓸 수 있다. 호환되는 두 설명자가 만나야 메시지가 전달된다. 수신자는 즉시 버퍼로 복사하거나 k_mbox_get(..., NULL, ...) 뒤 데이터 크기가 0보다 클 때 k_mbox_data_get()으로 복사·폐기할 수 있다. 크기가 0이면 수신할 때 즉시 정리되므로 후속 호출이 필요 없다. 동기 송신은 이 소비·정리가 끝나야 반환하고, 비동기 송신의 완료 알림은 semaphore를 전달했을 때만 온다.
↔ 좁은 화면에서 각 스레드의 단계와 버퍼 수명을 좌우로 이동해 읽으세요.
sem != NULL이면 완료 대기 가능sem != NULL일 때만 신호* 비동기 설명자 풀이 비어 있으면 k_mbox_async_put()도 내부 k_stack_pop(K_FOREVER)에서 설명자를 기다릴 수 있다. 비동기 설명자 복제만으로 payload의 독립 저장본이 생기지는 않는다. sem != NULL이면 완료 신호까지, sem == NULL이면 앱이 별도로 소비 완료를 확인할 때까지 tx_data를 재사용하지 않는다.
| 경로 | 기다리는 것 | 버퍼·제약 |
|---|---|---|
k_mbox_put() | timeout은 호환 수신자와 만나기까지. 매칭 후 payload 소비는 시간 제한 없이 기다릴 수 있음 | 송신 버퍼를 호출 완료까지 보존 |
k_mbox_async_put() | CONFIG_NUM_MBOX_ASYNC_MSGS > 0의 설명자 풀이 비면 내부적으로 기다릴 수 있음. 소비가 끝나면 sem != NULL인 경우에만 신호 | 설명자는 복제해도 tx_data는 복제 안 됨. semaphore가 없으면 앱의 별도 완료 확인 전 재사용 금지 |
| 수신 버퍼 NULL | 실제 수신 크기 > 0이면 설명자만 받고 payload 복사를 미룸. 크기 0이면 즉시 정리 | 크기 > 0일 때만 k_mbox_data_get(buffer) 또는 NULL로 복사·폐기 |
| ISR | 이 API 계열은 thread 간 메시지 교환 | ISR에서 송신/수신하지 않음 |
근거: 고정 문서 Mailboxes, kernel/mailbox.c 25–85행, 194–333행, 335–425행.
5. Pipe는 레코드 경계 없이 바이트 수를 약속한다
CONFIG_PIPES=y의 k_pipe는 선택적 ring buffer를 둔 바이트 흐름이다. 버퍼 크기 0도 가능하며, 대기하는 상대에게 직접 복사할 수 있다. 한 put이 한 get에 그대로 대응한다는 보장은 없다. min_xfer는 완료로 인정할 최소 바이트 수이고, bytes_written/bytes_read는 실제 이동량이다. 반환값이 0이어도 요청 전체가 이동했는지 이 숫자로 검사해야 한다. 특히 min_xfer=0의 성공 0은 한 바이트도 이동하지 않았을 수 있다.
↔ 좁은 화면에서 직접 전달·ring 저장·부분 완료 단계를 이동해 읽으세요.
min_xfer=4면 6바이트로 성공 가능K_NO_WAIT, min_xfer>0이면 미충족bytes_written=0과 오류 확인숫자는 API 동작을 읽기 위한 예시다. 실제 이동량은 동시 독자·writer, ring 공간, timeout에 따라 달라진다. min_xfer=0이라면 성공 0과 이동량 0도 함께 나올 수 있다.
| 조건 | 3.7.0 구현 | 해석 |
|---|---|---|
min_xfer > 요청량 | 입력 오류 -EINVAL | 최소 요구량부터 바로잡기 |
K_NO_WAIT이고 즉시 가능한 양 < min_xfer | 복사 전 -EIO, 이동량 0 | ISR도 이 무대기 조건을 따라야 함 |
min_xfer=0 | K_NO_WAIT 또는 timeout 경계에서 성공 0·이동량 0 가능 | 성공 코드만으로 전송을 주장하지 말고 이동량 확인 |
이동량 ≥ min_xfer이나 요청량보다 작음 | 성공 0 가능 | 남은 바이트 처리 책임은 호출자 |
| thread 대기 후 최소량 미달 | -EAGAIN 가능, 이동량은 별도 기록 | 오류값만 보고 부분 이동이 없었다고 단정 금지 |
근거: 3.7.0 Pipes, kernel/pipes.c 308–319행, 380–512행과 555–717행.
6. ISR에서 ‘쓸 수 있음’은 기다릴 수 있다는 뜻이 아니다
ISR는 잠들 수 없다. Zephyr 3.7.0의 FIFO/LIFO, message queue, pipe에는 ISR 허용 경로가 있지만 대기형 get/put과 구별해야 한다. 특히 k_msgq_put()과 k_pipe_put()은 ISR에서 K_NO_WAIT가 필요하다. mailbox는 이 계열과 달리 thread 간 교환용이다.
| 객체 | ISR send/put | ISR receive/get | thread가 기다릴 때 |
|---|---|---|---|
| FIFO / LIFO | put 가능 | get(K_NO_WAIT)만 | 빈 큐 get은 timeout 대기 가능 |
| Message queue | put(K_NO_WAIT)만 | get(K_NO_WAIT)만 | 가득 찬 put·빈 get 모두 timeout 대기 가능 |
| Mailbox | 불가 | 불가 | 동기 put의 수신 매칭 대기, 매칭 후 데이터 소비까지 대기 |
| Pipe | put(..., K_NO_WAIT)만 | get(..., K_NO_WAIT)만 | min_xfer 미달이면 timeout 대기 가능 |
실패 경로부터 설계한다. ISR가 보낸 데이터가 꽉 찬 ring이나 부족한 pipe 공간 때문에 거절될 수 있다. 즉시 실패를 단순히 무시하지 말고 데이터 유실 허용 여부와 재시도 주체를 정한다. FIFO/LIFO도 항목 수 상한이 없다는 말이 메모리 상한이 없다는 뜻은 아니다.
근거: 3.7.0 ISR 비교표, msg_q.c ISR assert, pipes.c ISR assert, 고정 Mailboxes 문서.
7. 보드에서 남길 검증 기록
- Zephyr commit, 최종
.config의CONFIG_PIPES와CONFIG_NUM_MBOX_ASYNC_MSGS, 객체 생성 위치·초기화 순서를 남긴다. - FIFO/LIFO 항목마다 예약 word, 정렬, 할당 주체, put 뒤 재사용 시점, 중복 삽입 방지를 기록한다.
- Message queue의
msg_size × max_msgs, put/get 반환값, 가득 참과 purge 정책, 큰 레코드 복사 시간을 측정한다. - Mailbox의 송신 버퍼 수명, 상대 매칭 조건, deferred
data_get, 완료 semaphore와 async 설명자 풀 고갈을 별도 실험한다. - Pipe의 요청 바이트·
min_xfer·실제 이동량·반환값을 함께 기록하고, 부분 이동 뒤 남은 데이터 처리를 확인한다. - ISR 경로는 모두 무대기 호출만 사용하고, 데이터 유실·재시도 규칙을 별도로 검증한다.
8. 고정 버전 근거와 설명의 경계
공식 v3.7.0 문서와 같은 commit의 공개 API·구현을 대조했다. A·B·C 및 바이트 수는 설명용이며, 보드에서의 처리량·ISR 지연을 측정한 값이 아니다. Mailbox의 비동기 payload 수명은 설명자만 복제하고 실제 복사는 수신 시 수행하는 고정 소스에서 도출한 사용 조건이다.