
[목표]
spt 보충 테이블 관련 코드를 직접 구현하면서 vm을 알아가보겠다.
프레임 관련 내용을 이해하고 코드를 작성해봅니다.
익명 페이지 관련된 내용을 이해하고 코드를 작성해봅니다.
spt page_hash부분 재이해하고 작성해야 합니다.
page_less structer(vm.h) 관련된 부분 재확인합니다.
테이블을 구현하는 방식 중에는 배열, 리스트, 비트맵, 해시 테이블 등이 있는데 저같은 경우 해시 테이블을 채택했습니다.
첫번째로, 어제에 이어서 spt 페이지 테이블을 초기화하는 코드를 구현했습니다. 그 과정에서 page_hash와 page_less 함수를 추가적으로 구현했습니다. 각 함수는 다음과 같은 역할을 합니다.
page_hash: 페이지의 가상주소를 바탕으로 고유한 해시값을 생성합니다.
page_less: 해시값이 같아서 충돌될 시 어떤 주소가 작은 가에 대해 비교하는 정렬기준을 제공합니다.
위의 두 함수를 사용하여 spt 테이블을 초기화합니다. 그 과정에서 hash_inti을 사용합니다.
void supplemental_page_table_init (struct supplemental_page_table *spt) {
hash_init(&spt->spt_hash, page_hash, page_less, NULL);
}
식사를 하고 잠을 자다 왔습니다.

spt_find_page를 추가해보도록 하겠습니다.
/* Find VA from spt and return page. On error, return NULL. */
// spt에서 VA를 찾아 페이지를 반환합니다. 오류가 발생하면 NULL을 반환합니다.
struct page *
spt_find_page (struct supplemental_page_table *spt UNUSED, void *va UNUSED) {
struct page *page = NULL;
struct page dummy;
dummy.va = va;
/* TODO: Fill this function. */
// 기능을 구현하세요.
struct hash_elem *e = hash_find(&spt -> spt_hash, &dummy.hash_elem);
if (e == NULL) return NULL;
return hash_entry(e, struct page, hash_elem);
// return page;
}
spt_insert_page 는 다음과 같이 작성해보았습니다.
/* Insert PAGE into spt with validation. */
// 검증을 통해 spt에 PAGE를 삽입합니다.
bool
spt_insert_page (struct supplemental_page_table *spt UNUSED,
struct page *page UNUSED) {
int succ = false;
struct page *va; // page 구조체에 있는 va를 가져온다.
/* TODO: Fill this function. */
// 먼저, 주어진 보충 테이블에 가상주소가 존재하는지 확인해야한다.
struct page *page = spt_find_page(spt, va);
if (page == NULL) {
struct hash_elem *inserted = hash_insert(&spt -> spt_hash, &page -> hash_elem); // 만약, 페이지가 비었다면 spt에 페이지를 삽입합니다.
}
else
return false;
return succ;
}
운동(가슴, 복근)을 진행하고 샤워까지 하고 복귀했습니다.
식사를 하고 약간의 휴식을 취했습니다.

