21.6.3. 타임스탬프 기반 주기적 실행(Scheduled Work) 로직 구현
자, 이제 내 모듈을 완벽한 C 구조체(Work Item)로 포장해서 원하는 버스(Work Queue) 정류소 앞에 세워두었다.
하지만 버스 기사(Kernel Scheduler)는 여러분의 모듈이 언제 차에 타고 싶은지 관심이 없다. 여러분이 직접 버스 기사에게 “나를 정확히 1초 뒤에 태워주시오!” 혹은 **“앞으로 0.05초마다 계속 나를 태워주시오!”**라고 예약을 걸어야만 비로소 Run() 함수가 작동하기 시작한다.
과거 독립 스레드(Task) 시절에는 Run() 함수 내부에서 무식하게 usleep(1000) 같은 슬립(Sleep) 함수를 호출하여 스스로 스레드를 기절시켰다 깨우기를 반복했다.
하지만 Work Queue 체제 안에서는 내 모듈이 슬립에 빠지는 순간, 내장된 버스(rate_ctrl 등) 전체가 길바닥에 멈춰 서서 뒷차(다른 모듈들)까지 모조리 블로킹(Blocking) 시켜버리는 대형 사고가 발생한다.
따라서 현대 PX4 모듈은 절대로 sleep() 류의 함수를 쓰지 않고, 오직 커널의 **타이머 인터럽트(Timer Interrupt)**에 기대어 점잖게 자신의 차례를 예약하는 ‘스케줄링(Scheduling)’ 기법을 사용해야 한다.
1. 가지 예약 스케줄링 기법
ScheduledWorkItem 클래스를 상속받은 우리의 모듈은 3개의 강력한 스케줄링 무기를 손에 쥐게 된다.
ScheduleNow(): “방금 uORB 메시지가 왔거나 급한 일이 터졌어! 지금 당장(0초 뒤) 다음 버스가 오는 즉시 나를 무조건 태워줘!” (이벤트 기반 비동기 실행)ScheduleClear(): “아까 예약했던 거 다 취소해! 나 당분간 버스 안 탈 거야.” (모듈 정지 또는 조건 불충족 시 대기)ScheduleOnInterval(time_us): “지금부터 정확히time_us마이크로초(us) 간격으로, 영원히 나를 주기적으로 버스에 태워줘!” (정주기 제어 루프의 핵심)
이 중에서 비행 제어기나 센서 모듈을 짤 때 가장 뼈대가 되는 함수가 바로 세 번째인 ScheduleOnInterval() 이다.
단 한 줄의 C++ 호출처럼 보이지만, 이 함수가 하드웨어 클럭(Hardware Clock)과 커널 타이머를 어떻게 구워삶아 그토록 정확한 주기를 만들어내는지, 그 미세한 마이크로초 단위의 시계약동을 다음 장(21.6.3.1)에서 뜯어보자.