CS:APP 9장 가상메모리 (9.1~9.8)

silver ·약 17시간 전

크래프톤 정글

목록 보기
26/26

가상메모리는 디스크를 메인메모리의 캐시처럼 사용하는 기법

크게 세 가지 역할을 함.

  • 캐싱 도구 → 큰 프로그램 실행 가능
  • 메모리 관리 도구 → 링킹, 로딩, 공유 등을 단순화
  • 메모리 보호 도구 → 프로세스 간 메모리 침범 방지

이 모든 기능의 핵심에 주소 변환이 있음.

9.1 물리 주소와 가상 주소

  • 물리 주소(PA)
    CPU가 실제 메모리(DRAM)의 위치를 가리키는 주소.

  • 가상 주소(VA)
    CPU가 사용하는 주소. 실제 물리 주소와는 다름.

CPU 칩
┌───────────────────────────┐
│ CPU --[VA]--> MMU--[PA]-│-->  메인메모리(DRAM)
└───────────────────────────┘

MMU는 페이지 테이블(page table)이라는, 메모리에 저장된 참조테이블을 참조해서 변환을 수행하고, 이 페이지 테이블은 운영체제가 관리함.

9.2 주소 공간

  • 선형 주소 공간: {0, 1, 2, …} 처럼 정렬된 음이 아닌 정수 집합.
  • 가상 주소 공간: 크기 N = 2ⁿ (n비트로 표현 가능한 만큼) → n비트 주소공간. 예: x86-64는 48비트 가상주소를 주로 사용.
  • 물리 주소 공간: 크기 M (실제 설치된 DRAM 바이트 수, 2의 거듭제곱일 필요 없음). M바이트의 물리메모리로 대응되는 물리주소공간을 가짐.

핵심은 가상 주소가 물리 주소에 매핑된다는 것!

가상 주소
→ 물리 주소
→ 또는 아직 할당되지 않음

9.3 VM을 캐시로 사용하기

  • 가상메모리는 "디스크에 저장된 N바이트 배열"
    (각 바이트 = 고유 가상주소 = 배열 인덱스)

  • 이 배열의 일부가 메인메모리에 "캐시"됨

  • 가상 페이지(Virtual Page, VP)
    = VM 시스템이 가상메모리를 관리하기 위해 고정 크기로 나눈 블록 단위
    (보통 P = 2ᵖ 바이트)

  • 각 가상 페이지는 아래 세 상태 중 하나임:
    ① unallocated : 아직 할당조차 안 됨 (데이터도 없고, 디스크 공간도 안 씀)
    ② cached : 디스크에 있고, 지금 물리메모리(DRAM)에도 올라와 있음
    ③ uncached : 디스크에는 있는데, 지금 물리메모리엔 없음

DRAM 캐시의 구성

디스크는 DRAM보다 매우 느림.
따라서 페이지 폴트를 최대한 줄이는 방향으로 설계함.

  • 페이지를 크게 잡음
  • 어느 물리 프레임에나 페이지를 배치할 수 있음
    → fully associative
  • OS가 정교한 페이지 교체 알고리즘 사용
  • write-back 방식 사용
    → 수정된 페이지는 쫓아낼 때 디스크에 저장

🖱️캐시 미스났을 때 처리 과정

페이지 테이블

: 가상 페이지가 현재 DRAM에 있는지 기록해 둔 표.
  PTE(Page Table Entry)가 모인 배열이고 OS가 관리함.

  • valid = 1
    → DRAM에 있음
    → PTE가 물리 프레임 번호를 가리킴

  • valid = 0
    → DRAM에 없음
    → 디스크에 있거나 아직 할당되지 않음

페이지 히트

PTE의 valid = 1

→ MMU가 바로 PA로 변환.
→ 메모리에 접근

페이지 폴트

PTE의 valid = 0

→ CPU가 예외 발생
→ 커널의 page fault handler 실행

  1. 희생 페이지(victim) 선택
  2. 수정된 페이지라면 디스크에 저장
  3. 필요한 페이지를 DRAM으로 가져옴
  4. 페이지 테이블 갱신
  5. 폴트를 발생시킨 명령어를 다시 실행

