21.1.2.1.1. 개별 태스크 스택(Stack) 할당으로 인한 SRAM 메모리 파편화 및 고갈 문제

21.1.2.1.1. 개별 태스크 스택(Stack) 할당으로 인한 SRAM 메모리 파편화 및 고갈 문제

우리가 일상적으로 사용하는 컴퓨터의 램(RAM)은 16GB, 32GB에 육박하지만, 오리지널 픽스호크(FMU-v2/v3)나 최신 H7 칩셋조차도 가용할 수 있는 램(SRAM)의 총량은 고작 256KB에서 1MB 수준에 불과하다. 이 좁쌀만 한 메모리 공간 위에서 task_create() 방식의 독립 태스크 아키텍처는 그야말로 최악의 메모리 낭비를 유발하는 ’블랙홀’과 같았다.

1. 선불제(Pre-paid) 스택 할당의 비극

OS 커널(NuttX)이 새로운 태스크(스레드)를 하나 생성할 때 가장 먼저 하는 일은, 그 태스크가 평생 동안 함수 호출과 지역 변수(Local Variable)를 저장하는 데 쓸 스택(Stack) 메모리 덩어리를 힙(Heap) 공간에서 영구적으로 떼어주는 것이다.

  • 예를 들어, 개발자가 custom_app 모듈을 하나 만들면서 보수적으로 스택 크기를 2,000 바이트(2KB)로 설정했다고 가정해 보자.
  • 이 모듈이 실제로 일을 할 때 함수 내부에서 기껏해야 50 바이트 버퍼만 쓰고, 나머지 시간의 99%는 poll()로 잠들어 있다 하더라도, OS는 할당된 나머지 1,950 바이트를 다른 급한 모듈에게 절대 빌려주지 못한다.
  • 만약 이런 비효율적인 태스크가 GPS, 나침반, 로거, MAVLink, VIO 연동 모듈 등 50개가 넘게 띄워지게 되면, 실제로 사용되지도 않는 **‘텅 빈 스택 여백’**들이 수십 KB씩 낭비되면서 결국 전체 시스템의 힙(Heap) 메모리를 바닥내 버린다.

2. 메모리 파편화(Fragmentation)와 시한폭탄

더 심각한 문제는, 이런 독립 태스크들이 사용자의 명령이나 조종기 연결 상태에 따라 다목적으로 생성(Create)되었다가 소멸(Exit)되는 과정을 반복할 때 발생한다.

  • 4KB짜리 태스크 A가 죽어서 공간을 반납하고, 이어서 2KB짜리 태스크 B가 그 자리에 들어오면, 중간에 애매하게 2KB의 구멍(Hole)이 남게 된다.
  • 이런 생성과 소멸이 수천 번 반복되면, 픽스호크의 램 공간 안쪽은 그물망처럼 지저분하게 쪼개지는 메모리 파편화(Memory Fragmentation) 현상을 겪는다.
  • 이론적으로 힙 영역에 전체 10KB의 남은 용량이 존재하더라도, 그것들이 1KB씩 10 조각으로 흩어져 있다면, 새로운 EKF2 제어기가 2KB짜리 태스크를 띄우려 할 때 OS 커널은 연속된 메모리 덩어리를 찾지 못해 Out of Memory (OOM) 에러를 뿜으며 기체를 그 자리에서 다운시켜 버린다.

3. 최대치(Worst Case) 예측의 딜레마

task_create 환경에서는 개발자가 런타임에 발생할 ’함수 호출 깊이(Call depth 깊이)’와 ’지역 변수 배열 크기’의 최대치(Worst-case Scenario)를 미리 계산하여 스택 용량을 큼직하게 때려잡아야만(Over-provisioning) 하드 폴트(Stack Overflow)를 면할 수 있었다. 즉, 안전해지고 싶을수록 스택 크기를 무식하게 늘려야 했고, 이는 필연적으로 시스템 전체의 동시 실행 가능한 모듈 개수를 치명적으로 제한하는 결과를 낳았다.

바로 이 **‘낭비되는 스택 공간의 죄악’**을 회개하기 위해, 현대 PX4 아키텍처는 태스크들이 한 개의 거대한 스레드 스택을 돌려쓰게 만드는 Work Queue 패러다임을 발명해 낸 것이다. 뒤이어 21.1.2.1.2 단원에서는 공간의 낭비를 넘어 시간을 갉아먹는 ’문맥 교환(Context Switching)’의 폐해를 다룬다.