사전 MMU
개념

MMU

gabury1고친 사람 github-actions[bot]

MMU 는 프로그램이 부른 주소를 메모리의 실제 주소로 바꿔 줍니다. 프로그램이 메모리를 읽거나 쓸 때마다 이 바꾸기가 한 번씩 돕니다. 바꾸면서 그 프로그램이 건드려도 되는 곳인지도 같이 봅니다.

쉽고 빠른 이해

MMU 는 주소 바꾸기만 맡는 전담 부품입니다. 프로그램이 「내 100번 칸」이라고 부르면 그것이 메모리의 어느 칸인지 찾아 줍니다.

이 부품이 없으면 프로그램마다 메모리의 어느 구간을 쓸지 미리 갈라 두어야 합니다. 그러고도 한 프로그램이 주소를 잘못 쓰면 옆 프로그램의 메모리를 덮어씁니다.

어떻게 도는가:

  1. 프로그램이 부른 주소가 들어옵니다
  2. 운영체제가 만들어 둔 표를 보고 실제 주소를 찾습니다
  3. 찾으면서 읽기와 쓰기 권한도 봅니다. 어긋나면 운영체제에 알립니다

대가는 둘입니다. 표가 메모리에 놓여 있어서 주소를 바꿀 때마다 메모리를 더 읽어야 합니다. 그리고 그 표를 만들고 고치는 일이 운영체제의 몫으로 남습니다.

상세

큰 건물의 안내 데스크를 떠올리면 됩니다. 방문객은 「3층 회의실」이라고만 말합니다. 그곳이 실제로 몇 호인지는 데스크가 찾아 줍니다. 데스크는 안내하면서 방문증도 봅니다. 못 들어갈 곳이면 막습니다.

MMU(Memory Management Unit, 메모리 관리 장치)는 그 안내를 맡는 하드웨어입니다. CPU(Central Processing Unit, 중앙처리장치) 안에 얹혀 있어서 메모리에 닿는 모든 접근이 이 부품을 지나갑니다.

낱말부터 세웁니다. 프로그램이 코드 안에서 부르는 주소를 가상 주소라고 합니다. 메모리에 꽂혀 있는 칸을 가리키는 주소는 물리 주소입니다. 가상 주소를 물리 주소로 바꾸는 일을 주소 변환이라고 부르고, MMU 는 그 변환만 하는 부품입니다.

두 주소의 짝을 적어 둔 표가 메모리에 놓여 있습니다. MMU 는 그 표를 읽어 짝을 찾습니다. 아래 그림이 그 관계입니다.

flowchart TD
    subgraph CPU["CPU"]
        CORE["코어 · 명령을 실행한다"]
        MMU["MMU · 주소를 바꾸고 권한을 본다"]
    end
    subgraph MEM["메모리"]
        PT["짝이 적힌 표"]
        FRAME["값이 실제로 담긴 칸"]
    end
    CORE -->|"가상 주소"| MMU
    MMU -->|"표를 읽는다"| PT
    MMU -->|"물리 주소"| FRAME

주소를 바꿔서 얻는 것

프로그램마다 주소를 따로 주면 서로 겹칠 걱정이 없어집니다. 두 프로그램이 똑같이 100번 칸을 불러도 MMU 가 서로 다른 칸으로 보냅니다. 프로그램 하나가 쓰는 주소 묶음을 주소 공간이라고 합니다. 주소 변환이 이 묶음을 프로그램마다 하나씩 만들어 줍니다.

주소를 미리 나눠 갖지 않아도 되는 것도 여기서 옵니다. 변환이 없으면 어느 프로그램이 메모리의 어느 구간을 쓸지 실행 전에 정해 두어야 합니다. 변환이 있으면 빈 칸이 어디든 짝만 고쳐 주면 됩니다.

남의 메모리에 못 닿는 것도 같은 부품이 맡습니다. 표에 짝이 안 적힌 주소는 아예 닿을 방법이 없습니다. 메모리 보호가 소프트웨어의 약속이 아니라 하드웨어의 성질이 되는 까닭입니다.

그리고 이 변환이 가상 메모리의 바탕이 됩니다. 짝을 그때그때 고칠 수 있으니 운영체제는 꽂혀 있는 메모리보다 넓은 주소를 프로그램에 내줄 수 있습니다.

변환에 쓰는 표

