운영체제 아주 쉬운 세가지 이야기 7주차 (빈 공간 관리)

2025. 10. 21. 16:33개념 공부

메모리 단편화 최적화 총정리 — Buddy Allocator까지 한 번에

malloc/free 힙 관리에서 단편화를 줄이는 핵심 설계들과 Buddy 알고리즘의 작동 원리/구현 포인트를 정리합니다.

1) 단편화(내부/외부) 한눈에 이해

  • 외부 단편화: 빈 공간 총합은 충분한데 연속 블록이 부족해서 큰 할당이 실패하는 현상.
  • 내부 단편화: 요청보다 큰 블록을 제공하거나 정렬/헤더 때문에 블록 내부에서 낭비되는 공간.
예) [free 10][used 8][free 10]  → 총 free=20, but 16바이트 연속 블록이 없어 실패

2) 단편화 최적화 툴킷

  • Split: 큰 free 블록을 잘라 요청 크기만큼 할당, 나머지는 free로 유지.
  • Coalesce: free 시 인접 free 블록을 병합해 큰 블록 유지.
  • 헤더/바운더리 태그: 블록 앞/뒤에 size, alloc 비트 저장 → 인접 블록 상태 확인, O(1) 병합.
  • 주소순 free-list: free-list를 주소순으로 유지하면 병합 판단이 단순해짐.
  • 크기별 분리 리스트 (segregated): 자주 쓰는 크기는 전용 리스트로 탐색/파편화 감소.
  • 고정 크기 캐시 (slab): 동일 타입 객체를 고정 크기 캐시에서 관리 → 내부 단편화/초기화 비용 절감.
  • Buddy: 2^k 크기만 사용해 split/merge를 규칙적으로 수행 → 외부 단편화 억제 & 빠른 병합.

3) Segregated-fit / Slab

Segregated-fit

크기 클래스를 미리 정의(예: 8,16,32,64, …)하여 크기별 free-list로 관리. 탐색 범위가 작아져 빠르고, 자주 쓰는 크기에서 외부 단편화가 줄어듭니다. 남는 부분을 split해 인접 클래스로 넘기면 내부 단편화도 일정 수준으로 제한됩니다.

Slab (고정 크기 객체)

커널/서버에서 특정 구조체를 반복적으로 할당/해제할 때 유리. 슬랩(페이지 단위) 안을 동일 크기의 슬롯들로 나누고, 초기화 상태를 캐시하여 할당/해제의 오버헤드와 내부 단편화를 낮춥니다.

[Slab: 1 page]
| obj | obj | obj | obj | ... | → 동일 크기 슬롯, 빠른 할당/해제, CPU 캐시 친화
    

4) Best/First/Next-fit + Coalesce 전략

  • First-fit: 처음 만난 충분히 큰 블록. 빠르지만 초반부가 조각날 수 있음.
  • Next-fit: 이전 탐색 위치 이후부터 검색. 파편 분산 기대.
  • Best-fit: 가장 근접한 크기의 블록. 내부 단편화 감소 기대, 탐색 비용↑, 작은 파편 가능.

코어 포인트: 어떤 fit을 써도 coalesce 정책(즉시/지연), free-list 정렬(주소/크기), boundary tag의 유무가 단편화/성능에 큰 영향을 줍니다.

5) Buddy Allocator 깊게 보기

5-1) 핵심 아이디어

  • 블록 크기를 2^k로만 유지. (최소 주문 order_min, 최대 주문 order_max)
  • 할당은 요구 크기 이상인 가장 작은 2^k 블록으로 round-up 후, 맞는 크기가 나올 때까지 반으로 분할(split).
  • 해제는 같은 order의 버디가 free면 즉시 병합(merge)하여 상위 order로 큼지막하게 만든다.

5-2) 버디 찾기 (주소 XOR 규칙)

베이스 주소를 0으로 가정하면, order k 블록의 크기는 size = 1 << k이고, 한 블록의 시작 주소 addr에 대해 버디는:

buddy = addr XOR size  (또는 buddy = addr ^ (1 << k))

즉, 해당 크기 비트(2^k 자리)를 토글하면 이웃 버디의 시작 주소가 됩니다. 시작 주소가 그 크기 배수인지(정렬) 확인이 중요합니다.

5-3) 자료구조

  • free-lists[order]: 각 order별로 이중 연결 리스트(또는 단순 리스트)로 free 블록을 관리.
  • 각 free 노드는 블록 시작 주소만 있으면 충분(헤더에 size/order와 링크 포인터 저장).
  • O(1)에 수록/삭제가 가능하고, 버디 주소 계산으로 병합 여부 판단도 O(1).

5-4) 할당 절차 (의사코드)

