사전 단편화
개념

단편화

gabury1고친 사람 github-actions[bot]

단편화는 이어져 있던 하나를 여러 조각으로 쪼갭니다. 무엇이 쪼개지느냐에 따라 이 말이 가리키는 일이 갈립니다. 네트워크에서는 큰 데이터 덩이를 길에 맞게 잘라 보내는 일을 부릅니다. 메모리에서는 빈 공간이 잘게 흩어져 큰 덩이를 못 내주게 된 상태를 부릅니다.

쉽고 빠른 이해

무슨 일을 하는 물건인가 — 이어진 하나가 여러 조각으로 나뉘는 일입니다. 큰 덩이를 그대로 못 지나가게 하는 길을 만나면 보내는 쪽이 그것을 조각으로 잘라 보냅니다. 메모리를 얻고 돌려주기를 오래 하면 빈 공간이 잘게 흩어집니다. 앞쪽은 일부러 하는 일이고 뒤쪽은 저절로 생기는 상태입니다.

왜 이렇게 하나 — 길마다 한 번에 실어 나를 수 있는 크기에 상한이 있습니다. 상한보다 큰 덩이는 자르지 않으면 못 지나갑니다. 메모리에서는 원해서 생기는 것이 아닙니다. 크기가 다른 요청을 받고 돌려받다 보면 빈 곳이 잘게 흩어집니다. 남은 양을 다 더하면 넉넉한데 이어진 큰 덩이는 못 내줍니다.

어떻게 도나

  1. 보내는 쪽이 상한에 맞춰 덩이를 자르고, 조각마다 어느 덩이의 몇 번째 조각인지를 붙입니다
  2. 받는 쪽이 그 표시를 보고 조각을 원래 순서대로 다시 잇습니다
  3. 메모리에서는 아무도 자르지 않습니다. 쓰고 돌려주기를 오래 하는 동안 빈 곳이 저절로 조각납니다

대가 — 네트워크에서는 조각 하나만 사라져도 원래 덩이를 못 되살려 처음부터 다시 받아야 합니다. 메모리에서는 남은 양이 넉넉한데도 할당이 실패합니다.

상세

큰 장롱은 문을 못 지나갑니다. 그래서 널빤지로 나누어 들여놓고 방 안에서 다시 맞춥니다. 그렇게 짐을 넣고 빼기를 오래 하다 보면 방 여기저기에 작은 빈 틈만 남아, 통짜 장롱은 더 들여놓을 곳이 없어집니다.

단편화는 이 두 가지를 한 낱말로 부릅니다. 하나는 지나가게 하려고 일부러 나누는 일입니다. 다른 하나는 넣고 빼기를 오래 한 끝에 남은 흩어진 상태입니다.

두 뜻 모두 「이어진 하나가 조각으로 나뉘었다」는 점은 같습니다. 갈리는 것은 누가 나누었나와 그것이 원하던 일인가입니다.

뜻이 갈리는 세 맥락

세 맥락에서 이 말을 씁니다. 무엇이 나뉘는지와 누가 나누는지를 보면 어느 뜻인지 갈립니다.

맥락 나뉘는 것 누가 나누나 원해서 하나
네트워크 보내려는 데이터 덩이 보내는 쪽이나 중간 장비 그렇습니다. 길을 지나가려고 자릅니다
메모리 아직 안 쓴 빈 공간 아무도 안 나눕니다 아닙니다. 쓰고 돌려받다 생깁니다
디스크 파일이 담긴 블록 아무도 안 나눕니다 아닙니다. 지우고 쓰다 흩어집니다

첫 줄만 동작이고 나머지 둘은 상태입니다. 그래서 같은 낱말인데도 「단편화한다」와 「단편화가 심하다」가 둘 다 말이 됩니다.

네트워크의 단편화