* 요구 페이징 : 페이지에 실제로 접근할 때 필요한 페이지를 가져오는 방식

지역성

: 디스크가 매우 느린데도 VM이 잘 동작하는 이유

프로그램은 한 번에 모든 메모리를 사용하는 것이 아니라
특정 페이지들을 집중적으로 사용하는 경향이 있음.

워킹셋(working set): 자주 사용하는 페이지들의 집합

  • 워킹셋이 물리 메모리보다 작음
    → 페이지 폴트가 적음
    → 대부분의 접근이 히트

  • 워킹셋이 물리 메모리보다 큼
    → 페이지를 계속 내보내고 다시 가져옴
    → 스레싱(thrashing)
    → 성능 급격히 저하

9.4 메모리 관리를 위한 도구로서의 VM

OS가 프로세스마다 독립적인 페이지 테이블을 유지 → 세 가지 이득

① 동일한 레이아웃 (링크/로드 단순화)

  • 프로세스마다 페이지 테이블이 독립적이므로
    같은 가상주소(예: 0x400000)를 써도 서로 다른 물리주소로 매핑 가능
  • 그래서 모든 프로세스가 "같은 형태"의 가상주소공간 레이아웃을 가질 수 있음
  • 링커/로더/컴파일러가 고정 주소를 가정하고 코드를 생성 가능
    (주소 재배치 고민 불필요, 구현 단순화)
  • 로딩 시점에 디스크 내용을 미리 복사/0채우기 안 해도 된다 (lazy loading)
    → 9.8.3 execve 다시보기에서 더 정확히 다룸

② 페이지 단위 공유

  • 각 프로세스는 사적(private) 영역 보유, 서로 중첩 없는 프레임 사용
  • 예외: libc 같은 공유 라이브러리는 중복 보관이 낭비
    → 여러 PTE가 "같은" 물리페이지를 가리키게 해서 물리메모리 한 벌만 유지
  • 참고: 커널 코드/데이터도 모든 프로세스가 이런 식으로 공유함

③ 간단한 메모리 할당

  • 문제: 물리주소 직접 쓰는 시스템이면 "연속된" 빈 공간을 찾아야 해서 어려움
  • 해결: 가상주소는 연속처럼 보여도, 물리적으로는 흩어진 빈 프레임 아무거나 갖다 매핑만 하면 끝
  • 결론: 물리메모리 레벨의 외부단편화 문제가 사실상 사라짐
    ("힙 내부"의 단편화 문제는 9.9에서 별개로 다룸)

* mmap 시스템 콜 (자세한 내용은 9.8.4)

: 파일의 일부를 프로세스의 가상 주소 공간에 매핑하는 시스템 콜.

파일의 내용을 미리 전부 메모리에 복사하는 것이 아니라,
파일과 가상 주소 공간을 연결해 둠.

이후 해당 주소에 접근하면 필요한 페이지가 page fault를 통해 메모리에 올라옴.

즉, 파일을 read()로 읽는 대신 메모리에 있는 데이터처럼 접근할 수 있게 해주는 기능

9.5 메모리 보호를 위한 도구로서의 VM

PTE에 접근 권한 비트를 추가로 둠.
MMU가 주소를 변환할 때 접근 권한도 함께 검사함.

  • 읽기 전용 영역에 쓰기
  • 커널 영역에 사용자 모드로 접근
  • 다른 프로세스의 메모리에 접근
  • 공유 페이지를 허용되지 않은 방식으로 수정

등을 막을 수 있음.

  • SUP
    → 커널 모드에서만 접근 가능한지

  • READ
    → 읽기 허용 여부

  • WRITE
    → 쓰기 허용 여부

  • 권한 위반 발생
    → CPU가 예외 발생
    → OS가 처리
    → 일반적으로 프로세스에는 SIGSEGV가 전달됨

9.6 주소 변환

가상 주소와 물리 주소를 페이지 단위로 나눠 생각함.

  • VA = [ VPN(Virtual Page Number) | VPO(Virtual Page Offset) ]
  • PA = [ PPN(Physical Page Number) | PPO(Physical Page Offset) ]

