가상 주소를 물리 주소로 변환하는 하드웨어(Memory Management Unit).
무엇을 가능하게 하나
| 프로세스 A가 보는 주소 0x1000 | → | 물리 0x8A000 |
|---|---|---|
| 프로세스 B가 보는 주소 0x1000 | → | 물리 0x3F000 |
- 같은 주소를 쓰는데 서로 다른 물리 메모리
이 한 겹의 변환이 만들어 내는 것들:
| 기능 | 어떻게 |
|---|---|
| 프로세스 격리 | 서로의 물리 메모리를 가리킬 수 없다 |
| 가상 주소 공간 | 각 프로그램이 0부터 시작하는 연속 공간을 본다 |
| 스왑 | 페이지를 디스크로 내보내고 필요할 때 다시 |
| 공유 라이브러리 | 같은 물리 페이지를 여러 프로세스가 매핑 |
| Copy-on-Write | fork 시 복사 없이 공유하다 쓸 때 복사 |
| 접근 권한 | 페이지 단위 읽기/쓰기/실행 제어 |
| 단편화 해소 | 물리적으로 흩어져도 가상으로는 연속 |
변환 과정
가상 주소 (32비트, 4KB 페이지)
┌──────────────────────┬───────────────┐
│ 페이지 번호 20비트 │ 오프셋 12비트 │
└──────────────────────┴───────────────┘
↓ 페이지 테이블 조회 (메모리 접근!)
프레임 번호
↓
물리 주소 = 프레임 번호 + 오프셋
변환 자체가 메모리 접근이므로 느리다. 그래서 TLB라는 캐시를 둔다.
MPU와의 결정적 차이 — 면접 단골
| MMU | MPU | |
|---|---|---|
| 주소 변환 | 한다 (가상 → 물리) | 안 한다 (물리 주소 그대로) |
| 관리 단위 | 페이지 테이블(메모리에 저장) | 레지스터 몇 개(8~16개 구역) |
| 기능 | 격리 + 가상화 + 스왑 | 접근 권한 검사만 |
| 복잡도·비용 | 높다 | 매우 낮다 |
| 결정론적 지연 | ✗ (TLB 미스·페이지 폴트) | ○ |
| 탑재 | Cortex-A | Cortex-M (선택) |
| OS | 리눅스 등 범용 OS | RTOS |
"MMU는 주소를 바꾸고, MPU는 검사만 한다" 가 한 줄 요약이다.
왜 MCU에는 MMU가 없나
- ① 실리콘 면적·전력 — 페이지 테이블 워커와 TLB는 비싸다
- ② 실시간성 — — 페이지 폴트가 나면 지연이 예측 불가능해진다
- 하드 실시간 시스템에서는 치명적
- ③ 필요성 부족 — — 단일 애플리케이션이 전체 메모리를 쓰는 구조
- ④ 메모리 부족 — — 페이지 테이블 자체가 메모리를 먹는다
②가 특히 중요하다. "이 코드는 반드시 10μs 안에 끝나야 한다"는 요구가 있을 때 페이지 폴트로 수 밀리초가 걸릴 수 있다면 보장이 불가능하다.
리눅스가 돌아가려면 MMU가 필요하다
- 일반 리눅스 — → MMU 필수 (프로세스 격리, fork, mmap)
- uClinux — → MMU 없이 동작하는 변형. fork 대신 vfork,
- 프로세스 격리 없음, 메모리 단편화에 취약
MMU 유무가 "리눅스를 올릴 수 있는가"를 가른다. Cortex-A는 MMU가 있어 리눅스를 올리고, Cortex-M은 없어서 RTOS를 쓴다.
TLB — 변환 캐시
변환 결과를 캐싱해 페이지 테이블 접근을 생략 히트율이 99% 이상이라 사실상 변환 비용이 사라진다
- 프로세스가 바뀌면 → TLB flush (또는 ASID 로 구분)
- 이것이 프로세스 컨텍스트 스위칭이 비싼 핵심 이유
임베디드 리눅스에서 만나는 것들
// 사용자 공간에서 하드웨어 레지스터에 접근하려면
int fd = open("/dev/mem", O_RDWR | O_SYNC);
void *p = mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0x40020000);
MMU 때문에 사용자 프로그램이 물리 주소를 직접 못 쓴다.
mmap으로 가상 주소에 매핑해야 접근할 수 있다.
DMA와의 관계도 복잡해진다 — 디바이스는 물리 주소를 보고,
드라이버는 가상 주소를 쓴다. dma_alloc_coherent처럼
물리적으로 연속이면서 가상 주소도 아는 메모리를 따로 할당해야 한다.