21.6.1.1. `WorkItem` 객체 생성 시 NuttX 커널 큐 연동

21.6.1.1. WorkItem 객체 생성 시 NuttX 커널 큐 연동

앞서 우리는 CustomApp 클래스가 px4::ScheduledWorkItem (즉, px4::WorkItem의 확장판)을 상속받으면서, 내부에 work_s 라는 원시적인 C 구조체를 품게 된다는 것을 알았다.
하지만 이 빈껍데기 구조체가 스스로 스케줄러(Scheduler) 버스 정류장까지 찾아갈 수는 없는 노릇이다. 우리는 CustomApp 객체가 태어나는 바로 그 순간, 이 구조체와 커널(Kernel) 사이에 은밀한 신고식을 치러야 한다.

1. 생성자의 완성: 스케줄러 버스 티켓 발권

모듈의 모든 초기화가 이루어지는 CustomApp 생성자의 초기화 리스트(Initializer List)를 다시 한번 들여다보자.
이번에는 21.5.1 단원에서 배웠던 ModuleParams 뿐만 아니라, ScheduledWorkItem 부모를 위한 초기화 구문이 추가된다.

// 완벽하게 완성된 CustomApp의 생성자 초기화 리스트
CustomApp::CustomApp() :
    ModuleParams(nullptr),
    ScheduledWorkItem(MODULE_NAME, px4::wq_configurations::rate_ctrl) // 핵심!
{
    // ...
}

생성자의 괄호 안에서 우리는 ScheduledWorkItem 부모에게 두 가지 중요한 정보를 넘겨주었다.

  1. MODULE_NAME: “내 이름은 custom_app 이야. 로그를 찍을 때 이 이름표를 가슴에 달아줘.”
  2. px4::wq_configurations::rate_ctrl: “나를 아무 큐(Queue)에나 넣지 말고, 반드시 자세 제어기(Attitude Controller)들이 타는 최고속 rate_ctrl 버스에 태워줘!”

이 두 번째 인자가 바로 픽스호크 내부에서 운영되는 여러 스레드 풀(Thread Pool) 노선 중에서 내가 묻어가고 싶은 정확한 타깃 워크 큐(Target Work Queue) 이름표이다. 이 이름표를 들고 부모 클래스인 ScheduledWorkItem이 태어나는 순간, 백그라운드에서는 어떤 일들이 벌어질까?

2. WorkItem 탄생의 뒷단 로직 (커널 등록)

ScheduledWorkItem의 내부 생성자는 여러분이 건네준 버스 이름표(rate_ctrl)의 진짜 메모리 주소를 찾기 위해 PX4 시스템 내부의 워크 큐 매니저(Work Queue Manager) 데몬에게 수소문한다.

// px4::ScheduledWorkItem 생성자 내부 (단순화된 개념)
ScheduledWorkItem::ScheduledWorkItem(const char *name, const wq_config_t &config)
{
    // 1. 내가 타야 할 버스(WorkQueue 스레드)의 진짜 메모리 객체를 찾아온다.
    WorkQueue *wq = WorkQueueManagerFindOrCreate(config);
    
    // 2. 그 버스 객체의 주소를 뱃속 깊은 곳에 저장해 둔다.
    _wq = wq;
    
    // 3. 커널 큐에 던져넣을 _work 구조체를 깨끗하게 청소해 둔다.
    memset(&_work, 0, sizeof(_work));
    
    // 4. (중요) 커널에게 나를 실행할 때 쓸 함수 주소를 묶어버린다!
    // -> 21.6.1.1.1 단원에서 다룸
}

이 과정이 성공적으로 끝나면, 내 모듈은 드디어 픽스호크 운영체제 스케줄러의 거대한 물류망(Logistics) 안에 정식 화물(Work Item)로 등록될 준비를 마친 것이다.

하지만 아직 한 가지 치명적인 미스터리가 남아있다.
NuttX 커널(C 언어)은 C++ 객체의 Run() 이라는 잘빠진 메서드를 직접 호출할 능력이 없다. 커널이 아는 것은 오직 “이 메모리 번지(Address)부터 코드를 실행해라“라는 함수 포인터(Function Pointer)뿐이다.

그렇다면 어떻게 운영체제의 C 언어 큐(Queue) 팝(Pop) 동작이 C++ 클래스 내부의 Run() 메서드를 기적처럼 발동시키는 것일까?
이 포인터 해킹과 캐스팅(Casting)의 미학을 다음 장(21.6.1.1.1)에서 속 시원하게 파헤쳐보겠다.