페이지 크기가 같기 때문에 VPO와 PPO는 동일함.

기본 주소 변환 과정

CPU가 VA 생성
↓
VPN으로 PTE 찾음
↓
valid 확인
↓
valid = 1
→ PPN 얻음
→ PPN + VPO로 PA 생성
→ 메모리 접근

valid = 0
→ page fault
→ OS가 처리

TLB

페이지 테이블을 매번 메모리에서 확인하면 느림.
그래서 MMU 안에 자주 사용하는 주소 변환을 저장하는 작은 캐시인 TLB를 둠.

  • TLB
    = VPN → PPN 변환 결과를 저장하는 캐시

  • 구조: VPN을 TLBT(태그) + TLBI(인덱스)로 쪼개서 세트에 인덱싱

  • TLB Hit
    → 변환이 MMU 칩 내부에서 끝남 → 매우 빠름(사이클 단위)

  • TLB Miss
    → L1 캐시(또는 메모리)에서 PTE를 가져와 TLB에 채움

멀티레벨 페이지 테이블

하나의 거대한 페이지 테이블을 만들면 사용하지 않는 가상 주소 영역까지 관리해야 해서 공간 낭비가 큼.

그래서 페이지 테이블을 여러 단계로 나눔.

가상주소
   ↓
Level 1 : 이 영역 쓰고있나 확인
   ↓
Level 2 : 쓰고있는 애들만 L2 페이지 테이블 만듦
   ↓
실제 페이지(PTE)

장점

  • 메모리 절약
    • 사용하지 않는 영역의 하위 페이지 테이블은 아예 만들지 않음.
  • 필요한 것만 메모리에 올릴 수 있음
    • Level 1은 항상 메모리에 두고,
    • Level 2는 필요할 때 메모리에 가져올 수 있음.

단점 : 주소 변환하려면 여러단계의 PTE를 찾아야 함

→ TLB가 해결!
: 자주 사용하는 PTE를 TLB에 저장해두기 때문에 실제로는 매번 L1 → L2를 전부 찾아갈 필요가 없음.

End-to-End 주소 변환 예시

CPU
↓ VA
TLB 확인
↓
├─ Hit → PPN 획득
│
└─ Miss → 페이지 테이블 확인
             ↓
          valid = 0 → Page Fault
          valid = 1 → PPN 획득
↓
PPN + VPO
↓
PA
↓
Cache
↓
DRAM

캐시와 가상메모리

캐시를 가상 주소로 접근할 수도 있고 물리 주소로 접근할 수도 있음.

  • VIVT
    → Virtual Index / Virtual Tag
    → 빠르지만 aliasing 등의 문제가 있음

  • PIPT
    → Physical Index / Physical Tag
    → 문제는 적지만 주소 변환을 먼저 해야 함

  • VIPT
    → Virtual Index / Physical Tag
    → 주소 변환과 캐시 조회를 병렬로 수행할 수 있음
    * VIPT에서는 페이지 오프셋이 VA와 PA에서 같다는 점을 이용함.

9.7 Intel Core i7 / Linux 사례

실제 시스템에서는 지금까지 배운 개념을 TLB, 멀티레벨 페이지 테이블, 캐시 등을 조합해서 사용함.

  • Core i7
    → 48비트 가상주소
    → 4KB 페이지
    → 다단계 페이지 테이블

  • Linux는 프로세스의 가상 주소 공간을 영역 단위로 관리함.
    코드 / 데이터 / 힙 / 스택 / mmap 영역 등

9.8 메모리 매핑

파일이나 익명 메모리 같은 객체를 프로세스의 가상 주소 공간에 연결하는 것.

1. Regular File에 매핑 :

: 실제 파일을 가상 메모리와 연결하는 경우.

ex) 실행파일의 .text/.data, 혹은 사용자가 mmap한 임의 파일

  • 파일의 내용이 가상 페이지의 초기값이 됨.
  • 처음부터 RAM에 올리지 않음 → Demand Paging
  • CPU가 해당 페이지를 처음 접근하면 Page Fault
  • 그때 파일에서 해당 페이지를 읽어 RAM에 가져옴.
  • 파일 영역보다 VMA가 크면 남는 부분은 0으로 채움

