VM 시작

유저프로그램까지의 pintos는 모든 사용자 프로세스가 물리 메모리에 올라갔다

BUT

현실의 운영체제는 그렇지 않다!

-> 그래서 이젠 필요한것이 Virtual Memory(가상 메모리)

 

문제 상황:

  • 다수의 프로세스가 실행될 경우 물리 메모리 부족
  • 메모리에 올릴 수 없는 프로세스는 디스크에 저장하고 나중에 불러와야 함 -> 너무 오래걸려요..
  • 접근하지 않은 페이지는 실제로 메모리에 올리지 않아도 됨 (Lazy Loading)

 

해결해야 할 핵심 과제:

  • 메모리에 없는 페이지에 접근할 경우 처리할 Page Fault 핸들링
  • 스택과 같은 영역은 자동 확장할 수 있어야 함
  • 메모리가 부족할 경우 스왑 아웃 / 스왑 인이 필요
  • 가상 주소 ↔ 물리 주소 매핑을 위한 SPT (보조 페이지 테이블) 필요

기본 개념

1. 가상 메모리(Virtual Memory)

CPU는 가상 주소로 메모리에 접근하고, MMU가 이를 물리 주소로 변환해준다

  • 사용자 프로세스는 자신의 고유한 가상 주소 공간을 가짐
  • 이 주소는 실제 물리 메모리에 직접 연결되지 않고, 페이지 테이블을 통해 간접적으로 연결됨
  • 이 구조는 프로세스 간 메모리 보호, 동적 할당, Lazy Loading스왑 같은 고급 기능을 가능하게 해준다!~~

 

2. 페이지(Page)와 프레임(Frame)

 

 페이지 (Page)

  • 가상 메모리에서의 최소 단위 (보통 4KB = 4096B)
  • 항상 페이지 정렬됨 (주소가 4096으로 나누어 떨어짐)
  • 64비트 주소에서 하위 12비트는 오프셋, 상위 비트는 페이지 번호 역할
63-48  | 47-39   | 38-30   | 29-21   | 20-12   | 11-0
SignEx | PML4Off | PDPTOff | PDOff   | PTOFF   | Offset


 프레임 (Frame)

  • 물리 메모리에서의 최소 단위
  • 크기와 정렬 방식은 페이지와 동일
  • 커널은 물리 메모리에 직접 접근할 수 없기 때문에 1:1 매핑된 커널 가상 주소를 사용함
  • 즉, vapa 변환은 커널 내부에서도 간접적으로 이뤄짐

 

3. 페이지 테이블 (Page Table)

가상 주소를 물리 주소로 바꾸는 맵핑 구조체

  • Pintos에서는 4단계 계층 구조 (x86-64 구조)
63-48  | 47-39   | 38-30   | 29-21   | 20-12   | 11-0
SignEx | PML4Off | PDPTOff | PDOff   | PTOFF   | Offset
  • 매 레벨마다 가상 주소를 쪼개 인덱스로 접근 → 최종적으로 PTE에서 물리 프레임 번호를 얻음
  • Pintos에서는 pml4e_walk()와 같은 유틸리티 함수로 이 구조를 관리

 

4. Supplemental Page Table (SPT)

기존의 하드웨어 페이지 테이블은 부족하다!

Pintos는 SPT라는 소프트웨어 기반 테이블을 추가로 만들어 사용함

 

왜 필요한가?

  • OS 입장에서 “아직 페이지가 메모리에 없음” → 페이지 폴트 발생
  • 그럴 때 우리가 해당 주소가 파일에서 lazy load 해야 하는지,아니면 진짜 오류인지 판단할 수 있어야 함
  • 아니면 stack 확장인지,
  • 이 판단은 SPT에서 수행함

SPT에는 어떤 정보가 있어야 할까?

항목 설명
va 어떤 가상 주소에 대한 페이지인지
타입 uninit, anon, file 중 하나
writable 쓰기 가능한 페이지인지 여부
프레임 연결 정보 실제 물리 메모리와 매핑되었는지 여부
스왑 정보 스왑된 경우 어떤 슬롯인지
초기화 함수 lazy load를 위한 init 함수
aux 정보 파일 정보, 오프셋 등 부가 정보

 

5. 프레임 테이블 (Frame Table)

물리 메모리 프레임의 사용 현황을 전역적으로 추적하기 위한 테이블

  • 어떤 페이지가 어떤 프레임을 쓰고 있는지 관리
  • 프레임이 부족할 경우, 교체 알고리즘(FIFO, CLOCK 등) 을 사용해 스왑 대상 선정
  • 매핑된 페이지의 역참조가 필요하기 때문에, 각 프레임은 struct page *page 필드 가짐

 

6. 스왑 테이블 (Swap Table)

디스크에 있는 스왑 슬롯을 할당/회수/매핑 관리

 

스왑? :

