Krafton Jungle/4. CSAPP

[Computer System] ⑨ 가상메모리 (1)

munsik22 2025. 4. 22. 16:29

메모리를 보다 효율적이고 더 적은 에러를 갖도록 관리하기 위해서 현대의 시스템은 가상메모리Virtual Memory(이하 VM)라고 알려진 메인 메모리의 추상화를 제공한다.

  • 메인 메모리를 디스크에 저장된 주소공간에 대한 캐시로 취급해서 메인 메모리 내 활성화 영역만 유지하고, 데이터를 디스크와 메모리 간에 필요에 따라 전송하는 방법으로 메인 메모리를 효율적으로 사용한다.
  • 각 프로세스에 통일된 주소공간을 제공함으로써 메모리 관리를 단순화한다.
  • 각 프로세스의 주소공간을 다른 프로세서에 의한 손상으로부터 보호한다.

9.1 물리 및 가상주소 방식

메모리에 접근하는 방식은 물리주소(PA) 방식과 가상주소(VA) 방식으로 나눌 수 있다. 현대의 프로세스들은 가상주소 방식을 사용한다.

가상주소지정 방식을 사용하는 시스템

가상 주소 지정에서 CPU는 가상 주소(VA)를 생성하여 주기억장치에 접근하고, 이 가상 주소는 주기억장치에 전송되기 전에 적절한 물리 주소로 변환된다. 가상 주소를 물리 주소로 변환하는 작업을 주소 변환이라고 한다. 예외 처리와 마찬가지로 주소 변환은 CPU 하드웨어와 운영 체제 간의 긴밀한 협력이 필요하다. CPU 칩에 있는 전용 HW인 메모리 관리 유닛(MMU)는 OS가 관리하는 주기억장치에 저장된 참조 테이블을 사용하여 가상 주소를 실시간으로 변환한다.

🔹 Physical Addressing : CPU가 실제 메모리 주소(PA)를 직접 생성하여 메모리에 접근한다. 고전적인 방식이며, 임베디드 시스템이나 DSP 등에서 여전히 사용되고 있다.

🔹 Virtual Addressing : CPU는 가상 주소(VA)를 생성하고, 이는 MMU를 통해 실제 물리 주소로 변환된다. 이 과정을 주소 변환이라 하며, OS가 관리하는 페이지 테이블을 통해 이루어진다.

9.2 주소공간

주소공간은 비음수 정수 주소의 정렬된 집합이다.

{0, 1, 2, ..., N-1}

위와 같은 선형 주소공간을 가정해보자. 가상메모리를 가지는 시스템에서 CPU는 가상 주소공간이라 불리는 N=2^n 주소의 주소공간에서 가상의 주소를 생성한다. 위처럼 N=2^n 주소를 가지는 가상 주소공간은 n-비트 주소공간이라 부른다. 현대 시스템은 32비트나 64비트 가상 주소공간을 지원한다.

🔹 Address Space : 연속적인 정수 주소 집합 ({0, 1, 2, ..., N−1})
🔹 Virtual Address Space : CPU가 생성하는 가상 주소의 집합, 일반적으로 2ⁿ 크기의 공간 (32-bit, 64-bit 주소 공간)
🔹 Physical Address Space : 실제 메모리 공간으로, 2ᵐ 크기를 갖는 공간. OS는 각각의 프로세스에 대해 독립적인 주소 공간을 제공함으로써 보호와 격리를 보장한다.

9.3 캐싱 도구로서의 VM

어느 시점에서든 가상 페이지의 집합은 세 가지 서로 겹치지 않는 하위 집합으로 나뉜다.

  • Unallocated : VM 시스템에 의해 아직 할당(또는 생성)되지 않은 페이지들
                          할당되지 않은 블록은 데이터와 연결되어 있지 않으므로 디스크에서 공간을 차지하지 않는다.
  • Cached : 현재 물리 메모리에 캐시되어 있는 할당된 페이지들
  • Uncached : 물리 메모리에 캐시되지 않은 할당된 페이지들

VM 시스템이 메인 메모리를 캐시처럼 사용하는 방법

