18.6.2.1 orb_publish() 내에서 데이터 갱신 후 poll_notify() 호출 체인
앞선 18.6.1절에서 구독자 스레드들이 px4_poll()을 호출하여 수많은 낚싯대(FD)를 던져놓고 세마포어(Semaphore)의 얕은 수면(Sleep) 속에 빠져드는 과정을 살펴보았다. 그렇다면 깊은 잠에 빠져든 이 스레드들을 번개처럼 깨워주는 주체는 누구일까? 바로 데이터를 쏘는 자, 퍼블리셔(Publisher) 이다.
1. orb_publish()의 숨겨진 역할: 기상(Wake-up) 나팔수
사용자 스페이스에서 특정 센서 드라이버가 orb_publish() C API나 uORB::Publication::publish() C++ 래퍼를 호출하여 새로운 데이터를 밀어 넣게 되면, 이 호출은 VFS 레이어를 타고 내려가 uORBDeviceNode::write() 구역으로 진입하게 된다.
uORBDeviceNode::write()는 단순히 링 버퍼에 memcpy()를 수행하고 전역 세대 카운터(_generation)를 1 올리는 것으로 임무를 끝내지 않는다. 데이터 복사가 완벽하게 끝난 직후, 퍼블리셔 스레드는 해당 노드에 매달려 잠들어 있는 모든 구독자들을 깨우기 위해 결정적인 연쇄 호출 체인(Call Chain)의 방아쇠를 당긴다.
바로 보이지 않는 시스템 내부의 나팔수, poll_notify() 의 스피커 전원을 올리는 것이다.
// uORBDeviceNode::write() 내부 호출 체인 (슈도코드)
ssize_t uORBDeviceNode::write(file_t *filp, const char *buffer, size_t buflen)
{
// 1. 링 버퍼에 데이터 복사 (Tearing 방지 락프리 마스킹 동반)
// ...
// 2. 전역 세대 카운터 원자적 증가
fetch_add(&_generation, 1);
// 3. ✨ 현재 노드에서 잠들어 있는 구독자들을 깨움 ✨
poll_notify(&_pollset, 1, POLLIN);
return buflen;
}
2. poll_notify()의 내부 파급 효과
poll_notify는 단일 함수가 아니라 OS 커널 내부 깊숙이 파고드는 트리거이다. 이 함수는 첫 번째 인자로 자신(디바이스 노드)이 관리하는 폴링 대기자 명단(_pollset 포인터) 배열 전체를 넘긴다.
커널은 이 명단을 순회하며, 이벤트 타입이 POLLIN으로 플래깅되어 있고 현재 세마포어 슬립 상태에 빠져있는 모든 낚싯대(구독자 스레드)들을 차례차례 색출해 낸다.
즉, 퍼블리셔가 단 한 번의 orb_publish를 호출하더라도, 커널의 poll_notify는 이 토픽 하나를 바라보고 대기하던 내비게이터 1번, 로거 2번, 통신 모듈 3번 등 N개의 잠든 스레드들 모두를 대상으로 순차적인 기상 신호(Signal)를 발송하게 되는 강력한 1:N 이벤트를 발생시키는 것이다.
이러한 즉각적인 알림(Notification) 체인이 write()의 가장 하단부 꼬리에 단단히 엮여 있기 때문에, 퍼블리셔와 무관하게 완전히 분리되어 잠들어 있던 구독자들의 스레드는 비로소 하드웨어 인터럽트 급의 극단적으로 짦은 지연 시간(Latency) 만에 눈을 뜨고 새로운 데이터를 영접할 준비를 마치게 된다.