메모리가 부족하면 디스크로 잠깐 빼놓고,

필요할 때 다시 데려오는 것이 스왑

 

스왑 아웃 (Swap-out)

 

  • 메모리 부족해서 자리를 만들어야 할 때
  • 안 쓰는 페이지를 디스크로 보냄
  • 디스크 안에 페이지 한 칸짜리 상자들이 있음 (이걸 “스왑 슬롯”이라 함)
  • 어떤 상자에 넣었는지 기억해둬야 나중에 찾을 수 있다우

스왑 인 (Swap-in)

  • 스왑아웃 된 페이지가 필요한데 지금 메모리에 없음 (페이지 폴트!)
  • “아 맞다 너 전에 디스크 보냈었지?” 하고 꺼내옴
  • 다시 메모리에 올려놓고 연결해줘

 

  • 스왑 슬롯: 디스크에서 페이지 크기(4KB) 만큼의 공간
  • 스왑 아웃 시 anon_page에 기록되고
  • 스왑 인 시 swap-in 과정을 거쳐 프레임으로 복귀
  • 보통 bitmap으로 슬롯 사용 여부 추적

전체 흐름 요약

[User Process]
    |
    | page fault (unmapped addr)
    ↓
[SPT 확인]
    └─ 없으면 → invalid access (segfault)
    └─ 있으면 → vm_do_claim_page()

        ├─ uninit → lazy loading → anon/file로 전환
        ├─ anon   → 스왑에서 복구
        └─ file   → 파일에서 로드

    ↓
[Frame Table]
    └─ 프레임 할당 or 교체

    ↓
[PML4 설정]
    └─ 가상주소 ↔ 물리주소 연결

    ↓
[유저 접근 재개]

 

핵심 개요

컴포넌트 역할
SPT 유저 페이지에 대한 정보를 저장하는 테이블
Frame Table 현재 메모리에 로드된 페이지 (frame)의 목록
Swap Table 스왑 아웃된 페이지의 위치를 저장
Page Table(PML4) 가상주소 → 물리주소 매핑 관리 (하드웨어 레벨)

구조체 정리:

struct page - 가상 메모리 페이지의 메타 데이터

/* "page"의 표현입니다.
 * 이것은 일종의 "부모 클래스"로, 네 개의 "자식 클래스"를 가집니다:
 * uninit_page, file_page, anon_page, 그리고 페이지 캐시(project4).
 * 이 구조체의 미리 정의된 멤버는 제거/수정하지 마세요. */
struct page
{
	const struct page_operations *operations;
	void *va;			 /* 사용자 공간 기준의 주소 */
	struct frame *frame; /* frame에 대한 역참조 */

	/* 구현 필드 */
	bool writable;

	/* 타입별 데이터는 union에 바인딩됩니다.
	 * 각 함수는 현재 union을 자동으로 감지합니다. */
	union
	{
		struct uninit_page uninit;
		struct anon_page anon;
		struct file_page file;
#ifdef EFILESYS
		struct page_cache page_cache;
#endif
	};
};

struct frame - 물리 프레임에 대한 메타 데이터이자 프레임 테이블 요소

/* The representation of "frame" */
struct frame {
    void *kva;           // 커널 가상 주소 (실제로 커널이 접근할 수 있는 물리 메모리)
    struct page *page;   // 이 프레임을 사용하는 가상 페이지 (역참조)
};

  • vm_get_frame()에서 새로운 프레임을 할당할 때 struct frame을 하나 만들고
  • 내부적으로 커널 가상 주소(kva)에 해당하는 물리 메모리를 palloc_get_page()로 얻고
  • frame_table (리스트 or 해시 등)에 이 struct frame을 등록해서 관리함
  • 즉, 프레임 테이블은 struct frame들의 컨테이너
/* 페이지 작업을 위한 함수 테이블입니다.
 * 이는 C에서 "인터페이스"를 구현하는 한 가지 방법입니다.
 * "메서드"의 테이블을 구조체의 멤버에 넣고,
 * 필요할 때마다 호출하면 됩니다. */
struct page_operations
{
	bool (*swap_in)(struct page *, void *);
	bool (*swap_out)(struct page *);
	void (*destroy)(struct page *);
	enum vm_type type;
};
//함수 포인터
  • 이것은 페이지 객체가 어떤 타입이든 간에,
    • 어떻게 swap_in 할지
    • 어떻게 swap_out 할지
    • 어떻게 destroy 할지를
    • 타입별로 달리 정의할 수 있게 해주는 인터페이스

관련 매크로 - 이로 하여금 객체지향의 다형성을 흉내냄

#define swap_in(page, v) (page)->operations->swap_in((page), v)
#define swap_out(page) (page)->operations->swap_out(page)
#define destroy(page)                \
	if ((page)->operations->destroy) \
	(page)->operations->destroy(page)

 

 


페이지 별 설명

