19.5.1.2. px4_pollfd_struct_t 구조체 배열 설정 및 감시 이벤트(POLLIN) 지정
직전 단원에서 px4_poll()이라는 우아하고도 폭력적인 비동기 블로킹 무기를 소개했지만, 이 육중한 시스템 콜 매크로 함수는 맨손으로 허공에 대고 무작정 때릴 수 없다. 커널 스케줄러에게 “내 스레드가 도대체 어떤 VFS 파이프라인의 꼬리 입구를 감시하고 싶은지”, 그리고 **“정확히 어떤 종류의 메모리 인터럽트 이벤트가 발생했을 때만 나를 이 수면 큐에서 후려쳐 깨워야 하는지”**를 아주 명확하고 엄격한 하드웨어적인 명세서로 작성하여 링커에 제출해야만 한다.
PX4 펌웨어 운영체제는 이 잔혹한 인터럽트 명세서를 작성하기 위해 px4_pollfd_struct_t 라는 철저히 규격화된 전용 C 구조체 메모리 변수 타입을 강제한다. 이 절대적인 구조체는 데몬이 향후 여러 개의 다수 센서를 동시에 병렬 감시할 수 있도록 스택에 대개 배열(Array) 형태로 선언되며, 런타임 오버헤드를 막기 위해 반드시 스레드의 무한 루프(while) 블록 진입 직전 가장 바깥쪽 외곽에 단 한 번만 영구적으로 메모리에 세팅(Setting)되어 타설된다.
1. 스케줄링 감시 명세서 타설: px4_pollfd_struct_t 배열 세팅
우리가 정말 힘겹게 앞서 19.4.1.1 단원에서 VFS를 뚫고 개통해 두었던 구독자 파일 디스크립터 절대 마스터 핸들(int sensor_sub_fd)을, 이 명세서의 타겟 방에 욱여넣고 락을 거는 과정이다. 그리고 가장 치명적으로, 커널에게 ’새로운 물리 데이터의 인입(Data In)’을 뜻하는 POLLIN 하드웨어 이벤트를 죽을 때까지 감시해 달라고 청원하는 세팅 코드를 파헤쳐 본다.
#include <px4_platform_common/posix.h> // px4_poll 코어 API와 px4_pollfd_struct_t 구조체 정의를 당겨오기 위한 절대 핵심 포직스 헤더 훅업
// 1. 미들웨어 VFS 수신 파이프 다이렉트 개통
int sensor_sub_fd = orb_subscribe(ORB_ID(sensor_test_data));
// 2. [가장 치명적인 인터럽트 명세서 타설] 커널이 감시해야 할 파일 디스크립터(FD) 타겟들을 묶어 담을 폴링(Polling) 전용 구조체 배열 선언
// - 현재 이 데몬은 오로지 sensor_test_data 단 1개의 토픽 파이프만 외롭게 감시하므로 배열 크기를 '1'로 아주 타이트하게 하드코딩 잡는다.
px4_pollfd_struct_t fds[1];
// 3. 0번째 배열 슬롯 안에, 감시 타겟 C 포인터 물리 데이터 세팅
// - fds[0].fd: 내가 스케줄러에게 감시해 달라고 청부하는, 아까 1번에서 확보한 진짜 VFS 마스터 키 텍스트 넘버
fds[0].fd = sensor_sub_fd;
// - fds[0].events: 도대체 어떤 물리적 조건의 이벤트에서 내 기절한 스레드의 뺨을 후려쳐 깨울 것인가? 마스크(Mask) 지정!
// [POLLIN]: "누군가 건너편 I2C 퍼블리셔가 저 까마득한 버퍼 반대편에서 단 1바이트라도 데이터를 새로 강제 써넣으면, 즉시 나를 깨워라"
// 만약 돌팔이처럼 이 값을 실수로 0으로 비워둔다면 커널은 죽을 때까지 데이터를 완전 영원히 무시해 버리고 스레드는 평생 깨지 못한다.
fds[0].events = POLLIN;
// 4. (아키텍처 관점) 백그라운드 무한 루프 진입 전, 이 폴링 마스크 덩어리 객체 배열은 로컬 메모리에 영구 등록 완료되었다.
// 이후 하단 while 루프 심연 안에서, 이 fds 배열 껍데기 자체를 통째로 px4_poll() 함수의 입구에 던져 넣고
// 내 스레드를 영원한 블로킹 수면 구렁텅이로 밀어 넣어 자발적 기절 상태에 빠지게 만들 것이다.
2. POLLIN 인터럽트 마스크의 소름 돋는 하드웨어적 진실 (Edge Trigger)
여기서 PX4를 처음 만지는 다른 RTOS 초급 개발자들이 가장 많이 바보같이 착각하는 것이 바로 이 POLLIN 마스크의 물리적 동작 원리이다. POLLIN은 단순히 “지금 버퍼에 안 읽은 데이터가 고여있다“고 해서 허공에 영원히 1(True) 플래그를 세우고 비명을 지르고 있는 무식한 정적 상태(State) 변수가 절대 아니다.
만약 내 구독자 스레드가 뺨을 맞고 깨어나서 하단 로직의 orb_copy()를 통해 버퍼 꼬리단에 고인 그 최신 새 패킷 스냅샷을 1방 쏙 빼먹어 복사해버리고 나면, 해당 VFS 링 노드의 커널 상태는 그 즉시 ‘안 읽은 신규 데이터 하나도 없음(Empty)’ 모드로 곤두박질치며 전락하며, 켜져 있던 POLLIN 인터럽트 레지스터 플래그는 그 즉시 0으로 소멸(Clear)된다.
그러다 또 10ms 뒤 다른 반대편 센서 퍼블리셔가 다음 프레임 값을 memcpy로 VFS 링 버퍼에 강제로 찍어 내려쓰는 가장 결정적이고 극단적인 상태 변화(Edge Triggering)의 절정의 찰나 순간에만, 커널이 다시 1로 POLLIN 이벤트를 내부 폭발시키며 백그라운드 스레드의 멱살을 잡고 px4_poll 콘크리트 바리케이드에서 탈출시켜 기상시켜 주는 것이다.
이 끔찍하게 정교한 엣지 트리거(Edge-Triggered) 이벤트 큐 아키텍처 덕분에, 우리는 19.4.1 단원에서 겪었던 그 참담한 맹점인 “똑같은 데이터를 두 번, 세 번 무지성으로 읽는(Over-reading)” CPU 점유율 대참사를 커널 하드웨어 인터럽트 단에서 0.00%로 완벽히 원천 물리 차단하게 되는 것이다.