[Project 3] Virtual Memory (2)

혀누·2022년 1월 26일
0

Pintos

목록 보기
11/11
post-thumbnail

Uninitialized page.

핀토스 Virtual Memory 에서 다루는 page의 타입은 세가지다.

  1. Uninit type
  2. Anonymous type
  3. File-backed type

anonymous 와 file backed는 이름에서 어느정도 특성과 사용처를 유추해 낼 수 있지만 uninit type은 쉽사리 떠오르지 않는다.

어떤 역할을 하는지 이해하기 위해서는 Pintos Lazy loading의 큰 그림을 알아야한다.

Project 2 까지 과정에서, User process 를 실행하는데 있어 필요한 모든 데이터는 실행하기전 kernel thread에서 setting 단계에서 모두 물리 메모리로 올렸다. 그리고 올린 데이터를 가리키는 주소와 유저에게 주는 페이지의 주소를 맵핑하는게 pml4 가 하던 역할 이었다. (install_page function)

그러나 이제는 실행에 필요한 모든 데이터를 최초에 물리메모리에 올리지 않고, 필요할때마다 page fault handler 를 통해서 디스크에서 물리 메모리로 올리게 된다. 유저에게 건네주는 페이지의 타입에 따라 추후에 실행될 initialize 함수와 물리메모리에 올리는 swap in 함수가 달라지게 된다. 이러한 페이지들의 준비를 하는 과정이 Uninitialized page 단계라고 생각하면 된다.

모든 타입별로 세세하게 보기 보다는 최초에 Uninit type으로 유저에게 던져주는 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. */
		
		struct page* page = (struct page*)malloc(sizeof(struct page));

        typedef bool (*initializerFunc)(struct page *, enum vm_type, void *);
        initializerFunc initializer = NULL;

        switch(VM_TYPE(type)) {
            case VM_ANON:
                initializer = anon_initializer;
                break;
            case VM_FILE:
                initializer = file_backed_initializer;
                break;
		}

        uninit_new(page, upage, init, type, aux, initializer);

        // page member 초기화
        page->writable = writable;
        // hex_dump(page->va, page->va, PGSIZE, true);

		/* TODO: Insert the page into the spt. */
		return spt_insert_page(spt, page);
	}
err:
	return false;
}

위 함수는 page 를 만들어 spt 에 없는 것을 확인하고 현재는 uninit type이지만 앞으로 어떤 type이 될지를 parameter로 받아서 그 페이지의 struct member 를 넣어준다.

즉, 이 함수의 종료시에 생성되는 페이지는 Uninit type일테지만 유저의 접근이 있을때 page fault 가 발생하고 이후에 멤버로 넣어주었던 page type에 따라 initialize 함수를 실행한다.

이렇게 만들어진 페이지를 유저는 vm-claim-page 함수를 통해 맵핑을 요청한다. SPT에는 initializer 함수에서 넣었었다. 맵핑 하는 과정에서 vm-do-claim-page 에서 결정적으로 물리프레임과 가상 페이지를 맵핑해주고, 직접 연결도 하고, page type에 맞는 swap in 함수를 통해서 디스크 (swap space)에서 물리 메모리로 올려준다.

이때 file backed 페이지의 경우에는 lazy-load-segment 함수를 통해서 비로소 실행 파일 관련 데이터를 디스크에서 물리 메모리로 올리게 된다.

정리하자면 vm-claim-page 는 유저가 전달받은 가상 페이지가 물리 프레임과 맵핑이 안되어 있을때 사용하는 함수이며, lazy-load-segment 는 물리 프레임에 디스크로부터 파일 관련 데이터를 적재(swap-in)하는 함수이다.

그리고 이 모든 것은 유저 프로그램의 페이지 폴트로 부터 비롯된다.

Page fault handling.

페이지폴트에서 도대체 무엇을 하길래 저런 작업들로 올 수 있게 되는 걸까?

바로 vm-try-handle-fault 다.

bool vm_try_handle_fault (struct intr_frame *f UNUSED, void *addr UNUSED,
		bool user UNUSED, bool write UNUSED, bool not_present UNUSED) {
	struct supplemental_page_table *spt UNUSED = &thread_current ()->spt;
	// struct page *page = NULL;
	/* TODO: Validate the fault */
	/* TODO: Your code goes here */
	if (is_kernel_vaddr(addr)) {
        return false;
	}

    void *rsp_stack = is_kernel_vaddr(f->rsp) ? thread_current()->rsp_stack : f->rsp;
    if (not_present){
        if (!vm_claim_page(addr)) {
            if (rsp_stack - 8 <= addr && USER_STACK - 0x100000 <= addr && addr <= USER_STACK) {
                vm_stack_growth(thread_current()->stack_bottom - PGSIZE);
                return true;
            }
            return false;
        }
        else
            return true;
    }
    return false;
}

함수에서 확인할 수 있다싶이 initializer 함수는 없다. 왜냐하면 이미 Uninit type의 페이지를 유저가 받았고 (최초 실행시) 이를 접근했을때 발생하는게 page fault 이기 때문이다. 그래서 코드에서 확인 할 수 있듯이, vm-claim-page (=> 가상 페이지와 물리 프레임을 맵핑) 부터 실행하고 있다.

여기까지가 위의 그림에서 4번까지에 해당하는 과정이다.

이후 5번 과정은 lazy-load-segement 혹은 anon-swap-in 과 같은 각 페이지 타입에 맞춘(File backed type page 가 궁금하다면 ) 함수에 의해 진행된다.

bool lazy_load_segment (struct page *page, void *aux) {
	/* TODO: Load the segment from the file */
	/* TODO: This called when the first page fault occurs on address VA. */
	/* TODO: VA is available when calling this function. */
	struct file *file = ((struct container *)aux)->file;
	off_t offsetof = ((struct container *)aux)->offset;
	size_t page_read_bytes = ((struct container *)aux)->page_read_bytes;
    size_t page_zero_bytes = PGSIZE - page_read_bytes;

	file_seek(file, offsetof);

    if (file_read(file, page->frame->kva, page_read_bytes) != (int)page_read_bytes) {
		palloc_free_page(page->frame->kva);
        return false;
    }
    memset(page->frame->kva + page_read_bytes, 0, page_zero_bytes);

    return true;
}

이렇게 실행에 필요한 데이터를 물리 프레임에 올리고 해당 프레임과 가상 페이지를 맵핑까지 해주고 나면 유저에게 직전 page fault 가 발생했던 instruction 부터 다시 실행하도록 만든다.

profile
개발자(물리)

0개의 댓글