🔹 가상 메모리는 디스크 기반 배열로 구성되며, DRAM을 캐시처럼 사용하여 일부 페이지만 물리 메모리에 저장된다.
🔹 페이지는 고정 크기의 블록 (보통 4KB ~ 2MB)이며, 가상 페이지(VP)물리 페이지(PP)에 매핑된다.
🔹 Page Table은 각 VP가 DRAM 또는 디스크에 있는지, 그리고 어느 PP에 위치하는지 추적한다.
🔹 지역성의 원리에 따라 대부분의 참조는 working set 내에서 발생한다. 그렇지 않으면 thrashing 현상이 발생한다.

9.3.1 DRAM 캐시의 구성

DRAM 캐시의 위치는 메모리 계층 구조에서 큰 영향을 미친다. DRAM은 SRAM보다 최소 10배 느리고(여기서 SRAM 캐시는 L1, L2, L3 캐시를, DRAM 캐시는 VM 시스템 캐시를 의미한다.), 디스크는 DRAM보다 약 100,000배 느리다는 점을 기억하자. 따라서 SRAM 캐시에서의 미스에 비해서 DRAM 캐시에서의 미스 비용이 매우 크다. DRAM 캐시 미스는 디스크에서 처리되지만, SRAM 캐시 미스는 보통 DRAM 기반의 주기억장치에서 처리되기 때문이다. 게다가 디스크 섹터에서 첫 번째 바이트를 읽는 비용은 섹터 내의 연속적인 바이트를 읽는 것보다 약 100,000배 느리다. DRAM 캐시의 구성은 전적으로 막대한 미스 비용에 의해 결정된다. 따라서 OS는 DRAM 캐시에 대해 HW가 SRAM 캐시를 위해 사용하는 것보다 훨씬 더 정교한 교체 알고리즘을 사용한다. 또한, 디스크의 접근 시간이 길기 때문에 DRAM 캐시는 항상 write-through 대신 write-back을 사용한다.

9.3.2 페이지 테이블

모든 캐시와 마찬가지로 VM 시스템은 가상 페이지가 DRAM 어딘가에 캐시되어 있는지를 확인할 방법이 있어야 한다. 만약 캐시되어 있다면, 시스템은 그 가상 페이지가 어떤 물리 페이지에 캐시되어 있는지를 파악해야 한다. 미스가 발생하면, 시스템은 가상 페이지가 디스크의 어디에 저장되어 있는지를 확인하고, 물리 메모리에서 희생자 페이지victim page를 선택한 다음, 디스크에서 DRAM으로 가상 페이지를 복사하여 희생자 페이지를 교체해야 한다. 이러한 기능은 운영 체제 소프트웨어, MMU의 주소 변환 HW, 그리고 가상 페이지를 물리 페이지에 매핑하는 페이지 테이블이라는 물리 메모리에 저장된 자료구조의 조합에 의해 제공된다.

8개의 가상 페이지와 4개의 물리 페이지를 가지는 시스템의 페이지 테이블

위 그림에서 주목해야 할 점은, DRAM 캐시가 완전 결합성fully associative이므로 물리 페이지가 모든 가상 페이지를 포함할 수 있다는 점이다.

9.3.3 페이지 적중Page Hits

CPU가 참조한 VP가 이미 DRAM에 캐시되어 있을 때 이를 페이지 히트라고 한다.

VM 페이지 히트 (VP 2)

9.3.4 페이지 오류

DRAM 캐시 미스는 페이지 오류Page fault라고도 부른다. CPU가 DRAM에 캐시되어 있지 않은 VP(가상페이지) 내의 워드를 참조하면, 주소 번역 HW는 메모리에서 PTEPage Table Entry를 읽으며, 해당 VP가 캐시되어 있지 않다는 것을 유효비트valid bit로부터 유추해서 페이지 오류 예외를 발생시킨다.

VM 페이지 오류(이전): VP 3 내의 워드에 대한 참조는 미스가 되고 페이지 오류를 발생시킨다.

VM 페이지 오류(이후): 페이지 오류 핸들러는 VP 4를 희생자 페이지로 선택하고, 디스크에서 VP 3의 복사본으로 교체한다.