주소를 한 칸씩 짝지으면 표가 메모리보다 커집니다. 그래서 주소를 일정한 크기로 자른 덩어리끼리 짝짓습니다. 가상 쪽 덩어리를 페이지, 물리 쪽 덩어리를 프레임이라고 부릅니다.

flowchart TD
    subgraph V["가상 쪽"]
        P1["페이지 1"]
        P2["페이지 2"]
        P3["페이지 3"]
    end
    subgraph R["물리 쪽"]
        F7["프레임 7"]
        F2["프레임 2"]
        F9["프레임 9"]
    end
    P1 --> F7
    P2 --> F9
    P3 --> F2

페이지는 차례대로 이어져 있습니다. 그 짝인 프레임은 흩어져 있어도 됩니다.

그 짝을 적어 둔 표가 페이지 테이블입니다. 운영체제가 프로세스마다 하나씩 만들어 메모리에 올려 둡니다. MMU 는 이 표를 읽어 「이 페이지가 어느 프레임에 올라앉아 있나」를 찾습니다.

MMU 는 지금 볼 표가 어디 있는지를 CPU 안의 작은 저장소에서 읽습니다. 값을 하나씩 담아 두는 이 저장소를 레지스터라고 합니다. 그 가운데 하나에 표의 시작 주소가 들어 있습니다.

실행하던 프로세스를 내리고 다른 프로세스를 올리는 일을 문맥 교환이라고 합니다. 문맥 교환이 일어나면 운영체제가 이 레지스터의 값을 갈아 끼웁니다. 그 순간부터 같은 가상 주소가 다른 프레임을 가리킵니다.

표는 한 벌이 아니라 층으로 쌓습니다. 프로그램이 쓰는 주소 범위는 대부분 비어 있어서, 평평한 표를 두면 아무도 안 쓰는 구간의 줄까지 들고 있어야 하기 때문입니다. 층을 따라 내려가며 표를 읽는 이 과정을 페이지 테이블 워크라고 합니다.

flowchart TD
    REG["레지스터 · 표의 시작 주소"]
    subgraph L1["위층 표"]
        L1A["줄 1 · 아래층 표를 가리킨다"]
        L1B["줄 2 · 비어 있다"]
        L1C["줄 3 · 비어 있다"]
    end
    subgraph L2["아래층 표"]
        L2A["줄 1 · 프레임을 가리킨다"]
        L2B["줄 2 · 비어 있다"]
    end
    FRAME["프레임"]
    REG --> L1
    L1A --> L2
    L2A --> FRAME

레지스터가 가리키는 위층 표에서 한 줄을 골라 아래층 표로 내려가고, 아래층 표의 한 줄이 프레임을 가리킵니다. 비어 있는 줄 밑에는 아래층 표를 아예 만들어 두지 않습니다.

권한을 같이 보는 검사

표의 한 줄에는 프레임 번호만 있는 것이 아닙니다. 읽어도 되는지, 써도 되는지, 그 안의 값을 명령으로 실행해도 되는지, 운영체제만 닿을 수 있는 구간인지가 같이 적혀 있습니다.

MMU 는 변환하면서 이 표시들을 함께 봅니다. 읽기 전용 페이지에 쓰기가 들어오면 그 접근을 멈춥니다. 변환과 검사가 한 번에 끝나므로 검사 때문에 접근이 한 번 더 도는 일은 없습니다.

flowchart TD
    A["프로그램이 부른 가상 주소"] --> B["MMU 가 받는다"]
    B --> C{"짝이 표에 있나"}
    C -->|"없다"| D["접근을 멈추고 운영체제에 넘긴다"]
    C -->|"있다"| E{"허용된 접근인가"}
    E -->|"아니다"| D
    E -->|"맞다"| F["물리 주소로 메모리에 닿는다"]

멈춘 접근은 운영체제에게 넘어갑니다. 이 넘김을 페이지 폴트라고 합니다. 운영체제가 보기에 고칠 수 없는 접근이면 그 프로세스는 세그멘테이션 폴트로 끝납니다.

폴트를 기회로 쓰는 일

짝이 없다고 늘 잘못은 아닙니다. 운영체제는 일부러 짝을 비워 두고, 프로그램이 그 주소를 처음 건드릴 때 폴트를 받아 그때 채워 넣습니다. 이것이 요구 페이징입니다.