선 하나가 한 번에 실어 나를 수 있는 덩이의 크기에는 상한이 있습니다. 이 상한이 MTU(Maximum Transmission Unit, 최대 전송 단위)입니다. 상한은 선의 종류마다 다르고, 한 덩이가 목적지까지 가는 동안 여러 선을 갈아탑니다.

IP(Internet Protocol, 인터넷 프로토콜)가 한 번에 나르는 덩이 하나를 데이터그램이라고 부릅니다. 상한보다 큰 데이터그램을 만나면 IP 가 그것을 여러 조각으로 자릅니다. 이 자르는 일이 네트워크에서 말하는 단편화입니다.

조각마다 원래 덩이의 헤더를 본떠 붙입니다. 헤더는 덩이 앞에 붙어 어디서 어디로 가는지를 적어 둔 부분입니다. 그래서 조각 하나하나가 혼자서도 길을 찾아갈 수 있는 온전한 패킷이 됩니다.

본떠 붙는 것은 길을 찾는 데 쓰는 바깥 헤더뿐입니다. 알맹이 맨 앞에 적혀 있는 것들은 본뜨지 않습니다. 그래서 그것들은 첫 조각에만 실립니다.

조각을 도로 잇는 쪽은 목적지뿐입니다. 이 되돌리는 일을 재조립이라고 합니다. 중간 장비가 안 잇는 까닭은 조각들이 서로 다른 길로 갈 수 있어서, 한 장비에 조각이 다 모인다는 보장이 없기 때문입니다.

잇는 데 필요한 표시는 조각의 바깥 헤더에 붙습니다. 셋입니다.

  • 식별자 — 어느 덩이에서 나온 조각인가. 한 덩이에서 나온 조각은 모두 같은 값을 답니다
  • 오프셋 — 원래 덩이의 앞에서 몇 번째 바이트부터인가
  • 다음 있음 표시 — 뒤에 조각이 더 있는가. 마지막 조각만 이 표시가 없습니다

세 표시가 조각마다 어떤 값으로 붙는지를 그림으로 보입니다.

block-beta
columns 3
  t["원래 덩이 — 앞에서부터 바이트가 이어진다"]:3
  f1["조각 1"] f2["조각 2"] f3["조각 3"]
  o1["오프셋 · 맨 앞부터"] o2["오프셋 · 조각 1 이 끝난 곳부터"] o3["오프셋 · 조각 2 가 끝난 곳부터"]
  m1["다음 있음"] m2["다음 있음"] m3["다음 없음"]
  i["식별자 — 세 조각이 모두 같은 값"]:3

여기서 읽어 낼 것은 오프셋의 정체입니다. 오프셋은 몇 번째 조각인지를 세는 순번이 아니라 원래 덩이 안의 바이트 위치입니다. 목적지는 그 값을 보고 조각을 제 위치에 끼워 넣습니다.

IP 에는 쓰이는 판이 둘 있습니다. IPv4와 IPv6이고, 누가 자르는지가 그 둘에서 갈립니다.

IPv4 에서는 보내는 쪽뿐 아니라 지나가는 길의 라우터도 자를 수 있습니다. IPv6 에서는 라우터가 자르지 않습니다. 출발지만 자르고, 라우터는 덩이가 너무 크다는 것을 보내는 쪽에 되돌려 알립니다.

IPv6 에서 큰 덩이가 어떻게 되돌아오는지를 그림으로 보입니다.

sequenceDiagram
    participant 출발지
    participant 라우터
    participant 목적지
    출발지->>라우터: 상한보다 큰 덩이
    Note over 라우터: 자르지 않고 여기서 버린다
    라우터-->>출발지: 너무 크다 · 이 상한에 맞춰라
    출발지->>라우터: 상한에 맞춰 자른 조각들
    라우터->>목적지: 그대로 지나보낸다

되돌아오는 화살표가 IPv6 에만 있습니다. IPv4 는 이 왕복이 없고 라우터가 바로 잘라 보냅니다.