페이지 오류 핸들러가 오류가 발생한 명령어를 다시 시작하면, 예외를 발생시키지 않고 정상적으로 메모리에서 단어를 읽게 된다.

 

현대의 모든 시스템은 요구 페이징demand paging 방식을 사용한다. 디스크와 메모리 사이에서 페이지를 전송하는 동작을 스와핑swapping 또는 페이징이라 부른다. 페이지들은 디스크에서 DRAM으로 스와핑되어 들어오며, DRAM에서 디스크로 스와핑되어 나간다. 요구 페이징은 미스가 발생할 때 하나의 페이지로 스와핑되어 들어오는 마지막 순간까지 기다리는 전략이다.

9.3.5 페이지 할당

새로운 가상페이지의 할당

위 그림은 OS가 가상 메모리에 새로운 페이지를 할당할 때, 예를 들어 malloc을 호출한 결과로, 예제 페이지 테이블에 미치는 영향을 보여준다. 여기서 VP 5는 디스크에 공간을 만들고 PTE 5를 업데이트하여 새로 생성된 페이지를 가리키도록 하여 할당된다.

9.3.6 문제해결을 위한 지역성 (again)

VM에 대해 배우는 많은 사람들은 처음에 이것이 매우 비효율적일 것이라고 생각한다. 큰 미스 페널티를 고려할 때, 우리는 페이징이 프로그램 성능을 저하시킬까 걱정한다. 하지만 실제로 가상 메모리는 잘 작동하는데, 그 주된 이유는 우리가 잘 알고 있는 지역성 때문이다.

프로그램이 좋은 시간 지역성을 가지는 한 VM 시스템은 잘 작동한다. 그러나 모든 프로그램이 좋은 시간 지역성을 보이는 것은 아니다. 작업 집합 크기가 물리 메모리의 크기를 초과하면, 프로그램은 페이지가 지속적으로 교체되는 불행한 상황인 쓰래싱thrashing을 발생시킬 수 있다. 가상 메모리는 일반적으로 효율적이지만, 프로그램의 성능이 급격히 저하된다면 쓰래싱의 가능성을 고려해봐야 할 것이다.

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

VM이 프로세스에 별도의 주소공간을 제공하는 방법

요구 페이징과 분리된 가상 주소 공간의 조합은 시스템에서 메모리를 사용하는 방식과 관리 방식에 깊은 영향을 미친다. 특히, VM은 링킹, 로딩, 공유, 메모리 할당을 단순화해준다.

🔹 각 프로세스는 고유의 페이지 테이블을 가지며, 가상 주소 공간이 격리된다.
🔹 메모리 공유를 위해 서로 다른 가상 페이지가 동일한 물리 페이지를 참조할 수 있다. (예: printf, kernel code 등)
🔹 VM은 링킹/로딩, 코드/데이터 공유, 동적 메모리 할당 등을 단순화시킨다.

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

모든 현대 컴퓨터 시스템은 운영 체제가 메모리 시스템에 대한 접근을 제어할 수 있는 수단을 제공해야 한다. 사용자 프로세스는 읽기 전용 코드 섹션을 수정할 수 없어야 하며, 커널의 코드와 데이터 구조를 읽거나 수정할 수 없어야 한다. 또한 다른 프로세스의 개인 메모리를 읽거나 쓸 수 없으며, 모든 당사자가 명시적으로 허용하지 않는 한(명시적 프로세스 간 통신 시스템 호출을 통해) 다른 프로세스와 공유된 가상 페이지를 수정할 수 없어야 한다.

페이지 수준 메모리 보호를 제공하기 위한 VM의 사용

위 그림은 PTE에 허가 비트permission bits를 추가해서 주소 번역 HW가 VP 내용으로의 접근을 제어하는 모습을 보여준다.

🔹 VM은 페이지 단위로 접근 권한을 관리한다. 각 페이지 테이블 엔트리(PTE)에 SUP, READ, WRITE 등의 권한 비트가 존재한다.
🔹 예: 유저 모드에서는 SUP 비트가 0인 페이지만 접근 가능
🔹 이로써 프로세스 간 메모리 보호, 커널 영역 보호가 가능해진다.

