21.5.3.1.1. 제어 루프 진입 시 updated() 플래그의 비트 마스킹(Bit Masking) 고속 검사
비행 제어 속도(Rate Control)를 관장하는 런루프는 1초에 무려 1000번 이상(1000Hz) 도는 극도의 핫 패스(Hot Path) 구역이다.
앞 단원에서 우리는 이 런루프의 가장 첫머리에 파라미터 업데이트를 감지하는 if (_parameter_update_sub.updated()) 라는 문장을 떡하니 배치했다.
초당 1000번 호출되는 이 함수가 만약 운영체제 커널 깊숙한 곳을 건드리는 무거운 시스템 콜(System Call)이나, 데이터베이스를 뒤지는 함수였다면 드론은 1초도 못 버티고 비행 제어 지연(Control Delay)으로 추락했을 것이다.
하지만 uORB 시스템 설계자들은 **비트 마스킹(Bit Masking)**이라는 고전적이고 원초적인 기법을 동원하여 이 검사의 지연 시간을 단 몇 CPU 클럭(Clock) 수준으로 끊어버렸다.
1. updated() 함수의 내부 동작: 시퀀스 넘버(Sequence Number)의 마술
_parameter_update_sub.updated() 함수의 배를 갈라보면 내부적으로 거대한 메시지 큐(Queue)를 뒤지지 않는다.
단순히 두 개의 64비트 정수형(Unsigned Integer) 숫자를 메모리에서 꺼내와서 비교 계산을 할 뿐이다.
_last_generation: 내가(구독자가) 마지막으로 메시지를 읽어 갔을 때의 세대 번호 (로컬 변수).ORB_node->generation: 서버(퍼블리셔)가 새 메시지를 쓸 때마다 1씩 올라가는 현재 세대 번호 (공유 메모리 변수).
// uORB::Subscription 내부의 실제 구동 원리 (단순화)
bool updated() {
// 1. 노드(공유 메모리)에서 현재 최신 시퀀스 넘버를 읽어온다.
unsigned current_gen = get_node_generation();
// 2. 내가 예전에 기억해 둔 숫자와 최신 숫자가 다르면? (뭔가 새로운 게 왔다는 뜻!)
return (_last_generation != current_gen);
}
이 단순한 정수 비교 연산은 멀티 코어 환경에서도 값싼 원자적 읽기(Atomic Read) 한 번으로 캐시 메모리(L1 Cache)에서 곧장 처리된다. 어떠한 락(Mutex)이나 스핀락(Spinlock)도 걸리지 않는 순수한 락-프리(Lock-free) 검사이다.
2. 비트 마스킹을 통한 멀티 토픽 고속 병렬 검사
런루프 안에는 parameter_update_s 에 대한 updated() 검사만 있는 것이 아니다. 센서 데이터(sensor_gyro, sensor_accel)나 조종기 입력(manual_control_setpoint) 등 수십 개의 토픽 수신기들이 일제히 updated()를 외치고 있다.
PX4의 숙련된 개발자들은 수십 개의 if 문을 줄세우는 대신, **이벤트 비트 마스크(Event Bit Mask)**라는 한 차원 높은 폴링(Polling) 최적화 기법을 사용하곤 한다.
px4_poll() 또는 px4_epoll 계열의 함수를 사용하면, 운영체제는 수십 개의 updated() 상태를 하나의 거대한 32비트 또는 64비트 정수 조각(Bitfield)으로 압축하여 관리한다.
// 32개의 토픽 상태를 단 하나의 32비트 정수에 매핑한 예시
// 0번째 비트: 자이로 센서 업데이트 여부
// 1번째 비트: 가속도 센서 업데이트 여부
// ...
// 31번째 비트: 파라미터 업데이트 여부
uint32_t event_mask = check_all_orbs();
// 비트 마스킹 고속 검사 (비트 시프트 연산)
if (event_mask & (1 << 31)) {
// 31번째 비트가 1로 켜져 있다!
// 파라미터 업데이트 수신 처리 로직!
_parameter_update_sub.copy(¶m_update);
updateParams();
}
CPU 아키텍처 관점에서 비트 논리곱(&)과 비트 시프트(<<) 연산은 산술 연산(덧셈, 뺄셈)보다도 훨씬 빠르며 파이프라인(Pipeline)을 가장 훌륭하게 활용하는 기계어 명령어들이다.
이 비트 마스킹 최적화 덕분에, 제어 루프는 수십만 번을 회전하면서도 “어떤 토픽이 새로 갱신되었는가?“를 찾아내는 데 티끌만 한 딜레이도 겪지 않게 된다.
이제 번개처럼 빠르게 파라미터 업데이트 사실을 감지했다. 그렇다면 그 감지 서킷 뒤쪽으로 호출되는 updateParams()는, 내 메모리 안의 멤버 변수들을 어떻게 Thread-Safe 하게 덮어쓰는지 다음 단원(21.5.3.1.2)에서 살펴보자.