PintOS

PintOS Project 3 : Virtual Memory(2) - file load , page fault 다루기

wook's note 2024. 10. 1. 22:29

VM 프로젝트 두 번째 포스팅은 프로세스의 execute 과정을 알아보려고 한다.

프로세스의 exec는 user가 exec라는 시스템 콜을 커널에게 보내고 이를 받아서 커널이 process_exec 함수를 실행한다.

process_exec 함수 내에 load 함수가 있는데 이는 실행가능 목적파일의 ELF 데이터들을 프로세스의 가상메모리로 로딩하는 함수이다.

- CSAPP 7장 링커 참고

ELF binary file
virtual memory

 

 

대부분의 정보는 pintOS 스켈레톤 코드에 로딩하는 코드가 있지만, User stack 과 kernel memory에 User Pool 부분에서 해주어야 하는 부분이 있다. 

 

 

 

process_exec의 흐름을 살펴보면 다음 그림과 같다.

function flow

 

 

메모리를 효율적으로 관리하기 위해 demand paging일 때 가상 메모리와 물리 메모리를 매핑한다. 

가상 메모리에 있는 페이지 단위의 세그먼트를 보조 페이지 테이블(spt)에 uninit type page 으로 넣어두고 #PF가 발생할 때, uninit page를 담아둔 aux를 사용하여 type에 맞게 initialize하며 가상메모리와 물리메모리를 완전히 매핑하는 것이다.

 

예를 들면 load_segment 함수에서 함수 포인터에 lazy_load_segment 함수를 넣고, aux에 lazy_load_argument를 넣어둔다.  이후 vm_alloc_page_with_initializer을 실행하는데, 여기서 uninit_new()함수에서 page구조체에 정보를 넣어주는 것을 포켓몬 이브이(uninit page)가 번개의돌( lazy_load_argument )을 가지고 쥬피썬더(anon page or file backed page)로 진화(lazy_load_segment)하는 것이라고 생각할 수 있다.

 

여기서 잡아야 할 개념은 
- page fault가 왜 발생하는지 ?
- page fault가 어떤 용도로 사용되는지 ?
- 처음 page를 할당할 때 uninit page으로 할당하고 type에 따라 어떻게 initialize가 되는지?
- vm_do_claim 함수가 어떻게 진행되는지?

에 대한 답을 할 수 있어야 한다.

 

 


 

 

코드구현

 

 

// load 에서 호출하여 lazy_load_arg에 file에 대한 정보를 담아두는 함수
static bool load_segment (struct file *file, off_t ofs, uint8_t *upage,
		uint32_t read_bytes, uint32_t zero_bytes, bool writable) {
	ASSERT ((read_bytes + zero_bytes) % PGSIZE == 0);
	ASSERT (pg_ofs (upage) == 0);
	ASSERT (ofs % PGSIZE == 0);

	while (read_bytes > 0 || zero_bytes > 0) {
		/* Do calculate how to fill this page.
		 * We will read PAGE_READ_BYTES bytes from FILE
		 * and zero the final PAGE_ZERO_BYTES bytes. */
        size_t page_read_bytes = read_bytes < PGSIZE ? read_bytes : PGSIZE;
        size_t page_zero_bytes = PGSIZE - page_read_bytes;
		/* TODO: Set up aux to pass information to the lazy_load_segment. */
		struct lazy_load_arg *lazy_load_arg = (struct lazy_load_arg*)malloc(sizeof(struct lazy_load_arg));

		lazy_load_arg->file = file;
		lazy_load_arg->offset = ofs;
		lazy_load_arg->length = page_read_bytes;

		if (!vm_alloc_page_with_initializer (VM_ANON, upage,
					writable, lazy_load_segment, lazy_load_arg))
			return false;

		/* Advance. */
		read_bytes -= page_read_bytes;
		zero_bytes -= page_zero_bytes;
		upage += PGSIZE;
		ofs += page_read_bytes;
	}
	return true;
}

 

 

//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 *p = (struct page *)malloc(sizeof(struct page));

		bool (*page_initializer) (struct page *, enum vm_type, void *kva);

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

		uninit_new(p,upage,init,type,aux,page_initializer);

		p->writable = writable;

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

 

 