9.6 주소의 번역

페이지 테이블을 사용한 주소의 번역
(a) 페이지 hit와 (b) 페이지 miss에 대한 동작 모습

🔹 MMU는 가상 주소를 물리 주소로 변환한다.
🔹 변환 과정:
    1. CPU가 VA 생성
    2. MMU가 VA의 페이지 번호를 기반으로 PTE 조회
    3. PTE가 유효하면 PA 생성
    4. 페이지가 존재하지 않으면 Page Fault 예외 발생 → OS가 디스크에서 해당 페이지를 읽어옴
🔹 TLB (Translation Lookaside Buffer): 자주 참조되는 PTE를 캐싱하여 주소 변환 속도 향상

9.6.1 캐시와 VM의 통합

VM을 주소지정된 캐시와 물리적으로 통합하기

9.6.2 TLB를 사용한 주소 번역 속도의 개선

TLB 접근 VA 컴포넌트

번역 참조 버퍼Translation Lookaside Buffer(이하 TLB)는 각 줄이 단일 PTE으로 구성된 작은 가상 주소 캐시이다. TLB는 일반적으로 높은 연관성을 가진다. 위 그림에서 보듯이, 세트 선택과 줄 일치를 위해 사용되는 인덱스와 태그 필드는 가상 주소의 가상 페이지 번호(VPN)에서 추출된다. TLB에 T = 2^t 세트가 있는 경우, TLB 인덱스(TLBI)는 VPN의 t 비트 중의 LSB로 구성되며, TLB 태그(TLBT)는 VPN의 나머지 비트로 구성된다.

TLB Hit와 TLB Miss의 동작 모습

9.6.3 다중 레벨 페이지 테이블

2단계 페이지 테이블 계층구조 (주소는 위에서 아래로 증가함에 유의하자.)

페이지 테이블을 압축하는 보편적인 방법은 대신 계층화된 페이지 테이블을 사용하는 것이다. 이 아이디어는 구체적인 예로 쉽게 이해할 수 있다. 32비트 가상 주소 공간이 4KB 페이지로 나뉘고, 페이지 테이블 항목이 각각 4바이트라고 가정해 보자. 이 시점에서 가상 주소 공간은 다음과 같은 형태를 가진다: 첫 2K 페이지는 코드와 데이터에 할당되고, 다음 6K 페이지는 할당되지 않으며, 다음 1,023 페이지도 할당되지 않고, 다음 페이지는 사용자 스택에 할당된다. 위 그림은 이 가상 주소 공간에 대해 2단계 페이지 테이블 계층 구조를 구성하는 방법을 보여준다.

 

이 방식은 메모리 요구 사항을 두 가지 방법으로 줄인다.

  1. 레벨 1 테이블의 PTE가 null인 경우, 해당 레벨 2 페이지 테이블은 존재할 필요가 없다. 이는 일반적인 프로그램의 4GB 가상 주소 공간 대부분이 할당되지 않기 때문에 상당한 잠재적 절약을 나타낸다.
  2. 항상 메인 메모리에 있어야 하는 것은 레벨 1 테이블뿐이다. 레벨 2 페이지 테이블은 필요할 때 VM 시스템에 의해 생성되고 페이지 인/아웃되며, 이는 메인 메모리에 대한 압력을 줄인다. 가장 많이 사용되는 레벨 2 페이지 테이블만 메인 메모리에 캐시하면 된다.

k단계 페이지 테이블을 사용한 주소 번역

9.6.4 종단 주소 번역

작은 메모리 시스템에 대한 주소지정

더보기

1️⃣ 가상 주소(VA)와 물리 주소(PA)

VM 시스템에서는 CPU가 직접 물리 주소를 사용하지 않는 대신, 가상 주소(VA)를 생성하고 MMUMemory Management Unit가 이를 물리 주소(PA)로 바꿔주는 과정을 수행한다.

  • VA = VPNVirtual Page Number + VPOVirtual Page Offset
  • PA = PPNPhysical Page Number + PPOPhysical Page Offset

 여기서 VPO = PPO이므로 VPN → PPN 변환이 핵심이다.

