18.6.1.1 px4_poll() 함수와 pollfd 구조체 배열의 동작 원리
PX4 소프트웨어 아키텍처에서 여러 개의 센서 토픽이나 제어 명령 토픽을 동시에 기다려야 하는 다중 구독(Multi-subscription) 모듈이 존재한다면, 앞서 배운 개별 토픽에 대한 orb_check()나 폴링(비지 웨이팅, Busy-waiting) 방식은 CPU 자원을 낭비할 수밖에 없다. 센서 데이터가 언제 들어올지 모르는 상황에서 계속루프를 돌며 상태를 묻는 것은 하드 리얼타임 시스템(Hard Real-time System)에서 절대 금기시되는 안티 패턴(Anti-pattern)이다.
이를 해결하기 위해 1세대 다중 동기화 기법인 px4_poll() API가 도입되었다.
18.6절에서는 개별 토픽 수신을 넘어선, 복수의 토픽에 대한 이벤트 구동(Event-driven) 동기화 메커니즘의 근간을 해부하며, 그 첫 번째 대상으로 POSIX poll 함수를 감싼 px4_poll()의 원리를 파악한다.
1. 다중 디바이스 멀티플렉싱(Multiplexing)의 필요성
내비게이터(Navigator) 모듈이나 센서 퓨전(Estimator) 필터를 생각해 보라. 이 모듈들은 가속도계(sensor_accel), 자이로스코프(sensor_gyro), GPS(sensor_gps), 지자기 센서(sensor_mag) 등 수십 개의 토픽 포문을 모두 열어두고(구독하고), 이 중 단 하나라도 새로운 데이터가 도착하면 즉시 깨어나(Wake-up) 퓨전 알고리즘을 한 사이클 돌린 뒤 다시 수면(Sleep) 상태로 들어가야 한다.
이 “동시에 여러 개의 파일 디스크립터(FD) 낚싯대를 던져놓고, 단 하나라도 찌가 흔들릴 때까지 스레드를 재워달라“는 요구사항을 완벽하게 만족하는 시스템 콜이 바로 POSIX 표준의 poll() 함수이다.
2. pollfd 구조체 배열의 세팅
PX4 애플리케이션 개발자는 px4_poll()을 호출하기 전, 먼저 운영체제 커널에 제출할 낚싯대의 목록, 즉 pollfd 구조체 배열을 직접 조립해야 한다.
// 다중 토픽 동기화를 위한 pollfd 배열 구성 예시
struct pollfd fds[3];
// 1번 낚싯대: 가속도 센서
fds[0].fd = orb_subscribe(ORB_ID(sensor_accel));
fds[0].events = POLLIN; // 읽을 데이터가 들어오는 이벤트(POLLIN)를 감시
// 2번 낚싯대: 자이로 센서
fds[1].fd = orb_subscribe(ORB_ID(sensor_gyro));
fds[1].events = POLLIN;
// 3번 낚싯대: GPS
fds[2].fd = orb_subscribe(ORB_ID(sensor_gps));
fds[2].events = POLLIN;
pollfd 구조체는 모니터링할 **파일 디스크립터(fd)**와, 어떤 종류의 이벤트를 감시할 것인지(PX4 수신에서는 항상 데이터 유입을 뜻하는 POLLIN)를 묶은 단순한 구조이다.
3. px4_poll()의 호출과 스레드 수면(Sleep)
낚싯대 배열이 완성되면, 개발자는 타임아웃(Timeout) 시간과 함께 px4_poll()을 호출한다.
// 3개의 토픽 중 하나라도 새 데이터가 오거나, 1000ms가 지나면 깨어남
int pret = px4_poll(fds, 3, 1000);
if (pret > 0) {
// 적어도 1개의 토픽에 데이터가 도착함
if (fds[0].revents & POLLIN) {
// 가속도 센서 데이터가 도착했음을 확인! (orb_copy 실행)
}
// ... 다른 센서 확인 로직 ...
} else if (pret == 0) {
// 1000ms 동안 아무 데이터도 오지 않아 타임아웃 발생
}
이 함수가 호출되는 순간, 이 코드를 실행하는 애플리케이션 스레드는 OS 커널 스케줄러 영역으로 이동하며 즉시 스케줄링 대기 상태(Blocked/Sleeping) 로 전환된다.
즉, pret가 반환되어 다음 줄의 if문이 평가되기 전까지, 이 스레드는 CPU 코어를 단 1사이클도 점유하지 않으며 완벽한 절전 및 자원 양보 모드에 돌입한다. 그리고 백그라운드의 uORB 코어 로직이 배열에 등록된 3개의 FD에 매핑된 uORBDeviceNode들 중 단 하나라도 새로운 데이터가 퍼블리싱되었음을 감지했을 때만, OS를 통해 이 스레드를 번개처럼 다시 깨워주는(Wake-up) 것이다.
이렇게 OS 레벨의 멀티플렉싱 시스템 콜에 기대어 비지 로직을 삭제하는 것이, PX4에서 수많은 모듈들이 서로 얽혀 있으면서도 치명적인 병목 현상 없이 부드럽게 비행을 제어할 수 있는 가장 든든한 동기화 기반 레이어이다.