유저프로그램까지의 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 매핑된 커널 가상 주소를 사용함
- 즉, va → pa 변환은 커널 내부에서도 간접적으로 이뤄짐
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 정보를 저장만 해둠
용어정리나 기본적인 정리는 이정도로 끝내면 될 것 같고
다음은 흐름 따라가기로 찾아오겠읍니다
'크래프톤정글 > Pintos' 카테고리의 다른 글
| lazy_load_segment? uninit_initialize? (0) | 2025.05.31 |
|---|---|
| 누나랑 페이지폴트 흐름 따라갈래? (1) | 2025.05.30 |
| User Program _System Call(테스트 별 정리_read-nomal, bad, syn, rox) (0) | 2025.05.25 |
| User Program _System Call(exit, write, open, close, create, remove) (0) | 2025.05.21 |
| User Program _System Call(filesize, read, seek, tell) (0) | 2025.05.19 |