2️⃣ 주소 변환의 전체 흐름 (Simplified Process)

  1. PU가 VA를 생성함 (ex. 어떤 배열 접근 등)
  2. MMU가 VA를 VPNVPO로 나눔
  3. VPN을 기반으로 페이지 테이블에서 PTE(Page Table Entry)를 찾음
  4. PTE에는 PPN(물리 페이지 번호)와 권한 비트(READ, WRITE 등)가 있음
  5. PPN + VPO를 합쳐서 PA를 생성함
  6. 이 PA를 사용해서 메모리에 접근함

3️⃣ 페이지 테이블(Page Table)

  • 프로세스마다 존재하는 테이블
  • VPN → PPN 매핑 정보 + 권한 비트
  • 각 항목(PTE)은 다음을 포함:
    • Valid Bit: 유효한 페이지인지
    • Permission Bits: 읽기/쓰기/실행 가능한지
    • PPN: 물리 주소 상의 위치

4️⃣ TLB (Translation Lookaside Buffer)

  • 문제 : 주소 변환을 위해 매번 페이지 테이블을 접근하면 속도가 느리다
  • 해결책 : MMU는 TLB라는 캐시를 사용하여 최근 주소 변환 결과를 저장해둔다.
  • TLB의 특징:
    • CPU 내부에 있음 (L1 수준)
    • 매우 빠름 (1~2 사이클)
    • 제한된 엔트리 수 (수십 개 정도)
    • Miss 시 페이지 테이블을 직접 접근해야 함 → 느려짐

5️⃣ Multi-Level Page Tables (다중 레벨 페이지 테이블)

  • 문제 : 64-bit 시스템에서 페이지 테이블을 평면(flat)으로 구성하면 너무 커진다.
  • 해결책 : 페이지 테이블도 페이지 단위로 나누어 다단계로 구성한다.
  • 예시: 4단계 페이지 테이블 (x86-64)
    • VA = [VPN₃ | VPN₂ | VPN₁ | VPN₀ | VPO]
    • 변환 과정:
      1. VPN₃ → Page Directory Pointer Table
      2. VPN₂ → Page Directory
      3. VPN₁ → Page Table
      4. VPN₀ → PTE
      5. PTE에서 PPN → PA 완성
  • 장점 : 
    • 공간 절약: 사용되는 경로만 메모리에 올림
    • 접근은 느려질 수 있지만, TLB와 캐시로 해결

6️⃣ Page Fault (페이지 폴트)

  • VA에 해당하는 PTE가 invalid하거나, 페이지가 아직 메모리에 올라와 있지 않다면 Page Fault 발생 (예외 interrupt)
  • 처리 과정:
    1. CPU가 OS에 제어권을 넘김
    2. OS는 디스크에서 해당 페이지를 메모리로 적재
    3. 페이지 테이블 갱신
    4. fault 발생한 명령어 재시도
  • 이 과정을 통해 demand paging 구현이 가능하다.

7️⃣ Caching과의 통합 (Caches and VM)

  • 문제 : 물리 주소로 캐시를 인덱싱? 가상 주소로 캐시를 인덱싱?
  • 해결 전략 :
    • Virtually Indexed, Physically Tagged (VIPT) 캐시 사용
    • L1은 VIPT, L2는 PA 기반 캐시 사용이 일반적
    • TLB miss와 cache miss를 동시에 고려해야 함

✅ 요약 다이어그램

VA = [VPN | VPO]
         |
         ↓
Page Table (in memory)
         ↓
TLB hit? → 빠르게 PPN
TLB miss? → Page Table walk
         ↓
PPN + VPO → PA
         ↓
Access physical memory!

9.7 인텔 코어 i7/리눅스 메모리 시스템

🔹 Core i7의 가상 메모리 구조는 48비트 VA, 52비트 PA를 사용하며, 4단계 페이지 테이블로 구성한다.
🔹 Linux는 demand paging, copy-on-write, mmap 등을 활용하여 효율적 메모리 사용과 프로세스 간 공유를 구현한다.