읽기 전용 표시도 같은 방식으로 쓰입니다. 같은 프레임을 두 프로세스가 같이 보게 해 둡니다. 쓰기가 들어오는 순간에만 복사본을 만듭니다. 이것이 쓰기 시 복사입니다.

오래 안 쓴 페이지를 디스크로 밀어냈다가, 다시 건드릴 때 폴트로 되받아 오는 일도 있습니다. 이것을 스와핑이라고 합니다. 셋 다 이 하드웨어가 내는 폴트 위에 얹혀 있습니다.

변환 비용을 줄이는 캐시

표가 메모리에 있으니 변환 한 번에 메모리를 여러 번 읽게 됩니다. 값 하나 읽으려다 표를 먼저 읽는 셈입니다.

그래서 MMU 는 방금 한 변환의 답을 안에 담아 둡니다. 이 작은 저장소가 TLB(Translation Lookaside Buffer, 변환 색인 버퍼)입니다. 같은 페이지를 다시 건드리면 표를 읽지 않고 담아 둔 답을 씁니다.

flowchart TD
    A["가상 주소"] --> B{"TLB 에 답이 있나"}
    B -->|"있다"| C["담아 둔 답을 그대로 쓴다"]
    B -->|"없다"| D["표를 읽고 그 답을 TLB 에 담는다"]
    C --> E["물리 주소"]
    D --> E

MMU 가 없는 기계

작은 마이크로컨트롤러에는 이 부품이 없습니다. 프로그램이 부르는 주소가 곧 물리 주소입니다. 주소 공간이 하나뿐이라 한 프로그램이 주소를 잘못 쓰면 다른 프로그램의 값을 덮어씁니다.

대신 MPU(Memory Protection Unit, 메모리 보호 장치)를 얹은 기계가 있습니다. MPU 는 주소를 바꾸지는 않고 구간마다 권한만 봅니다. 검사는 되지만 프로그램마다 주소 공간을 따로 주는 일은 못 합니다.

백엔드 개발자가 MMU 를 만나는 때

이 부품을 코드로 부르는 일은 없습니다. 표를 만드는 것은 운영체제입니다. 표를 읽는 것은 하드웨어라 애플리케이션에는 손잡이가 없습니다.

그래도 이름이 나오는 데가 둘 있습니다. 첫째는 프로세스의 메모리 사용량입니다. 프로세스 목록을 보면 메모리가 두 수로 나뉘어 찍힙니다. 짝만 잡아 둔 주소의 합을 가상 크기(VSZ, Virtual Size), 실제로 프레임을 차지한 양을 상주 크기(RSS, Resident Set Size)라고 합니다.

둘째는 널 포인터를 건드린 프로세스가 곧바로 죽는 것입니다. 짝이 없는 주소라 접근이 그 명령에서 멈춥니다. 잘못된 값이 옆 구간을 덮으며 퍼지는 대신 거기서 끝납니다.

관련 항목

MMU 가 얹혀 있는 하드웨어와 그것을 부리는 소프트웨어

CPU · 코어 · 레지스터 · 커널 · 운영체제 · 프로세스

MMU 가 바꾸는 주소와 그 단위

가상 주소 · 물리 주소 · 페이지 · 프레임 · 페이지 크기 · 거대 페이지 · 오프셋

MMU 가 변환에 쓰는 표와 그 부품

페이지 테이블 · 페이지 테이블 엔트리 · 페이지 테이블 워크 · 다단계 페이지 테이블 · CR3 · TLB

MMU 가 떠받치는 메모리 관리 기능

가상 메모리 · 주소 공간 · 메모리 보호 · 요구 페이징 · 스와핑 · 쓰기 시 복사 · 메모리 매핑 · 공유 메모리

MMU 의 검사에 걸렸을 때 터지는 오류

페이지 폴트 · 세그멘테이션 폴트 · 널 포인터 역참조 · 코어 덤프 · 버퍼 오버플로

MMU 를 비우거나 갈아 끼우게 만드는 사건

문맥 교환 · TLB 미스 · TLB 슛다운 · 무효화 · ASID · 스래싱

MMU 대신 쓰거나 한 겹 더 쌓는 장치

MPU · IOMMU · 하이퍼바이저 · 가상화 · 중첩 페이지 테이블

MMU 를 다루는 커널 쪽 이름

메모리 관리자 · 페이지 교체 알고리즘 · 메모리 할당자 · mmap · OOM 킬러

다른 이름: Memory Management Unit · 메모리 관리 장치