
가상 메모리(Virtual Memory)는 프로세스가 실제 물리 메모리보다 더 큰 메모리를 사용하는 것처럼 보이게 하는 기술이다.
→ 주기억장치(램)의 일부와 보조기억장치(디스크)를 조합해, 논리적 주소 공간을 확장한다.
1. MMU (Memory Management Unit): 논리 주소 → 물리 주소로 변환
2. 페이징(Paging) 기법: 메모리를 페이지 단위로 나눠 필요한 페이지만 로딩
3. 보조 기억장치와의 연동: 디스크에 일시적으로 데이터를 저장하고 교체 가능
즉, 주소 변환 하드웨어 + 운영체제의 메모리 관리 기법 덕분
CPU가 사용하는 가상 주소를 실제 물리 주소로 바꿔주고, 메모리를 보호해주는 메모리 관리 하드웨어로, CPU가 메모리에 접근하기 전에 메모리 주소 번역 작업을 수행한다.
❌ 사용할 수 있다
세그멘테이션도 가상 메모리 기법 중 하나인데, 페이징과는 다르게 논리 단위(코드, 데이터 등)를 기준으로 나누는 방식이다.
방식이 다를 뿐, 가상 메모리를 사용할 수 있다.
Page Fault란, 프로세스가 요청한 페이지가 메모리에 없을 때 발생하는 인터럽트이다.
처리 순서
| 페이지 크기 작음 | 페이지 크기 큼 |
|---|---|
| 내부 단편화 ↓ | 내부 단편화 ↑ |
| 페이지 수 ↑ | 페이지 수 ↓ |
| 페이지 테이블 ↑ | 페이지 테이블 ↓ |
| I/O 오버헤드 ↑ | I/O 효율 ↑ |
| 캐시 효율 ↑ | 캐시 효율 ↓ |
❌ 아니다.
페이지 크기가 커지면, 한번에 많은 데이터를 가져오기 때문에 Page Fault 빈도가 줄어드는 경향이 있다. 하지만, 너무 크면 불필요한 데이터까지 함께 로딩되어 캐시 효율이 떨어지고 내부 단편화가 증가되는 문제가 있다. 또, 지나치게 커진다면 Thrashing을 증가시킬 수 있다.
| 항목 | 세그멘테이션 (Segmentation) | 페이징 (Paging) |
|---|---|---|
| 분할 단위 | 논리적 단위 (코드, 데이터 등) | 고정 크기 블록 (페이지 단위) |
| 단편화 문제 | 외부 단편화 발생 | 내부 단편화 발생 |
| 크기 | 가변 크기 | 고정 크기 |
| 주소 구조 | 세그먼트 번호 + 오프셋 | 페이지 번호 + 오프셋 |
| 장점 | 논리 구조 반영, 보안 적용 쉬움 | 단순한 구현, 메모리 관리 효율적 |
일반적으로 둘의 크기는 같으며 MMU는 페이지 테이블을 사용하여 가상 주소 -> 물리 주소로 변환한다.
고정 크기 할당 시, 남은 공간이 낭비되는 문제
가변 크기 할당 시, 작은 빈 공간이 흩어져서 사용 불가능해지는 문제
💡 가상 주소 = 페이지 번호 + 오프셋
변환 과정
페이지 테이블에 있는 각 엔트리는 해당 페이지에 대한 접근 권한을 비트로 관리한다.
R/W/X 플래그: 읽기(Read), 쓰기(Write), 실행(Execute) 가능 여부
예: 읽기 전용 페이지에 쓰기 시도 → 보호 오류 (Protection Fault) 발생
-> 페이지 테이블을 확인하여 해당 페이지의 접근 권한 정보를 보고 수정 가능 여부를 판단할 수 있다.
주소 공간: 32비트 = 2³² = 4GB 페이지 크기: 2¹⁰ (1KB) → 페이지 개수 = 2³² / 2¹⁰ = 2²²
32비트 주소는 2³² = 4GB까지의 주소만 표현 가능
페이징과 관계: 페이지 번호와 오프셋 합쳐서 32비트 안에 들어야 한다.
따라서 페이지 테이블을 통해 접근할 수 있는 전체 주소 공간은 4GB가 최대
→ 페이지 테이블 검사 → 보호 위반 → Segmentation Fault 발생
TLB (Translation Lookaside Buffer) 이란, 가상 주소를 물리 주소로 변환한 결과를 캐싱해두는 고속 메모리이다.
📌 주소 변환 속도 향상
➡ TLB는 이걸 미리 저장해두고 빠르게 꺼내쓰는 캐시이다.
예시
| 방법 | 속도 |
|---|---|
| 페이지 테이블 참조 | 느림 (메모리 접근 필요) |
| TLB 캐시 히트 | 매우 빠름 (CPU 내부 접근) |
➡ TLB hit율이 높을수록 주소 변환이 빨라지고, 전체 성능이 올라간다.
MMU란, 가상 주소 -> 물리 주소 변환을 담당하는 하드웨어
| 역할 | 설명 |
|---|---|
| 주소 변환 | 가상 주소 → 물리 주소 |
| 권한 검사 | 읽기/쓰기/실행 권한 확인 |
| TLB 관리 | TLB를 사용해서 변환 속도 향상 |
| 페이지 폴트 처리 협력 | 페이지 테이블에 없는 주소 → 예외 발생 처리 |
문제
각 CPU 코어는 자신만의 TLB를 가지고 있기 때문에, 멀티코어 환경에선 TLB의 동기화 문제가 발생할 수 있다.
📌 해결 방법
📌 Context Switching이란?
CPU가 다른 프로세스로 전환할 때 발생한다.
📌 TLB 관점 변화
TLB는 이전 프로세스의 가상 주소 → 물리 주소 매핑을 갖고 있다. 여기서 다른 프로세스로 바뀌면 → 기존 TLB 매핑은 잘못된 주소가 된다.
해결 방법
1. TLB Flush (TLB 초기화)
OS가 TLB를 전부 비우고, 새 프로세스가 실행되면서 새 주소 매핑이 생기게 된다.
단점: 초기화 후에는 TLB가 비어 있으므로 TLB miss가 증가하게 되고, 결국 페이지 테이블을 자주 조회하게 되어 성능 저하 발생
2. Address Space Identifier (ASID) 사용 (성능 향상)
각 프로세스에 고유 번호(ASID)를 붙이고, TLB가 ASID + 가상 주소를 함께 기억한다.
Context Switch 시 TLB를 Flush하지 않아도 되고, TLB 안에 여러 프로세스의 매핑이 공존할 수 있게 된다.
단순히 ASID만 바꾸면, 해당 프로세스의 매핑만 유효하게 작동한다.
➡ 여러 프로세스의 TLB 엔트리를 함께 보관 가능
한 번에
0->1로 바꾸는 연산으로, 기존 값을 반환하고, 변경하는 작업이 한 사이클에 이뤄진다. 일반적으로lock구현에 사용된다.
// C 스타일 예시
bool test_and_set(bool *target) {
bool old = *target;
*target = true;
return old;
}
메모리 값이 기대한 값일 때만 변경 작업이 이뤄지고, 성공/실패 여부를 반환한다.
AtomicInteger.compareAndSet()에 사용된다.CPU 명령의 실행 순서를 강제적으로 보장하는 방법이다. 하드웨어 최적화로 예상한 값과 다른 결과가 나올 수 있는 문제를 방지한다.
volatile은 변수를 최적화 대상에서 제외하고, 항상 메인 메모리에서 읽도록 강제하는 키워드이다.
volatile boolean flag = true;
❗주의:
count++는 여전히 안전하지 않음 → synchronized나 AtomicInteger 필요멀티코어에서는 각 CPU 코어가 자신만의 캐시를 갖고 있어 문제가 더 복잡하다.
발생 문제
한 코어가 변수 x를 바꿔도 → 다른 코어는 옛날 값을 캐시에서 읽게 된다.
🎯 해결 방법: 캐시 일관성(Cache Coherency) + Memory Barrier
1. 캐시 일관성 프로토콜 (예: MESI)
2. Memory Barrier (Memory Fence)