ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [번역/요약] C언어 메모리 부족(OOM) 상황 처리 방법
    Programming/번역&요약 2024. 11. 24. 19:18

    Eli Bendersky의 'Handling Out of Memory Conditions in C' 글을 번역하고 정리했습니다. C언어에서 malloc이 실패하는 OOM 상황의 세 가지 처리 정책(복구/중단/Segfault)과 Glib, SQLite, Git 등 실제 프로젝트들의 구현 사례를 살펴봅니다."

    원문: Handling Out of Memory Conditions in C

    서론

    • malloc 함수의 반환값 0(NULL)은 메모리 할당 실패(Out of Memory, OOM)를 의미함
      • 애플리케이션은 이를 발견하고 적절하게 "처리"해야 하나, 어떻게 처리해야 하는지는 명확하지 않음
    • 글은 실제 자주 사용되는 OOM 처리 방법들을 분석하고 통찰을 제공하고자 함
      • OOM 처리 방법에 "정답"이란 없으며, 글은 임베디드가 아닌 데스크탑/서버 환경의 프로그램에 초점을 둠

    OOM 처리 방법

    OOM 처리 방법에는 크게 세 가지 처리 정책이 있음

    1. 복구(Recover) 정책
    2. 중단(Abort) 정책
    3. Segmentation Fault 정책

    복구(Recover) 정책

    • 도메인에 특화된 구현이 필요하고 구현 방법이 어려워 자주 사용되지는 않음
    • 프로그램 복구란 주로 다음 중 하나를 의미함
      • 메모리 자원 해제 후 재시도
      • 사용자 작업 저장 후 종료
      • 임시 리소스 정리 후 종료
    • 구현이 어려운 이유는 다음과 같음
      • 추가 메모리 할당 없이 복구 과정을 수행해야 함
      • C언어는 예외 처리(Exception) 기능이 없어 오류 전파가 복잡하고 번거로움

    중단(Abort) 정책

    • 가장 일반적으로 사용하는 방식
    • OOM 발생 시 에러 메시지만 출력하고 프로그램 종료
    • 반환 값 검사(if (ptr == NULL))가 필요 없어 코드가 간결해짐
      • gnulibxmalloc: malloc 함수가 실패(OOM 발생) 시 프로그램을 종료하는 래퍼 함수(wrapper function)

    xmalloc 코드

    void *xmalloc(size_t size) {
        void *p = malloc(size);
        if (!p && size != 0) 
        {
            xalloc_die(); // Abort
        }
        return p;
    }

    Segmentation Fault 정책

    • 아무 것도 하지 않는 가장 단순한 방식
    • OOM으로 인해 생긴 NULL 포인터는 역참조 시 Segmentation Fault를 만들며 프로그램을 종료
      • 어차피 프로그램이 종료될 것이므로 OOM 처리를 할 필요가 없다는 이유로 사용
      • 에러 메시지 대신 코어 덤프를 활용해 문제 발생 시점을 파악

    주요 라이브러리 사례 분석

    Glib

    • Glib: 크로스 플랫폼 유틸리티 라이브러리
    • 두 개의 메모리 할당 함수(와 그 변형)를 제공, 프로그래머가 적절하게 선택할 수 있음
      • g_malloc: OOM 발생 시 프로그램 종료 (중단 정책)
      • g_try_malloc: OOM 발생 시 NULL 반환 (복구 정책)
    • 라이브러리 내부적으로는 주로 중단 정책을 사용함

    SQLite

    • SQLite: 내장형 경량 데이터베이스
    • 다양한 메모리 관리 옵션 사용
      • 일반 malloc
      • 미리 메모리를 할당해 둔 정적 버퍼
      • 디버깅용 메모리 할당자 (메모리 누수 및 오버플로우 검사 기능)
      • 사용자 정의 메모리 할당자 지원
    • 기본적으로는 복구 정책을 사용
      • 메모리 할당 크기를 할당한 블록의 맨 앞에 저장
        • 따라서 NULL 포인터는 메모리 할당 크기가 0인 블럭으로 해석됨
      • OOM이 발생해도 포인터를 그대로 반환

    SQLite의 sqlite3MemMalloc 코드

    static void *sqlite3MemMalloc(int nByte) 
    {
        sqlite3_int64 *p;
        assert(nByte > 0);
        nByte = ROUND8(nByte);
    
        p = malloc(nByte + 8);
        if (p) 
        {
            p[0] = nByte; // Size of the allocation
            p++;
        }
    
        return (void *)p;
    }

    주요 애플리케이션 사례 분석

    Git

    • Git: 버전 관리 시스템
    • 래퍼 함수 xmalloc 정의해 사용
      • OOM 발생 시 메모리 자원 해제 후 재시도 (복구 정책)
      • 재시도 후에도 OOM 발생 시 프로그램 중단 (중단 정책)

    Git의 xmalloc 코드

    void *xmalloc(size_t size)
    {
        void *ret = malloc(size);
        if (!ret && !size)
        {
            ret = malloc(1);
        }
    
        if (!ret)
        {
            release_pack_memory(size, -1);
            ret = malloc(size);
            if (!ret && !size)
            {
                ret = malloc(1);
            }
    
            if (!ret)
            {
                die("Out of memory, malloc failed");
            }
        }
    #ifdef XMALLOC_POISON
        memset(ret, 0xA5, size);
    #endif
        return ret;
    }

    Lighttpd

    • Lighttpd: 초경량 웹 서버
    • OOM 체크 없음, Segmentation Fault 정책 사용

    Redis

    • Redis: 키-값 데이터베이스
    • zmalloc 함수 사용
      • OOM 발생 시 NULL 반환, 애플리케이션에 처리 위임
      • 애플리케이션은 OOM 발생 시 프로그램 중단 (중단 정책)
        • 네트워크 레이어를 통해 클라이언트에게 에러 메시지를 전송하려면 메모리 할당이 필요
          • 따라서 OOM 발생 시에는 전송이 불가능하므로 그냥 프로그램을 종료하는 것이 낫다고 판단

    결론

    • 코드 작성 시 어떤 OOM 처리 방법을 사용할지는 프로그램의 특성과 사용자 기대에 따라 다름
    • 라이브러리 개발 시
      • 라이브러리가 프로그램 동작을 강제해서는 안 되므로 복구 정책을 사용하는 것이 바람직함
      • 가능하면 사용자 정의 메모리 할당자/에러 핸들러를 제공하면 좋음.
    • 애플리케이션 개발 시
      • 고신뢰성이 필요한 경우가 아니면 중단 정책을 권장
        • 고신뢰성이 필요한 C 애플리케이션을 만드는 실력 있는 개발자라면 이미 이런 글이 필요 없을 것
      • xmalloc같이 할당 함수를 감싸는 래퍼 함수를 정의하는 것을 추천
        • 반환 값 검사가 필요 없어 코드가 간결해짐
        • 소프트웨어가 고도화 된 이후 적절한 복구 전략을 추가하더라도 수정이 간편(Git의 사례)

    댓글