18.6.1 POSIX Poll 메커니즘을 이용한 비동기 멀티플렉싱 대기

18.6.1 POSIX Poll 메커니즘을 이용한 비동기 멀티플렉싱 대기

비행 제어 소프트웨어에서 가장 복잡한 모듈로 꼽히는 위치 추정기(EKF2)를 상상해 보자. 이 모듈은 위치를 계산하기 위해 자이로, 가속도계, 지자기 센서, 기압계, GPS 등 수많은 센서 데이터를 동시에(하지만 각기 다른 주기로) 수신해야 한다.

만약 이 5개의 센서를 폴링(Polling, orb_check) 방식으로 무한 루프를 돌며 검사한다면 CPU 자원 낭비가 극심할 것이다. 그렇다고 5개의 센서마다 별도의 스레드를 만들어 대기시키는 것 또한 메모리와 컨텍스트 스위칭(Context Switching) 오버헤드 측면에서 최악의 설계다.

단일 스레드가 수면(Sleep) 상태에 빠진 채로 이 5개의 센서 중 ‘아무거나 하나라도’ 새로운 데이터를 뱉어낼 때 즉각적으로 깨어날 수 있다면 어떨까? 이 우아한 비동기 멀티플렉싱(Multiplexing) 대기를 가능하게 해주는 마법의 지팡이가 바로 px4_poll() (표준 POSIX poll 함수의 래퍼) 메커니즘이다.

1. px4_pollfd_struct_t 배열과 다중 채널 감시

개발자는 자신이 관찰하고 싶은 센서들의 인터페이스(파일 디스크립터, fd)들을 px4_pollfd_struct_t 라는 구조체 배열에 정갈하게 담아낸다.

px4_pollfd_struct_t fds[2];

// 1번 채널: 자이로 센서 관찰
fds[0].fd = _sensor_gyro_fd;
fds[0].events = POLLIN; // 데이터 수신 대기 이벤트 설정

// 2번 채널: GPS 센서 관찰
fds[1].fd = _vehicle_gps_position_fd;
fds[1].events = POLLIN;

// 최대 1000ms 동안 수면에 빠지며, 둘 중 하나라도 데이터가 오면 즉시 기상
int ret = px4_poll(fds, 2, 1000);

이 코드가 실행되는 순간, 제어기 스레드는 운영체제 스케줄러에 의해 즉시 실행 대기열에서 제거되고 깊은 수면에 빠진다(Blocking). CPU 점유율은 0%로 곤두박질친다.

2. uORBDeviceNode::poll() 내부의 대기열(Wait Queue) 등록

스레드가 수면에 빠지기 직전, 커널은 fds 배열에 담긴 각 토픽 경로를 따라 내려가 VFS에 마운트된 uORBDeviceNode::poll() C++ 메서드를 차례대로 두드린다.

이 메서드는 매우 정교한 작업을 수행한다.
“지금 이 스레드(PID)가 내 링 버퍼에 새 데이터가 오기를 기다리며 잠들려고 하니, 내 세마포어(Semaphore) 대기열(Wait Queue) 리스트에 이 스레드의 연락처를 등록해 두어라.”

노드 객체 내부에 존재하는 이 비밀스러운 대기열 명단에는 현재 이 토픽을 구독하며 잠들어 있는 모든 스레드들의 식별자가 촘촘히 기록된다.

3. 기상(Wake-up)과 선별적 데이터 페치

  1. 시간이 흘러 GPS 드라이버가 링 버퍼에 최신 위도/경도를 밀어 넣는다(publish).
  2. 퍼블리시 메커니즘이 대기열 리스트를 순회하며 잠들어 있던 EKF2 스레드에 강력한 ‘기상(Wake-up)’ 시그널을 날린다 (18.6.2절 상세 설명).
  3. 스레드가 잠에서 깨어나며 px4_poll() 함수가 반환(Return)된다.
  4. 반환 직후 EKF2는 fds 배열의 revents 필드를 순회 검사한다.
    fds[0].revents & POLLIN (자이로는 조용함), fds[1].revents & POLLIN (GPS에 이벤트 발생!)
  5. 오직 업데이트 플래그가 뜬 GPS fd에 대해서만 선별적으로 orb_copy를 수행한다.

이처럼 uORB의 px4_poll() 멀티플렉싱 도입은, 실시간 운영체제(RTOS)의 인터럽트 구동력을 100% 활용하여 “단 1바이트의 자원 낭비도 없이, 유효한 데이터가 생성되는 바로 그 1 마이크로초의 찰나에만 스레드를 기동시키는” 현대적 PX4 소프트웨어 아키텍처의 근간을 완성하였다.