조각을 잃었을 때 치르는 대가

자르는 것은 공짜가 아닙니다. 조각 가운데 하나만 사라져도 목적지는 원래 덩이를 못 되살립니다. 남은 조각들은 시간이 지나면 버려집니다. 보내는 쪽은 덩이 전체를 다시 보내야 합니다.

조각마다 헤더가 붙는 것도 비용입니다. 같은 알맹이를 나르는데 헤더는 조각 수만큼 늘어납니다. 선으로 나가는 양이 그만큼 많아집니다.

중간 조각들은 다루기도 까다롭습니다. 한 기계는 여러 상대와 동시에 주고받으므로, 들어온 덩이가 그중 어느 갈래에 속하는지를 갈라야 합니다. 그 갈래를 가리키는 번호가 포트입니다.

포트 번호는 알맹이 맨 앞에 한 번만 들어 있습니다. 그래서 첫 조각만 그것을 품고, 뒤 조각만 보고는 어느 갈래에 속한 것인지 알 수 없습니다. 포트를 보고 통과와 차단을 정하는 장비가 뒤 조각을 제대로 못 가려내는 까닭입니다.

그래서 요즘 설계는 중간에서 자르는 대신 처음부터 상한에 맞춰 보냅니다. 목적지까지 가는 길에서 가장 작은 상한을 미리 알아내는 일이 경로 MTU 탐색입니다.

메모리의 단편화

힙은 프로그램이 도는 동안 필요한 만큼 얻어다 쓰는 메모리 영역입니다. 프로그램은 필요할 때마다 여기서 메모리를 얻고 다 쓰면 돌려줍니다.

얻는 크기는 저마다 다르고 돌려주는 순서도 얻은 순서와 다릅니다. 이 일을 오래 하면 쓰는 덩이 사이사이에 빈 틈이 흩어집니다.

빈 틈이 흩어지기까지를 세 단계로 보입니다.

flowchart TD
    subgraph S1["1 · 다섯 덩이를 차례로 내준다"]
        direction TB
        a1["A"] --- b1["B"] --- c1["C"] --- d1["D"] --- e1["E"]
    end
    subgraph S2["2 · B 를 돌려받는다"]
        direction TB
        a2["A"] --- b2["빈 틈"] --- c2["C"] --- d2["D"] --- e2["E"]
    end
    subgraph S3["3 · D 도 돌려받는다"]
        direction TB
        a3["A"] --- b3["빈 틈"] --- c3["C"] --- d3["빈 틈"] --- e3["E"]
    end
    S1 --> S2 --> S3

돌려받은 순서가 내준 순서와 달라서 빈 틈이 가운데에 생깁니다. 그리고 그 틈들 사이에 아직 쓰는 덩이가 남아 있어 둘을 하나로 합칠 수 없습니다.

이 상태가 메모리의 단편화입니다. 아무도 나눈 적이 없는데도 빈 공간이 조각나 있습니다. 곤란한 것은 남은 양이 아니라 모양입니다.

앞 그림의 마지막 단계를 크기와 함께 다시 놓아 봅니다.

block-beta
columns 1
  a["쓰는 중 · A"]
  b["빈 틈 · 24바이트"]
  c["쓰는 중 · C"]
  d["빈 틈 · 32바이트"]
  e["쓰는 중 · E"]

이 상태에서 48바이트를 이어진 한 덩이로 달라는 요청이 들어오면 내줄 곳이 없습니다.

빈 틈을 다 더하면 56바이트로 요청보다 많습니다. 그런데 이어진 가장 큰 틈은 32바이트입니다. 메모리가 모자라다는 오류를 만났을 때 남은 양이 넉넉해 보인다면, 대개 이 모양이 이유입니다.

외부 단편화와 내부 단편화

같은 메모리 안에서도 두 가지를 갈라 부릅니다. 앞서 본 것은 빈 공간이 내준 덩이 바깥에 흩어진 것이라 외부 단편화입니다.

