# 1. 스레드(Thread)와 태스크(Task) 관리

# 1. 스레드(Thread)와 태스크(Task) 관리

하나의 CPU 코어가 수백 개의 작업을 ‘동시에’ 처리하는 것처럼 보이게 쪼개는 마술의 핵심은 결국 태스크(Task)와 스레드(Thread) 패러다임이다. PX4는 코드의 90%가 백그라운드 무한 루프 스레드 형태로 구동되는 지독한 멀티스레드 아키텍처이므로, 스레드의 생성 철학과 컨텍스트 스위칭(Context Switching)의 피 비린내 나는 이면을 숙지하지 않으면 메모리 누수나 데드락(Deadlock)에 빠져 시스템 전체가 먹통이 되는 늪을 피할 수 없다.

Task vs. pthreads 의 이중주
사실 NuttX 커널 관점에서 태스크(Task)와 스레드(Thread)는 근본적으로 동일한 스케줄링 엔티티(TCB: Task Control Block)를 가진다. 하지만 PX4 아키텍처는 이를 교묘하게 분리해서 사용한다.

  1. 메인 태스크 (Tasks): 콘솔(NSH)에서 커맨드를 치거나 부팅 스크립트(rc.S)에서 독립적으로 실행을 명할 때 생성되는 큰 덩어리다 (px4_task_spawn_cmd 로 호출됨). 예를 들어 ekf2 start 를 치면 새로운 태스크 프로세스가 만들어진다. 자신만의 독립적인 파일 디스크립터(File Descriptor) 테이블과 메인 힙을 소유한다.
  2. pthreads (POSIX Threads): 이미 실행 중인 메인 태스크 내부에서 서브 스레드를 은밀하게 여러 갈래로 쪼갤 때 사용한다 (pthread_create() 호출). 예를 들어 logger 태스크는 메인 구동 루프와는 별개로 SD 카드에 무식하게 버퍼를 밀어 넣기 위한 writer_thread 를 별도로 파생(Fork)시켜 쓴다. 태스크 내부의 힙 메모리와 전역 변수를 그대로 공유하므로 자원 접근 락(Lock) 관리에 실패하면 즉각 데이터 크러시가 터진다.

끝나지 않는 무한 루프: 워커 스레드(Worker Thread) 철학
대부분의 핵심 PX4 모듈 내부 진입점(module_main)을 열어보면, 99% 확률로 커다란 while(!_task_should_exit) 문장에 갇혀 영원히 무한 뺑뺑이를 도는 구조로 설계되어 있다.
그렇다면 백 개의 스레드가 무한 루프를 돌고 있는데 왜 CPU가 100% 한계에 도달해 뻗지 않는가?
그 비밀은 바로 블로킹(Blocking) 설계를 통한 자의적 양보(Yield) 기법에 있다.
ekf2 의 극악한 루프 한가운데에는 십중팔구 센서 유도탄 구독 블로킹인 orb_poll(...) 이나 px4_sem_wait(...) 이 박혀있다. “새로운 IMU 센서값이 들어올 때까지 내 스레드를 잠재워주세요!“라고 커널에게 요청하는 것이다.
이 함수를 만나는 순간, NuttX 스케줄러는 ekf2의 레지스터 상태를 RAM에 싸서 대기열(Wait Queue) 장롱에 밀어 넣고(Sleep 상태 전환), 다른 권한이 높은 모듈에게 남는 CPU 자원을 미련 없이 던져버린다. IMU가 새 데이터를 뿌려 인터럽트가 터지면 스케줄러는 다시 ekf2를 장롱에서 꺼내 깨워버린다.
이 정교한 잠재우기(Sleep)와 깨우기(Wake-up)의 리듬, 즉 블로킹 패러다임 로드를 이해하는 자만이 과열 없이 수백 헤르츠의 고속 멀티 태스킹 제어 로직을 창조해 낼 수 있는 것이다.