저번spt 구현에 이어서 프레임 구현을 진행했습니다.
먼저 vm_get_frame은 다음과 같이 구현했습니다.
// palloc() 함수는 프레임을 가져옵니다. 사용 가능한 페이지가 없으면 해당 페이지를 제거하고 반환합니다.
// 이 함수는 항상 유효한 주소를 반환합니다.
// 즉, 사용자 풀 메모리가 가득 차면 이 함수는 프레임을 제거하여 사용 가능한 메모리 공간을 가져옵니다.
static struct frame *
vm_get_frame (void) {
struct frame *frame = NULL;
/* TODO: Fill this function. */
void *kva = palloc_get_page(PAL_USER); // 커널이 접근할 수 있는 물리 페이지 주소
if (kva == NULL) { // 사용자 풀에서 페이지를 성공적으로 가져왔는가?
return NULL;
}
struct frame *f = malloc(sizeof(struct frame));
if (f == NULL) {
palloc_free_page(kva);
return NULL;
}
f -> kva = kva;
f -> page = NULL;
lock_acquire(&frame_table_lock);
list_push_back(&frame, &f -> elem);
lock_release(&frame_table_lock);
ASSERT (frame != NULL);
ASSERT (frame->page == NULL);
return f;
}
페이지를 할당합니다. mmu를 설정하고, 가상 주소에서 페이지 테이블의 물리적 주소로의 매핑을 추가하고 bool값으로 여부를 나타냅니다.
/* Claim the PAGE and set up the mmu. */
// PAGE를 선언하고 mmu를 설정하세요.
static bool
vm_do_claim_page (struct page *page) {
struct frame *frame = vm_get_frame ();
if(frame == NULL) {
return false;
}
/* Set links */
frame->page = page;
page->frame = frame;
/* TODO: Insert page table entry to map page's VA to frame's PA. */
// 페이지의 VA를 프레임의 PA에 매핑하기 위해 페이지 테이블 항목을 삽입합니다.
if(!pml4_set_page(thread_current() -> pml4, page -> frame, frame -> kva, true)) {
return false;
}
return swap_in (page, frame->kva);
}
할당할 페이지를 claim 합니다. 페이지를 가져오고 다음 페이지와 함께 vm_do_claim_page를 호출합니다.
/* Claim the page that allocate on VA. */
// VA로 할당된 페이지를 선언합니다.
bool
vm_claim_page (void *va UNUSED) {
struct page *page = NULL;
/* TODO: Fill this function */
// 기능을 구현하세요.
if(page -> va == NULL) return false;
struct page *page = spt_find_page(thread_current() -> spt, va); // spt 페이지를 가져옵니다.
return vm_do_claim_page (page);
}
해당 내용은 밑에서 수정
이제부터 gitbook에 따라 익명페이지에 대해 구현합니다. 설명들을 한번 훑어보고, 코드를 작성해보겠습니다.
vm_alloc_page_with_initializer 을 구현했습니다. 주어진 타입으로 초기화되지 않은 페이지를 생성하고 VM_TYPE 에 따라서 자동으로 페이지를 초기화하고 주어진 aux로 init을 호출합니다. 페이지 구조체 생성을 하면 프로세스의 보조 페이지 테이블에 삽입합니다.
저는 여기서 VM_TYPE에 따라 자동으로 페이지를 초기화해야하는데, 어떤 식으로 구현해야될지 모르겠어서 방황을 많이 했던것 같습니다. 그리고 코드의 TODO에 따르면 uninit_new 를 호출하고 필드를 수정해야한다고 하는데 어떤식으로 호출하고 인자들을 설정할지 고민이 많이 되었습니다. 특히나 appropriate_ops 인자를 설정하는데 문제가 있어 많은 시간을 사용하였습니다.
굉장히 어려운 만큼 이해도 100퍼센트 되지 않은 것 같아 하나하나 살펴보며 코드를 이해해봐야겠습니다.
/* Create the pending page object with initializer. If you want to create a
* page, do not create it directly and make it through this function or
* `vm_alloc_page`. */
// 초기화 함수를 사용하여 보류 중인 페이지 객체를 생성합니다.
// 페이지를 생성하려면 직접 생성하지 말고 이 함수나 `vm_alloc_page`를 통해 생성하세요.
bool
vm_alloc_page_with_initializer (enum vm_type type, void *upage, bool writable,
vm_initializer *init, void *aux) {
ASSERT (VM_TYPE(type) != VM_UNINIT)
struct supplemental_page_table *spt = &thread_current () -> spt;
/* Check wheter the upage is already occupied or not. */
// 이미지가 이미 점유되어 있는지 확인하세요.
if (spt_find_page (spt, upage) == NULL) {
/* TODO: Create the page, fetch the initialier according to the VM type,
* TODO: and then create "uninit" page struct by calling uninit_new. You
* TODO: should modify the field after calling the uninit_new. */
// 해야될 것: 페이지를 생성하고, VM 유형에 따라 초기화 파일을 가져온 후, uninit_new를 호출하여 "uninit" 페이지 구조체를 생성합니다.
// 해야될 것: uninit_new를 호출한 후 필드를 수정해야 합니다.
// 해당 함수는 아직 메모리에 로딩되지 않은 페이지에 대한 메타데이터를 생성해서 SPT에 등록하는게 목적입니다.
enum vm_type real_type = VM_TYPE(type);
const struct page_operations *appropriate_ops;
if (real_type == VM_ANON) {
init = anon_initializer;
appropriate_ops = &anon_ops; // 사용을 위한 anon.h 선언
}
else if (real_type == VM_FILE) {
init = file_backed_initializer;
appropriate_ops = &file_ops; // 사용을 위한 file.h 선언
}
else {
return false; // 지원하지 않는 타입일때
}
struct page *page = malloc(sizeof(struct page)); // page 할당
if (page == NULL)
return false;
uninit_new(page, upage, init, type, aux, appropriate_ops);
/* TODO: Insert the page into the spt. */
// 해당 페이지를 spt에 삽입합니다.
if (!spt_insert_page (spt, page))
return false;
return true;
}
}
기존에는 vm.c를 수정했지만, 이번에는 uninit.c에 있는 코드를 수정해봅니다.
uninit_initialize 을 구현해봤습니다.
| 항목 | uninit 상태 | anon 상태 |
|---|---|---|
| 의미 | 아직 메모리에 로딩되지 않은 "예약된" 페이지 | 실제 메모리에 존재하는 익명 페이지 |
| 실제 프레임 할당 여부 | ❌ 아직 없음 | ✅ 있음 (claim됨) |
| 목적 | lazy loading을 위해 대기 중인 페이지 | 실제로 메모리에서 데이터 보관 |
| 주요 구조체 | page->uninit 사용 | page->anon 사용 |
page_operations | uninit_ops | anon_ops |
| 변환 타이밍 | vm_do_claim_page() → uninit_initialize() 호출 시 | uninit_initialize() 내부에서 vm_anon_init()을 통해 변환 |
🗂️uninit은 “예약된 좌석”이고,
🪑anon은 “사람이 실제로 앉아 있는 좌석”이에요.
상태전이
vm_alloc_page_with_initializer()
↓
[ uninit 상태 생성 → page->operations = &uninit_ops ]
user 접근 → page fault 발생
↓
vm_do_claim_page()
↓
page->operations->swap_in() → uninit_initialize()
↓
page->uninit.page_initializer() → vm_anon_init()
↓
page->operations = &anon_ops (이제 anon 상태!)
요약
| 구분 | uninit | anon |
|---|---|---|
| 아직 메모리에 없음 | ✅ | ❌ |
| 예약 정보만 존재 | ✅ | ❌ |
| 실제 프레임 연결됨 | ❌ | ✅ |
| lazy loading 대상 | ✅ | ❌ (로딩 완료됨) |
uninit_initialize 은 다음과 같이 구현했습니다. 이게 오류 발생시 페이지를 초기화하는데, 그 과정에서 페이지를 해제하는 기능까지 포함시켰습니다.
static bool
uninit_initialize (struct page *page, void *kva) {
struct uninit_page *uninit = &page->uninit;
/* Fetch first, page_initialize may overwrite the values */
// Fetch를 먼저하세요. page_initialize가 값을 덮어쓸 수 있습니다.
// 필요한 값 먼저 복사
vm_initializer *init = uninit->init;
void *aux = uninit->aux;
// 타입에 맞게 페이지 초기화
if (!uninit -> page_initializer(page, uninit->type, kva))
return false;
// 사용자 정의 초기화 함수가 있다면 호출
if (init != NULL) {
if (!init(page, aux)) {
palloc_free_page(page -> frame -> kva); // 오류 발생시 페이지 해제
return false;
}
}
return true;
/* TODO: You may need to fix this function. */
// 이 기능을 수정해야 할 수도 있습니다.
// return uninit->page_initializer (page, uninit->type, kva) &&
// (init ? init (page, aux) : true);
}
이제는 anon.c에 있는 vm_anon_init 를 수정하면 됩니다.
밑에 사진은 날이 좋아서 찍어봤습니다.