bool spt_insert_page (struct supplemental_page_table *spt UNUSED,
		struct page *page UNUSED) {
	int succ = false;
	/* TODO: Fill this function. */

	if(hash_insert(&spt->hash_table,&page->hash_elem) == NULL)
		succ = true;

	return succ;
}

 

 

# page 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 = spt_find_page(&thread_current()->spt, addr);

    /* TODO: Validate the fault */
    if (addr == NULL || is_kernel_vaddr(addr))
        return false;

    /** Project 3: Copy On Write (Extra) - 접근한 메모리의 page가 존재하고 write 요청인데 write protected인 경우라 발생한 fault일 경우 write 0 읽기전용  write 1 쓰기 가능*/
    if (!not_present && write)
        return vm_handle_wp(page);

    /** Project 3: Copy On Write (Extra) - 이전에 만들었던 페이지인데 swap out되어서 현재 spt에서 삭제하였을 때 stack_growth 대신 claim_page를 하기 위해 조건 분기 */
    if (!page) {
        /** Project 3: Stack Growth - stack growth로 처리할 수 있는 경우 */
        /* stack pointer 아래 8바이트는 페이지 폴트 발생 & addr 위치를 USER_STACK에서 1MB로 제한 */
        void *stack_pointer = user ? f->rsp : thread_current()->stack_pointer;
        if (stack_pointer - 8 <= addr && addr >= USER_STACK-MAX_STACK_POINT && addr <= USER_STACK) {
            vm_stack_growth(thread_current()->stack_bottom - PGSIZE);
            return true;
        }
        return false;
    }

    return vm_do_claim_page(page);  // demand page 수행
}

 

 

vm_do_claim_page에서 swap_in 에 의해  page type에 따라 초기화가 되고 lazy_load가 진행됨.

 

static bool uninit_initialize (struct page *page, void *kva) {
	struct uninit_page *uninit = &page->uninit;

	/* Fetch first, page_initialize may overwrite the values */
	vm_initializer *init = uninit->init;
	void *aux = uninit->aux;

	/* TODO: You may need to fix this function. */
    // initialize & lazy_load
	return uninit->page_initializer (page, uninit->type, kva) &&
		(init ? init (page, aux) : true);
}

 

 

// file page swap in 
bool lazy_load_segment (struct page *page, void *aux) {
	struct lazy_load_arg *lazy_load_arg = (struct lazy_load_arg *) aux;
    struct file *file = lazy_load_arg->file;
    off_t offset = lazy_load_arg->offset;
    size_t page_read_bytes = lazy_load_arg->length;
    size_t page_zero_bytes = PGSIZE - page_read_bytes;

	file_seek(lazy_load_arg->file,lazy_load_arg->offset);

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

	return true;
}



// anon page swap in 
static bool anon_swap_in (struct page *page, void *kva) {
    struct anon_page *anon_page = &page->anon;
    size_t slot = anon_page->slot;
    size_t sector = slot * SECTORS_IN_PAGE;

    if (slot == BITMAP_ERROR || !bitmap_test(swap_table, slot))
        return false;

    bitmap_set(swap_table, slot, false);

    for (size_t i = 0; i < SECTORS_IN_PAGE; i++)
        disk_read(swap_disk, sector + i, kva + DISK_SECTOR_SIZE * i);

    sector = BITMAP_ERROR;

    return true;
}

 

 

 

이렇게 하면 페이지를 spt에 추가하고 frame과 연결하여 매핑하고 pml4에 세팅한 후, page fault로 인해 매핑된 것을 확인할 수 있다.

 

 

page fault : 인스트럭션이 가상메모리 테이블을 참조했을 때 , 대응되는 실제 메모리 page가 존재하지 않는 상황이며, 디스크에서 데이터를 가져와야 할 때 발생한다. 페이지 오류 핸들러는 디스크에서 적절한 페이지를 로드해서 오류를 발생시킨 인스트럭션으로 제어를 넘겨준다. 이 인스트럭션이 다시 실행될 때 적절한 페이지는 메모리에 있게 되므로 인스트럭션은 오류를 발생하지 않고 동작할 수 있다.

page fault 가 발생하면 운영체제는 그 데이터를 메모리로 가져와서 마치 페이지 폴트가 전혀 발생하지 않은 것처럼 프로그램이 계속적으로 작동하게 해준다.

 

 

page fault