9.1.2.2 가비지 컬렉터(GC) 부재 환경의 수동 메모리 라이프사이클 통제 시스템

9.1.2.2 가비지 컬렉터(GC) 부재 환경의 수동 메모리 라이프사이클 통제 시스템

고수준 프로그래밍 언어의 가장 큰 수혜는 가비지 컬렉터(Garbage Collector, GC)가 백그라운드에서 불필요한 메모리를 수거해준다는 점이다. 그러나 초저지연, 초소형 마이크로스케일(Micro-scale) 장비 위에서 동작하는 zenoh-c 혹은 zenoh-pico 환경은 메모리 풀이 수 킬로바이트(KB) 남짓한 하드코어 C 언어 생태계다. 이곳에는 개발자의 뒤를 닦아줄 어떠한 런타임 수집기도 존재하지 않는다.

통신망을 오고가는 수만 개의 패킷이 메모리에 잔존할 때, 단 한 번의 해제(Free) 누락은 수 초 만에 시스템 메모리 고갈(Out-Of-Memory, OOM) 사태를 일으킨다. 본 절에서는 GC 부재 환경에서 Zenoh C 클라이언트를 다룰 때 반드시 사수해야 하는 수동 메모리 라이프사이클 제어와 정밀한 소유권 이전(Ownership Transfer) 아키텍처를 전개한다.

1. 묵시적 컴파일러 소유권의 C 언어 패턴 매핑

Rust 언어에 뿌리를 둔 Zenoh의 아키텍처는 C 언어용 래퍼 함수(zenoh-c)를 제공하더라도 그 이면에 러스트 특유의 소유권(Ownership) 정책을 그림자처럼 강제한다. C 개발자는 이를 인지하지 못하면 이중 해제(Double Free)나 접근 위반(Access Violation)에 시달리게 된다.

Zenoh C API에서 변수 생명주기는 함수 명명법(Naming Convention)에 의해 결정된다. 가장 중요한 원칙은 생성(alloc/open/declare)된 모든 식별자(Handle)는 반드시 소멸(z_drop, undeclare, close)되어야 한다는 점이다.

// 1. 세션의 개방 및 구조체 할당
z_session_t session;
if (z_open(&session, config, NULL) < 0) {
    printf("세션 개방 실패\n");
    return;
}

// 2. 메시지 수신 버퍼 또는 설정 정보(Config)의 수동 제어
z_owned_config_t config = z_config_default();
// ... 설정 사용 후, 명시적으로 메모리를 해제하는 권한 반납 명령
z_config_drop(&config);

이처럼 변수 이름에 owned가 붙어 있으면 힙(Heap) 영역 시스템 메모리가 할당되었음을 의미한다. 개발자는 이 변수를 사용한 후 반드시 관련된 _drop() 핸들러를 호출하여 스스로 쓰레기 수집기의 역할을 수행해야 한다.

2. 수신 콜백(Callback) 구조에서의 Zero-Copy 포인터 제약

수동 메모리 관리의 난이도가 극한으로 치닫는 구간은 외부에서 유입된 네트워크 페이로드(Payload)를 다루는 수신 콜백 구간이다.
마이크로컨트롤러에서는 메모리 복사 자체를 불허해야 하므로, 수신된 데이터(z_sample_t)의 페이로드 바이트 어레이는 Zenoh의 코어 버퍼 캐시를 가리키는 참조 포인터(Reference Pointer) 로만 노출된다.

void on_telemetry_received(const z_sample_t *sample, void *context) {
    // sample->payload.start 포인터는 수신 스레드 링 버퍼의 주소를 가리킴 
    uint8_t* raw_data = sample->payload.start;
    size_t length = sample->payload.len;

    // 즉각적인 데이터 파싱(Parsing) 및 연산 진행
    execute_control_algorithm(raw_data, length);

    // [경고] raw_data를 전역 큐(Queue)에 포인터 형태로 저장하는 것은 절대 금지
    // 콜백 함수가 종료되면 이 영역은 다른 네트워크 트래픽에 의해 덮어씌워짐(Overwritten).
}

이 구조체 포인터들은 콜백 함수 스코프(Scope)를 벗어나는 즉시 메모리 생명주기를 상실한다. 만약 개발자가 인터럽트를 늦추기 위해 비동기 큐에 이 포인터만 적재한 뒤 콜백을 탈출해버리면, 이후 백그라운드 태스크는 이미 덮어씌워진 오염된 센서값(Garbage Data)을 읽고 로봇을 오작동시킬 것이다. 따라서 GC 없는 시스템에서는 콜백 내에서 모든 처리를 동기적으로 완료하거나, 지연 처리가 불가피한 경우에 한해서만 개발자가 선행 할당해둔 정적 버퍼(Static Buffer) 배열에 값을 깊은 복사(Deep Copy)한 뒤 리턴해야 한다.

3. 정적 메모리 풀(Memory Pool)과 데드 패킷 마크(Dead Packet Mark)

통제 불능의 힙 할당을 방지하기 위해 센서 퓨전 단말에서는 운영체제의 malloc을 전면 봉쇄하고 자체적인 고정 길이 메모리 풀(Static Memory Pool)을 개설해야 한다.
발행(Publisher)을 위해 데이터를 전송할 때에도, 사전에 .bss 영역에 선언해둔 옥텟(Octet) 배열만을 재활용하라.

전송이 완료된 버퍼 공간은 is_occupied 상태 플래그를 0으로 전환(Dead Packet Mark)하여, 다음 센서 데이터 수집 주기에 곧바로 엎어칠 수 있도록 상태 머신을 통제하라. 이러한 수동적 생존 주권 통제(Sovereign Lifecycle Control)의 집념만이, 메모리 256KB의 볼품없는 단일 칩 아두이노(Arduino) 코어조차도 글로벌 Zenoh 통신망의 정식 피어(Peer) 노드로 10년 이상 재부팅 없이 구동시키는 핵심 미학(Aesthetics)이다.