내부 단편화는 반대로 내준 덩이 안에서 남는 것입니다. 메모리를 나눠 주고 돌려받는 할당자는 정해진 크기 단위로만 내주는 일이 많습니다. 그래서 단위보다 조금 큰 요청은 단위 하나를 더 받고, 그 덩이의 끝에 남는 부분은 아무도 못 씁니다.

두 낱말을 가르는 것은 빈 곳이 어디에 남느냐입니다. 그 두 위치를 그림으로 보입니다.

block-beta
columns 4
  l1["외부 단편화"] u1["내준 덩이"] g1["빈 틈"] u2["내준 덩이"]
  l2["내부 단편화"] b1["내준 덩이 하나 · 쓰는 부분"]:2 b2["남는 부분"]

윗줄은 빈 곳이 덩이 바깥에 있고 아랫줄은 덩이 안에 있습니다. 이름이 외부와 내부로 갈린 까닭입니다.

남는 곳 왜 생기나 어떻게 보이나
외부 단편화 내준 덩이 사이 크기가 다른 요청과 반납 빈 양은 넉넉한데 큰 요청이 실패합니다
내부 단편화 내준 덩이 안 정해진 단위로 올려서 내주기 쓰는 양보다 많이 잡힙니다

가상 메모리를 쓰는 시스템은 메모리를 페이지 단위로 다룹니다. 페이지는 메모리를 같은 크기로 잘라 놓은 칸입니다.

칸의 크기가 모두 같으면 빈 페이지는 어느 것이든 서로 대신할 수 있습니다. 흩어져 있어도 상관없으므로 외부 단편화가 크게 줄어듭니다.

대신 요청 하나마다 페이지 끝에 남는 부분이 생깁니다. 외부 단편화를 내부 단편화와 맞바꾼 것입니다.

흩어진 것을 되돌리는 방법

메모리에서는 이미 흩어진 것을 다시 모으거나, 애초에 안 흩어지게 나눠 줍니다. 네트워크에서는 아예 자르지 않는 길로 갑니다. 셋을 차례로 봅니다.

쓰는 덩이를 한쪽으로 몰아붙여 빈 곳을 하나로 잇는 방법이 있습니다. 이렇게 옮겨 모으는 일이 컴팩션입니다. 앞 절의 흩어진 띠가 어떻게 바뀌는지를 그림으로 보입니다.

flowchart TD
    subgraph B1["옮기기 전"]
        direction TB
        a1["A"] --- g1["빈 틈 · 24바이트"] --- c1["C"] --- g2["빈 틈 · 32바이트"] --- e1["E"]
    end
    subgraph B2["옮긴 뒤"]
        direction TB
        a2["A"] --- c2["C"] --- e2["E"] --- g3["이어진 빈 곳 · 56바이트"]
    end
    B1 --> B2

떨어져 있던 두 틈이 한 덩이가 되어 48바이트 요청을 받을 수 있게 됩니다. 대신 옮긴 덩이는 주소가 바뀌므로 그것을 가리키던 곳을 전부 고쳐야 합니다. 고치는 동안 프로그램이 멈춥니다.

이 방법은 가비지 컬렉션을 하는 런타임에서 주로 씁니다. 어디에서 누가 그 덩이를 가리키는지 이미 알고 있어서 주소를 고칠 수 있기 때문입니다. 반대로 주소를 그대로 들고 다니는 프로그램에서는 덩이를 함부로 못 옮깁니다.

옮기지 못하면 애초에 흩어지지 않게 나눠 줍니다. 같은 크기끼리 모아 두고 그 크기 요청만 받으면, 돌려받은 곳에 같은 크기가 그대로 다시 들어갑니다. 슬랩 할당자와 버디 할당자, 메모리 풀이 이 방식입니다.

