36940db938a8f4a1e919496793ed439850a221c2를 기준으로 한다. IPC / Synchronization의 개별 객체 입문과 Kernel Queues의 payload·버퍼 계약 다음 단계로, 여러 조건의 대기와 상태 보존을 설명한다. 예시는 동작 경계 설명용이며 보드 실행 기록은 아니다.1. 객체를 가로질러 기다릴까, 비트를 함께 기다릴까
k_poll()은 semaphore, FIFO, message queue, pipe, poll signal처럼 서로 다른 객체의 준비 조건을 한 스레드가 함께 관찰한다. 하나 이상이 준비되면 반환한다. k_event는 한 객체의 32비트 상태에 원인별 비트를 놓고, 지정한 비트 중 하나 또는 전부가 켜졌는지를 판정한다. 이름이 비슷한 k_poll_event.state는 전자의 배열 원소에 남는 알림 상태이고, k_event의 비트 값과 같은 저장소가 아니다.
↔ 좁은 화면에서는 두 모델을 오른쪽으로 이동하며 비교하세요.
Polling API · 여러 객체 중 하나
count > 0msgq
used > 0signal
raised
k_poll(events[], timeout)준비된 배열 원소의 state 확인
알림만 준다. semaphore 토큰이나 메시지는 그 뒤 별도 호출로 획득한다.
Event API · 한 객체의 비트 조합
입력bit 1
설정bit 2
전송
k_event_wait(_all)(mask)같은 32비트 값에서 any / all 판정
반환값은 요청한 비트와 현재 값의 교집합이다. 기본 대기는 비트를 소비하지 않는다.
2. “준비”가 뜻하는 것은 객체마다 다르다
k_poll_event에는 조건의 종류, 대상 객체, 현재 state가 있다. v3.7.0에서 사용 가능한 mode는 K_POLL_MODE_NOTIFY_ONLY다. 준비를 보고도 객체가 호출자에게 넘어오지 않으므로, 데이터 전달 형식과 별개로 어떤 동작을 다시 시도해야 하는지를 먼저 정해야 한다.
| poll type | 준비로 검사하는 것 | 반환 뒤 호출자가 할 일 | 경계 |
|---|---|---|---|
SEM_AVAILABLE | semaphore count > 0 | k_sem_take(..., K_NO_WAIT) | 알림과 take 사이에 다른 소비자가 가져갈 수 있음 |
FIFO_DATA_AVAILABLE | queue가 비어 있지 않음 | k_fifo_get(..., K_NO_WAIT) | 받은 포인터의 수명은 FIFO 계약을 따름 |
MSGQ_DATA_AVAILABLE | used_msgs > 0 | k_msgq_get(..., K_NO_WAIT) | 데이터가 경합 중 사라지면 빈 큐 반환을 처리 |
PIPE_DATA_AVAILABLE | 읽을 수 있는 바이트가 있음 | k_pipe_get(..., K_NO_WAIT) | CONFIG_PIPES를 쓸 때 |
SIGNAL | signaled != 0 | k_poll_signal_check()로 결과 확인 | 별도 reset 전에는 다음 poll도 즉시 준비 |
3. 알림은 예약이 아니다
k_poll()이 0을 돌려줘도 semaphore나 메시지의 소유권을 예약한 것은 아니다. 직접 take/get을 기다리던 스레드가 poller보다 먼저 객체를 얻을 수 있고, 알림을 받은 뒤 다른 소비자가 끼어들 수도 있다. 따라서 후속 획득은 K_NO_WAIT로 시도하고 실패를 정상 경쟁 결과로 다룬다.
↔ 좁은 화면에서는 시간의 흐름을 오른쪽으로 이동해 보세요.
k_sem_give()count가 1이 된다.
state = SEM_AVAILABLE준비 알림으로 깨어난다.
k_sem_take()먼저 토큰을 가져가 count가 0이 된다.
k_sem_take(K_NO_WAIT)실패할 수 있다. 알림은 획득 보증이 아니다.
한 번의 poll이 끝났다고 배열의 첫 원소만 보지 말자. 반환할 때 두 조건 이상이 준비될 수 있으므로 모든 state를 검사한다. 재사용 전에 각 state를 K_POLL_STATE_NOT_READY로 되돌린다. Signal에는 별도의 signaled 플래그도 있다. 다시 기다리려면 k_poll_signal_reset()으로 플래그를 지우고 poll event의 state도 초기화해야 한다.
/* sem, msgq, sig는 각각 이미 초기화되어 있다고 가정 */
struct k_poll_event watch[3];
k_poll_event_init(&watch[0], K_POLL_TYPE_SEM_AVAILABLE,
K_POLL_MODE_NOTIFY_ONLY, &sem);
k_poll_event_init(&watch[1], K_POLL_TYPE_MSGQ_DATA_AVAILABLE,
K_POLL_MODE_NOTIFY_ONLY, &msgq);
k_poll_event_init(&watch[2], K_POLL_TYPE_SIGNAL,
K_POLL_MODE_NOTIFY_ONLY, &sig);
for (;;) {
int rc = k_poll(watch, 3, K_FOREVER);
if (rc == 0) {
if (watch[0].state & K_POLL_STATE_SEM_AVAILABLE) {
if (k_sem_take(&sem, K_NO_WAIT) == 0) { handle_token(); }
}
if (watch[1].state & K_POLL_STATE_MSGQ_DATA_AVAILABLE) {
if (k_msgq_get(&msgq, &item, K_NO_WAIT) == 0) { handle_item(&item); }
}
if (watch[2].state & K_POLL_STATE_SIGNALED) {
unsigned int raised;
int result;
k_poll_signal_check(&sig, &raised, &result);
if (raised) { handle_signal(result); }
k_poll_signal_reset(&sig);
}
}
/* -EINTR 같은 다른 반환도 별도 정책으로 처리 */
for (size_t i = 0; i < 3; ++i) {
watch[i].state = K_POLL_STATE_NOT_READY;
}
}
위 코드는 핵심 순서를 보여 주는 발췌다. handle_*, item, 객체의 실제 선언과 오류 정책은 애플리케이션이 채운다. 여러 생산자가 같은 poll signal을 반복해서 raise하면 reset 경계의 알림이 합쳐지거나 놓일 수 있다. 발생 횟수가 필요하면 signal보다 count가 있는 semaphore나 항목을 저장하는 FIFO/msgq를 선택한다. Signal을 초기화·reset하는 소유자는 polling 스레드로 두는 것이 문서의 권장 방식이다.
근거: poll 반환·경합·state 재설정, 여러 준비 원소 검사, signal 플래그·결과·raise, 반복 reset과 신호 경쟁.
4. post, set, clear는 같은 32비트를 다르게 고친다
k_event는 이벤트 발생 횟수나 payload 큐가 아니라 켜져 있는 조건의 비트 집합이다. 예를 들어 아래는 32비트 중 낮은 네 비트만 보인 것이다. post는 OR로 더하고, set은 전체를 교체하며, clear는 지정한 비트만 내린다.
↔ 좁은 화면에서는 비트 3부터 결과값까지 오른쪽으로 이동해 확인하세요. 진한 칸은 1이다.
post(0100)01110111 = ORset(0100)01000100 = 교체clear(0100)00000000 = 해당 비트 제거| API | 새 저장값 | 0011에서 0100을 인수로 주면 | 주의 |
|---|---|---|---|
k_event_post(e, bits) | 기존값 OR bits | 0111 | 같은 비트를 여러 번 post해도 횟수는 남지 않음 |
k_event_set(e, bits) | bits로 전체 교체 | 0100 | 기존의 다른 비트가 내려갈 수 있음 |
k_event_clear(e, bits) | 기존값 AND NOT bits | 0011 | 켜져 있지 않은 bit 2만 지우므로 이 예시에서는 변화 없음 |
k_event_set_masked(e, bits, mask) | mask 안에서만 bits로 교체 | mask에 따라 다름 | 다른 비트는 보존 |
5. any/all은 판정 조건, reset은 대기 전 초기화
k_event_wait(e, mask, false, timeout)는 mask 중 하나라도 켜지면, k_event_wait_all은 모두 켜져야 반환한다. 성공 반환값은 저장된 전체 값이 아니라 현재값 & mask다. false로 기다리면 반환 뒤에도 비트가 남는다. true는 “읽은 비트 소비”가 아니라 대기 시작 전에 객체의 모든 비트를 0으로 지우기다.
↔ 좁은 화면에서는 시작 상태, reset=false, reset=true 경로를 오른쪽으로 이동해 비교하세요.
요청 mask는 0010. bit 1은 이미 켜져 있다.
wait(0010, false, K_NO_WAIT)반환 = 0010저장값 = 1010요청 비트가 보인다. 읽어도 지워지지 않는다.
wait(0010, true, K_NO_WAIT)반환 = 0000저장값 = 0000검사 전에 bit 3까지 모두 지운다. 다음 대기가 성공하려면 새 post가 필요하다.
| 호출 / 시작값 | 요청 mask | 즉시 결과 | 호출 후 저장값 |
|---|---|---|---|
wait(..., false, K_NO_WAIT) / 1010 | 0110 | 0010: 하나 일치 | 1010 |
wait_all(..., false, K_NO_WAIT) / 1010 | 0110 | 0000: bit 2 부족 | 1010 |
wait(..., true, K_NO_WAIT) / 1010 | 0010 | 0000: 먼저 전체 reset | 0000 |
요청 mask가 0이면 v3.7.0 구현은 reset 처리보다 먼저 0을 반환한다. 따라서 이 경우 reset=true여도 저장 비트를 지우지 않는다. 위 그림과 표는 요청 mask가 0이 아닌 경우다.
대기 중 새 비트가 조건을 만족하면 해당 waiter들이 깨어나지만 비트는 자동 소모되지 않는다. 여러 스레드가 같은 객체를 기다릴 때 한 스레드의 reset=true는 다른 스레드가 보던 비트도 지울 수 있다. 이미 처리한 특정 상태만 내려야 한다면 해당 bit mask로 k_event_clear()를 명시적으로 호출하되, 다른 생산자가 같은 비트를 동시에 갱신하는 설계에서는 세대 번호나 별도 큐가 필요한지 따져야 한다.
근거: any/all 판정, 대기 전 reset·반환 mask, 만족한 waiter 깨우기, 공개 wait 계약.
6. timeout과 ISR의 선을 넘지 말자
| 경로 | 즉시 / 유한 / 무한 대기 | 반환과 다음 동작 | ISR |
|---|---|---|---|
k_poll() | K_NO_WAIT, K_MSEC(n), K_FOREVER | 0 준비, -EAGAIN 시간 초과 또는 준비 조건 없는 K_NO_WAIT, -EINTR 취소. 0일 때 모든 state를 검사 | thread API. 구현이 ISR 호출을 assert로 막음 |
k_poll_signal_raise() | 대기 호출이 아님 | signal의 signaled와 사용자 result를 기록. reset 전까지 유지 | ISR에서 알림 생산 가능 |
k_event_wait() / wait_all() | K_NO_WAIT, K_MSEC(n), K_FOREVER | 성공하면 요청 mask와 일치한 비트, 시간이 초과되면 0. 이 API에는 -EAGAIN 반환이 없음 | ISR에서는 블로킹 금지. 현재 비트 조회는 k_event_test() 사용 |
k_event_post/set/clear() | 대기 호출이 아님 | 비트 변경이 여러 waiter의 조건을 함께 만족시킬 수 있음 | ISR에서 전달 가능 |
서로 다른 결과형에 주의한다. Poll은 성공/오류를 int로 반환하고 준비 원인은 배열의 state에 둔다. Event wait는 일치한 uint32_t 비트 자체를 반환하며 0은 조건 불충족이다. ISR에서 현재 상태를 확인할 때 공개 헬퍼 k_event_test()는 내부적으로 k_event_wait(..., false, K_NO_WAIT)를 호출한다.
근거: poll의 ISR assert와 즉시 반환, poll 반환 계약, event ISR의 블로킹 제한과 0 반환, k_event_test 헬퍼, ISR의 event 전달·조회.
7. 객체의 준비와 사건의 기록을 구별하기
하나의 dispatcher가 여러 입력원을 기다리고 각 객체를 별도로 소비한다면 k_poll이 알맞다. 반대로 여러 조건의 현재 성립 여부를 한 비트 집합으로 공유하고 any/all을 판정한다면 k_event가 맞다. 같은 비트의 발생 횟수나 매 발생의 payload가 중요하다면 둘 중 어느 비트 상태만으로도 충분하지 않다. count나 queue에 실제 정보를 보관해야 한다.
| 요구사항 | 기본 선택 | 반드시 확인할 것 |
|---|---|---|
| 서로 다른 sem/msgq/signal 중 먼저 준비되는 것 처리 | k_poll | state 전체 순회, K_NO_WAIT 획득 실패, 다음 반복 state 초기화 |
| 입력 A/B가 모두 발생했는지, 현재 플래그를 여러 waiter가 조회 | k_event_wait_all / k_event_test | reset=true의 전체 지우기, 오래 남은 비트 |
| 같은 알림이 여러 번 왔는지 또는 각 알림의 값 보존 | semaphore count / queue | signal과 event 비트의 합쳐짐, 큐 용량과 수명 |
디버깅 로그에는 k_poll의 반환값과 각 state, 실제 take/get 결과를 같은 시각에 남긴다. Event 쪽은 wait mask, reset 인수, 직전 저장 비트, 반환 비트, 이후 clear/post를 기록한다. “깨어났는데 일이 없다”는 현상도 이 두 시간축을 분리하면 경쟁인지 상태 초기화 실수인지 구별할 수 있다.
8. 고정 버전 근거와 이어 읽기
설명의 기준은 Zephyr 3.7.0 Polling API 문서와 3.7.0 Events 문서, 고정 commit의 poll.c, events.c, 공개 API 계약이다. 위 각 절의 좁은 링크는 실제 준비 검사와 상태 변경 위치를 가리킨다.