9.7.1 코어 i7에서의 주소 번역

Core i7의 메모리 시스템
코어 i7 주소 번역 과정의 요약

9.7.2 Linux VM 시스템

리눅스 프로세스의 가상메모리

리눅스 VM 영역

리눅스는 가상 메모리를 영역(세그먼트라고도 함)의 집합으로 구성된다. 하나의 영역은 어떤 방식으로든 관련된 페이지로 구성된 연속적인 할당된 가상 메모리 덩어리이다. 예를 들어, 코드 세그먼트, 데이터 세그먼트, 힙, 공유 라이브러리 세그먼트, 사용자 스택 등이 모두 별개의 영역이다. 각 기존 가상 페이지는 어떤 영역에 포함되어 있으며, 어떤 영역의 일부가 아닌 가상 페이지는 존재하지 않으며 프로세스에서 참조할 수 없다. 영역의 개념은 가상 주소 공간에 간격을 허용하기 때문에 중요하다. 커널은 존재하지 않는 가상 페이지를 추적하지 않으며, 이러한 페이지는 메모리, 디스크 또는 커널 자체에서 추가 자원을 소모하지 않는다.

Linux가 VM을 구성하는 방식

위 그림은 프로세스의 가상 메모리 영역을 추적하는 커널 데이터 구조를 보여준다. 커널은 시스템의 각 프로세스에 대해 별도의 작업 구조체(task_struct)를 유지한다. 작업 구조체의 요소들은 커널이 프로세스를 실행하는 데 필요한 모든 정보를 포함하거나 가리킨다(예: PID, 사용자 스택에 대한 포인터, 실행 파일의 이름, 프로그램 카운터 등).

 

리눅스 페이지 오류 예외 처리

MMU가 어떤 가상주소 A를 번역하려고 하는 동안에 페이지 오류를 유발한다고 가정하자. 이 예외는 커널의 페이지 오류 핸들러로 제어를 이동하게 되며, 이후에 다음과 같은 단계를 수행한다.

  1. 가상주소 A가 합법적인가? (A가 정의된 영역 내에 위치하는가?)
  2. 시도한 메모리 접근이 합법적인가? (이 프로세스는 이 영역에서 페이지들을 읽고 쓰고 실행할 허가를 가지고 있는가?)
  3. 커널은 희생자 페이지를 선택해서 오류를 처리하고, 만약 이 페이지가 dirty하다면 희생자 페이지를 스와핑 아웃하고 새 페이지를 스와핑 인하고 페이지 테이블을 갱신한다. 페이지 오류 핸들러가 리턴할 때, CPU는 오류 인스트럭션을 재시작하고 A를 MMU로 다시 보낸다. 이 때 MMU는 A를 페이지 오류 없이 정상적으로 번역한다.

리눅스 페이지 오류 핸들링

9.8 메모리 매핑

리눅스는 VM 영역의 내용을 디스크의 객체에 연결해서 초기화한다. 이 과정을 메모리 매핑Memory mapping이라고 한다. 영역들은 리눅스 파일 시스템 내의 일반 파일 또는 무기명 파일 중 하나로 매핑될 수 있다. 어떤 경우든지 일단 가상 페이지가 초기화되면, 그것은 커널에 의해 유지되는 특별한 스왑 파일 사이에서 교환된다. 이 스왑 파일은 스왑 공간 또는 스왑 영역이라고도 알려져 있다. 중요한 점은, 현재 실행 중인 프로세스가 할당할 수 있는 총 가상 페이지 수가 언제든지 스왑 공간에 의해 제한된다는 것이다.

🔹 Memory Mapping은 파일의 내용을 메모리에 직접 매핑하는 방식. 디스크에서 데이터를 복사하지 않고 페이지 단위로 접근 가능하다.
🔹 mmap 시스템 호출을 통해 사용자 수준에서 특정 파일을 메모리에 매핑 가능하다.
🔹 이는 파일 입출력, 공유 라이브러리 로딩, 프로세스 간 메모리 공유 등에 활용된다.

9.8.1 공유 객체