// 요청 n 바이트
n' = roundup_pow2(max(n + header, MIN_BLOCK))     // 2^k 반올림
k = order_of(n')

for o in range(k, MAX_ORDER+1):
    if free_list[o] not empty:
        // o에서 블록 하나 꺼내고 k가 될 때까지 반으로 쪼갠다
        blk = pop(free_list[o])
        while o > k:
            o -= 1
            split = blk + (1 << o)  // 절반 주소
            push(free_list[o], split) // 한 쪽은 free-list에 반환
            // blk는 남겨 계속 쪼갬
        return blk + header_size
// 여기까지 없으면 힙 확장 or 실패
    

5-5) 해제 절차 (의사코드)

blk = user_ptr - header_size
o = order_of(blk)      // 헤더에서 order 읽기
addr = start_of(blk)

while o <= MAX_ORDER:
    buddy = addr ^ (1 << o)
    if buddy is free in free_list[o]:
        // buddy 제거 후 병합
        remove(free_list[o], buddy)
        addr = min(addr, buddy)
        o += 1         // 상위 order로 승격
    else:
        break
push(free_list[o], addr)
    

5-6) 예시 (간단 시각화)

전체 풀: 2^4 = 16바이트 (order=4)
[16]

8바이트 요청 → 8로 round-up
[8][8]   // split 한 번
 <- 할당

다시 8바이트 free → 인접 buddy(다른 [8])도 free면 merge:
[16]     // 재병합 성공
    

5-7) 장단점

장점 단점
O(1)에 가까운 빠른 split/merge (버디 계산, 주소 정렬) 2^k 제한으로 내부 단편화 증가(최대 낭비 < 2배)
병합(코얼레스)이 단순하여 장기 외부 단편화 억제 요청 분포가 “애매한 크기”에 몰리면 효율 저하
크기별 free-list로 탐색 비용 낮음 헤더/메타데이터, 정렬 제약으로 약간의 오버헤드

5-8) 내부 단편화 상한

요청 크기 n을 2^k로 반올림할 때, 낭비는 < 2^k − n. 최악 비율은 거의 50% 부근까지 나올 수 있습니다. 단, 실제 워크로드에서 자주 쓰는 크기를 별도 segregated로 빼거나, 작은 크기만 buddy에 태우는 식으로 보완합니다.

5-9) 변형/개선

  • Hybrid: 소형 크기는 segregated-list/슬랩, 중대형은 buddy.
  • Lazy coalesce: 즉시 병합 대신 필요할 때 병합해 캐시 지역성↑.
  • Bitmap buddy: free-list 대신 비트맵으로 상태 관리(캐시 친화/간결).

6) Buddy vs Segregated vs 일반 Free-list

기법 탐색 속도 외부 단편화 내부 단편화 베스트 케이스 워스트/주의
Buddy 빠름 (O(1) 수준) 낮음(병합 쉬움) 높을 수 있음 (2^k 제약) 다양 크기 균형, 병합 잦은 워크로드 요청이 2^k 경계에 애매하면 낭비↑
Segregated/Slab 매우 빠름 작은 크기에서 매우 낮음 작은 고정 크기는 낮음 자주 쓰는 크기가 뚜렷함 희귀 크기 분포엔 별도 백업 전략 필요
Free-list(First/Best-fit) 중간~느림(탐색 비용 큼) coalesce에 따라 좌우 클래스 없으니 중간 간단한 구현, 다양한 크기 유연 탐색/병합 정책 미흡 시 장기 파편화

7) 구현 체크리스트

  • 헤더: size/order, alloc 비트, (옵션) 가드(magic)/체크섬.
  • 정렬: 페이지/캐시라인/아키텍처 정렬 요구를 만족.
  • coalesce: boundary tag 또는 buddy XOR 규칙으로 O(1) 병합.
  • zeroing/INIT: 보안/안정성 요구 시 할당/해제 시 초기화 정책.
  • 성능 계측: 평균/최대 탐색 길이, split/merge 횟수, 내부/외부 단편화 지표 추적.
  • 하이브리드: 소형(슬랩/segregated) + 중대형(buddy/페이지) 조합 고려.
  • 확장/반납: 메모리 부족 시 힙 확장(sbrk/mmap), 장기 유휴 블록은 OS 반납(munmap) 여부.
// Buddy에서 자주 쓰는 계산 요약
size(k)   = 1 << k
order(n)  = ceil_log2( max(n + header, MIN_BLOCK) )
buddy(a,k)= a ^ (1 << k)
merge 조건: (buddy free) && (둘 다 같은 order) → 상위 order로 승격
    

Tip: 실제 서비스에선 “요청 분포”에 맞춘 하이브리드 구성이 가장 효과적입니다. 작은 객체는 슬랩/segregated, 큰 요청은 buddy/페이지로 보내 외부 단편화와 탐색 비용을 동시에 줄이세요.