2. Anonymous File에 매핑

: 실제 파일과 연결되지 않은 메모리 영역.

ex) 힙, 스택 초기 영역

  • 처음에는 물리 페이지가 없음.
  • 페이지에 처음 접근하면 Page Fault
  • 물리 프레임을 할당하고 0으로 초기화
  • 이후 해당 페이지를 사용함.

→ 이런 페이지를 demand-zero page라고 함.

Shared / Private Mapping

하나의 물리 페이지를 여러 프로세스가 공유할 수도 있음.

Shared

 Process 1 ─┐ 
			├──→ 같은 물리 페이지 
 Process 2 ─┘
  • 여러 프로세스가 같은 물리 페이지를 공유함.
  • 각 프로세스는 서로 다른 가상주소로 같은 물리 페이지를 가리킬 수 있음.
  • ex) libc.so 같은 공유 라이브러리의 read-only 코드 영역
  • 공유된 영역을 변경하면 다른 프로세스에서도 변경 내용이 보일 수 있음.
  • 파일 매핑의 경우 변경 내용이 원본 파일에 반영될 수도 있음.

Private + Copy-On-Write(COW)

처음에는 물리 페이지를 공유하지만, 쓰기 발생 시 해당 페이지만 복사함.

읽기
Process 1 ─┐
           ├──→ 같은 물리 페이지
Process 2 ─┘

쓰기 발생
           ↓
      Protection Fault
           ↓
       페이지 복사
           ↓
Process 1 ─→ 원래 페이지
Process 2 ─→ 새 페이지
  • 읽기만 할 때 → 물리 페이지 공유
  • 쓰려고 할 때 → 해당 페이지만 복사
  • 필요할 때만 복사해서 메모리를 절약

Swap

DRAM이 부족하면 사용하지 않는 페이지를 DRAM에서 내보낼 수 있음.

가상 페이지
    ↓
   RAM
    ↓ RAM 부족
 Swap Out
    ↓
Swap Space

나중에 다시 필요해지면

Swap Space
    ↓
 Swap In
    ↓
   RAM

특히 익명 페이지는 원본 파일이 없기 때문에, RAM에서 쫓겨날 때 현재 내용을 보존하기 위해 swap space가 사용될 수 있음.

→ COW로 만들어진 private 페이지도 메모리가 부족하면 swap될 수 있음.

fork()

: 부모 프로세스의 가상 주소 공간을 자식에게 복제함.
하지만 물리 페이지를 처음부터 전부 복사하지 않음.

부모와 자식이 같은 물리 페이지를 공유
↓
쓰기 발생
↓
페이지 복사
↓
각자의 페이지 사용

→ Copy-on-Write

execve()

: 현재 프로세스의 프로그램을 새로운 프로그램으로 교체함.

  • 기존 사용자 영역을 버리고 새 프로그램의 .text, .data, .bss, 힙, 스택 등을 새로운 가상 메모리 영역으로 구성.
  • 실제 페이지 내용은 필요한 시점에 page fault를 통해 가져올 수 있음. (demand paging)

mmap()

파일이나 익명 메모리를 프로세스의 가상 주소 공간에 매핑하는 시스템 콜.

  • mmap()
    → 가상 주소 공간에 영역 생성 + 파일/익명 메모리와 연결

  • munmap()
    → 매핑 제거

주요 옵션

MAP_SHARED
→ 변경 내용을 공유

MAP_PRIVATE
→ Copy-on-Write

MAP_ANON
→ 파일 없는 익명 메모리

PROT_READ / WRITE / EXEC
→ 접근 권한 설정

정리

가상메모리의 핵심: 가상 주소를 이용해 실제 물리 메모리를 추상화한다!

⇒ 이 구조를 이용해서

  • 필요한 페이지만 DRAM에 올리고
  • 프로세스마다 독립적인 주소 공간을 제공하고
  • 같은 물리 페이지를 공유하고
  • 접근 권한을 검사하고
  • 파일을 메모리처럼 사용할 수 있게 함.

0개의 댓글