객체는 가상 메모리의 영역에 공유 객체 또는 사적 객체로 매핑될 수 있다. 프로세스가 공유 객체를 자신의 가상 주소 공간의 영역에 매핑하면, 해당 영역에 대한 프로세스의 쓰기 작업은 그 공유 객체를 매핑한 다른 프로세스에서도 볼 수 있다. 또한, 이러한 변경 사항은 디스크의 원본 객체에도 반영된다. 반면, 사적 객체에 매핑된 영역에 대한 변경 사항은 다른 프로세스에 보이지 않으며, 프로세스가 해당 영역에 대한 쓰기를 수행해도 디스크의 객체에는 반영되지 않는다. 공유 객체가 매핑된 가상 메모리 영역은 공유 영역이라고 불리며, 마찬가지로 사적 객체에 대한 영역은 사적 영역이라고 한다.

공유 객체: (a) 프로세스 1이 공유 객체를 매핑한 후 (b) 프로세스 2가 동일한 공유 객체를 매핑한 후

사적 객체들은 copy-on-write라는 기법을 사용해서 VM에 매핑된다. copy-on-write는 마지막 가능한 순간까지 사적 객체 내에서 페이지를 복사하는 것을 지연시켜서 부족한 물리 메모리를 가장 효율적으로 사용한다. 

사적 copy-on-write 객체: (a) 두 프로세스가 모두 사적 copy-on-write 객체를 매핑한 후

(b) 프로세스 2가 사적 영역에 쓰기 작업을 수행한 후

9.8.2 fork 함수

현재 프로세스가 fork 함수를 호출하면, 커널은 새로운 프로세스를 위해 다양한 데이터 구조를 생성하고 고유한 PID를 할당한다. 새로운 프로세스의 가상 메모리를 생성하기 위해, 현재 프로세스의 mm_struct, 영역 구조체, 페이지 테이블과 동일한 복사본을 만든다. 두 프로세스의 각 페이지는 읽기 전용으로 플래그가 설정되고, 각 영역 구조체는 사적 copy-on-write로 플래그가 설정된다.

새로운 프로세스에서 fork가 리턴되면, 새 프로세스는 fork가 호출되었을 때의 가상 메모리의 동일한 복사본을 가지게 된다. 이후 두 프로세스 중 어느 하나가 쓰기 작업을 수행하면, copy-on-write 메커니즘이 새로운 페이지를 생성하여 각 프로세스의 사적 주소 공간에 대한 추상화를 유지한다.

9.8.3 execve 함수

execve("a.out", NULL, NULL);

execve 함수는 현재 프로세스 내에서 현재 프로그램을 효과적으로 교체하면서 실행 목적파일 a.out에 포함된 프로그램을 다음 단계에 거쳐 실행하고 로드한다.

  1. 기존 사용자 영역을 제거한다.
  2. 사적 영역을 매핑한다.
  3. 공유 영역들을 매핑한다.
  4. PC를 설정한다.

이 프로세스가 다음에 스케줄링되면 엔트리 포인트에서 실행을 시작하게 된다. 리눅스는 필요에 따라 코드와 데이터 페이지를 스왑인한다.

9.8.4 함수를 이용한 사용자수준 메모리 매핑

#include <unistd.h>
#include <sys/mman.h>
void *mmap(void *start, size_t length, int prot, int flags, int fd, off_t offset);

mmap 함수는 커널에 새 가상 메모리 영역을 생성하도록 요청하며, 가능한 한 start 주소에서 시작하도록 한다. 이 함수는 파일 디스크립터 fd로 지정된 연속된 객체들을 새 영역에 매핑한다. 이 연속된 객체들은 length 바이트 크기를 가지며, 파일의 시작부터 offset 바이트 떨어진 곳에서 시작한다. start 주소는 단순히 힌트일 뿐이며, 일반적으로 NULL로 지정된다.

mmap 함수의 시각적 해석

#include <unistd.h>
#include <sys/mman.h>
int munmap(void *start, size_t length);

munmap 함수는 가상 주소 start에서 시작하여 다음 length 바이트로 구성된 영역을 삭제한다. 삭제된 영역에 대한 이후의 참조는 세그멘트 오류를 발생시킨다.