WEEK 12 PintOS TIL(6월3일 화요일)

Devkty·2025년 6월 4일
post-thumbnail

[목표]
spt 보충 테이블 관련 코드를 직접 구현하면서 vm을 알아가보겠다.
프레임 관련 내용을 이해하고 코드를 작성해봅니다.
익명 페이지 관련된 내용을 이해하고 코드를 작성해봅니다.

spt page_hash부분 재이해하고 작성해야 합니다.
page_less structer(vm.h) 관련된 부분 재확인합니다.

09:55 ~ 12:00

테이블을 구현하는 방식 중에는 배열, 리스트, 비트맵, 해시 테이블 등이 있는데 저같은 경우 해시 테이블을 채택했습니다.

첫번째로, 어제에 이어서 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);
}

12:00 ~ 14:00

식사를 하고 잠을 자다 왔습니다.

14:00 ~ 16:30

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;
}

16:30 ~ 18:00

운동(가슴, 복근)을 진행하고 샤워까지 하고 복귀했습니다.

18:00 ~ 19:00

식사를 하고 약간의 휴식을 취했습니다.

19:00 ~ 21:20

저번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);
}

해당 내용은 밑에서 수정

21:20 ~ 00:15

이제부터 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;
	}
}

00:15 ~ 03:00

기존에는 vm.c를 수정했지만, 이번에는 uninit.c에 있는 코드를 수정해봅니다.
uninit_initialize 을 구현해봤습니다.

항목uninit 상태anon 상태
의미아직 메모리에 로딩되지 않은 "예약된" 페이지실제 메모리에 존재하는 익명 페이지
실제 프레임 할당 여부❌ 아직 없음✅ 있음 (claim됨)
목적lazy loading을 위해 대기 중인 페이지실제로 메모리에서 데이터 보관
주요 구조체page->uninit 사용page->anon 사용
page_operationsuninit_opsanon_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 상태!)

요약

구분uninitanon
아직 메모리에 없음
예약 정보만 존재
실제 프레임 연결됨
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 를 수정하면 됩니다.
밑에 사진은 날이 좋아서 찍어봤습니다.

profile
모든걸 기록하며 성장하고 싶은 개발자입니다. 현재 크래프톤 정글 8기를 수료하고 구직활동 중입니다.

0개의 댓글