1. uninit_page : 아직 초기화되지 않은 페이지

 

목적:

Lazy Loading을 위한 가짜 페이지

→ 실제로 데이터는 메모리에 없지만, SPT에는 등록되어 있음

 

예시 상황:

코드 segment, 데이터 segment, 스택 등

처음에는 load_segment() 시점에 uninit으로 등록만 해두고,

사용자가 접근할 때 진짜로 swap_in 되면서 메모리에 로딩됨

/* 초기화되지 않은 페이지 (Uninitialized page).
 * Lazy Loading을 구현하기 위한 타입입니다. */
struct uninit_page
{
    /* 실제 데이터를 메모리에 채워넣는 초기화 함수입니다.
     * swap_in 시 호출되어, 페이지를 파일에서 읽거나 0으로 채우는 등의 작업을 수행합니다. */
    vm_initializer *init;

    /* 실제로 이 페이지가 어떤 타입으로 전환될지를 나타냅니다.
     * 예: VM_ANON, VM_FILE 등 → lazy load 이후 이 타입으로 바뀜 */
    enum vm_type type;

    /* 초기화 함수에 넘길 추가적인 인자.
     * 예: 파일 정보, 읽을 오프셋 등 lazy load를 위한 context */
    void *aux;

    /* struct page 자체를 초기화하고, 
     * 해당 가상 주소(va)에 물리 주소(pa)를 매핑하는 함수입니다.
     * 일반적으로 anon_initializer, file_backed_initializer가 들어갑니다. */
    bool (*page_initializer)(struct page *, enum vm_type, void *kva);
};

주요 동작:

  • vm_do_claim_page()에서 실제로 접근되면,
  • uninit_initialize() 호출 → page_initializer() 실행 → anon/file 타입으로 변신
  • 이후 swap_in()도 그 타입에 맞게 덮어씀
static const struct page_operations uninit_ops = {
	.swap_in = uninit_initialize,   // 초기화 시 실제 타입으로 전환
	.swap_out = NULL,               // swap-out 안 함 (아직 초기화도 안 됐음)
	.destroy = uninit_destroy,      // 초기화 구조 정리
	.type = VM_UNINIT
};
  • 이 타입은 초기화 전이기 때문에 swap-out은 불가능하고,
  • swap-in 시 uninit_initialize()에서 page_initializer() 호출하여 anon이나 file로 전환됨

 

2. anon_page : 익명 페이지

 목적:

파일에 연결되지 않은 일반적인 데이터용 페이지

예: heap, stack, malloc 등

 

예시 상황:

  • vm_stack_growth()로 확장한 스택 페이지
  • malloc처럼 동적 할당된 힙 영역

수정필요

구조체 구성 (anon.c):

struct anon_page
{
};

예 텅텅 비어있조?

설계에 따라 수정이 필요하답니다

 

static const struct page_operations anon_ops = {
	.swap_in = anon_swap_in,       // 디스크 → 메모리 복원
	.swap_out = anon_swap_out,     // 메모리 → 디스크 저장
	.destroy = anon_destroy,       // 스왑 슬롯 반환 등
	.type = VM_ANON
};
  • 스왑 디스크를 직접 사용하는 유일한 페이지 타입 → swap_in/out 구현이 꼭 필요함

 

3. file_page : 파일에서 매핑된 페이지

목적:

파일의 내용을 직접 메모리에 매핑하는 페이지

(ELF 파일의 code segment, data segment)

 

예시 상황:

  • load_segment()에서 실제로 파일을 읽어 들일 필요가 있는 페이지
  • 메모리 맵핑 (mmap) 기능에도 사용

수정필요

구조체 구성 (file.c):

struct file_page
{
	/* dirty bit가 명시적으로 필요한가??
	어차피 PTE의 dirty bit가 있는데  */

	// file_backup 정보를 필드로 두어도 될듯함
};

네 여기도 텅텅 비어있죠?

수정이 필요합니다

static const struct page_operations file_ops = {
	.swap_in = file_backed_swap_in,   // 파일에서 읽기
	.swap_out = file_backed_swap_out, // dirty 시 파일에 저장
	.destroy = file_backed_destroy,   // 매핑 해제 처리
	.type = VM_FILE
};
  • 스왑 디스크는 안 씀
  • 대신 백업 대상이 파일이므로 dirty 체크 필요

 

초기화 시점: vm_alloc_page_with_initializer()

vm_alloc_page_with_initializer(VM_FILE, addr, writable,
                               lazy_load_segment, aux);
  • 이 시점에는 아직 메모리 할당도 안 됨
  • 대신 SPT에 uninit 페이지가 등록됨
  • uninit->page_initializer와 aux 정보를 저장만 해둠

용어정리나 기본적인 정리는 이정도로 끝내면 될 것 같고

다음은 흐름 따라가기로 찾아오겠읍니다