네트워크에서 되돌리는 방법은 자르지 않는 것입니다. 앞서 본 경로 탐색으로 길에서 가장 작은 상한을 알아냅니다. 보내는 쪽이 처음부터 그 크기에 맞춰 덩이를 만들면 중간에서 잘린 조각을 다룰 일이 없어집니다.

디스크와 파일 시스템의 단편화

파일도 한 덩이로 놓이지 않습니다. 파일 시스템은 파일을 블록이라는 단위로 쪼개 저장 장치에 놓습니다. 파일을 지우고 새로 쓰기를 오래 하면 한 파일의 블록들이 여기저기 떨어져 앉습니다.

이 상태에서 파일을 처음부터 끝까지 읽으면 읽는 곳이 계속 건너뜁니다. 회전하는 원판에 바늘을 옮겨 가며 읽는 장치에서는 이 건너뜀이 곧 기다림입니다. 흩어진 블록을 모아 다시 붙여 놓는 조각 모음이 그래서 나왔습니다.

SSD(Solid State Drive, 반도체 저장 장치)에는 옮기는 바늘이 없어 건너뛰는 비용이 거의 없습니다. 대신 조각 모음은 쓰기를 잔뜩 일으켜 수명만 깎으므로 하지 않습니다.

어느 뜻인지 가르는 단서

글이나 대화에서 이 낱말을 만나면 함께 나온 말을 봅니다. 대개 그것만으로 어느 뜻인지 갈립니다.

함께 나오는 말 뜻
상한 · 조각 · 재조립 · 라우터 네트워크
힙 · 할당 · 해제 · 할당 실패 메모리
파일 · 블록 · 읽기 속도 디스크

이름이 비슷해 헷갈리는 이웃도 둘 있습니다. 잘린 조각 하나를 가리키는 말은 프래그먼트입니다. 단편화는 그 조각을 만드는 일이나 조각난 상태를 가리킵니다.

그리고 같은 기술이 서로 안 맞는 여러 갈래로 갈린 상태는 파편화라는 다른 낱말로 부릅니다.

관련 항목

단편화된 조각이 오가는 네트워크 계층의 구성 요소

IP · IPv4 · IPv6 · 패킷 · 데이터그램 · 라우터 · 링크 계층 · 페이로드 · 캡슐화

자를지 말지를 정하는 크기 상한과 탐색 절차

MTU · MSS · 경로 MTU 탐색 · MSS 클램핑 · 최소 링크 MTU · 점보 프레임 · Packet Too Big

조각에 붙는 헤더 필드와 조각을 되돌리는 처리

재조립 · 프래그먼트 · 프래그먼트 오프셋 · 프래그먼트 식별자 · DF 비트 · MF 비트 · 단편 헤더

메모리를 나눠 주고 돌려받는 장치

힙 · 메모리 할당자 · 슬랩 할당자 · 버디 할당자 · 메모리 풀 · 아레나 할당 · 가비지 컬렉션 · 가상 메모리 · 페이지

흩어진 메모리를 다시 모으는 수단

컴팩션 · 마크 앤 컴팩트 · 복사 수집 · 재배치 · 핸들 · 고정

단편화가 심해졌을 때 나는 장애

메모리 단편화 · 할당 실패 · OutOfMemoryError · 메모리 부족 · 메모리 누수 · 스래싱 · 패킷 손실 · 재전송

맥락마다 이 낱말이 붙는 대상

외부 단편화 · 내부 단편화 · 힙 단편화 · 디스크 단편화 · 단편화와 재조립 · 파편화

파일 조각이 흩어지는 저장 장치 구조

파일 시스템 · 블록 · 익스텐트 · 조각 모음 · 순차 읽기 · SSD · 웨어 레벨링

통짜를 잘라 낸 조각을 가리키는 다른 이름

세그먼트 · 청크 · 프레임 · 슬랩 · 스트라이프 · 샤드

다른 이름: fragmentation