ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Pintos project3_Anonymous Page
    OS/Pintos P.J_3 2022. 1. 21. 02:16

    Anonymous Page

    이건 또 뭘까 ... 

    이번 파트 에서는 anonymous page라고 불리는 non-disk based imge에 대해 구현한다고 한다.

     

    어떠한 명명된 파일소스도 없기 때문에 어나니머스라고 불리며 어나니머스 매핑은 백업파일이나 장치가 없다.

     

    어나니머스페이지는 스택이나 힙과 같은 실행파일에 사용된다고한다.

     

    Page Initialization with Lazy Loading

    Lazy loading 이란 메모리 로딩이 필요한 시점까지 지연되도록 디자인된것을 말한다.

    페이지가 할당되어 해당 페이지 구조가 있으나 물리적 프레임이 없고 페이지의 실제 내용이 아직 로드되지 않은 상태로 contents가 실제로 필요한 경우 page fault 시그널을 받아 로드 된다. 

     

    pintos에는 3가지 type의 페이지(uninit_page, anon_page, file_page.)가 있고 이 각각은 초기화 루틴이 다르다고 한다. 

     

    간략하게 말해보자면 먼저 vm_alloc_page_with_initializer는 커널이 새로운 페이지에대한 요청을 받았을때 호출된다. 

    vm_alloc_page_with_initializer는 이름과 같이 페이지 구조를 할당, 페이지 유형에 따라 적절한 초기화를 진행하여 다시 사용자 프로그램으로 반환시킨다.

     

    유저프로그램이 실행될때 어느 시점에 page fault가 발생한다.

    왜냐하면 프로그램은 어떠한 페이지에 접근했을때 그것을 가지고 있다고 생각하지만 실제로는 lazy loading으로 필요할떄 물리프레임으로 올리기 때문에 물리 프레임에는 해당 페이지가 없기 때문이다.

     

    page fault 핸들링 프로시져동안 uninit_initialize()가 호출되고 전에 설정했던 initializer가 호출된다.anonymous pages라면 anon_initializer()가 호출되고 file-backed page라면 file_backed_initializer()가 호출된다.

     

    페이지의 life cycle은 이렇다.

    page faule -> lazy load -> swap in -> swap out -> ... -> destroy.

    각 life cycle의 transition에서 요구되는  procedure는  page type마다 다르다.

     

    Lazy Loading for Executable

    lazy loading에서는 프로세스가 실행을 시작할 때 즉시 필요한 메모리 부분만 main memory 에 로드 된다. 이를 통해 오버헤드를 줄일수 있다.

     

    Lazy loading을 위해 먼저 VM_UNITIT이라 불리는 페이지 타입에 대해 알아보자.

    모든 page는 초기에 VM_INIT 타입으로 생성된다.

    uninitalized pages에 대한 구조체는 struct uninit_page에서 제공된다.

    uninitalized pages의 생성, 초기화, 삭제등은 vm/uninit.c에서 찾아볼 수 있다. 이거 구현해야한다.

     

    Page fault가 일어나면 page fault handler는 vm_try_handle_fault에 제어권을 전달해 먼저 정말 page fault인지 체크한다. 만약 bogus fault (lazy-loaded, swaped-out page, write-protected page) 라면 일부 contents를 페이지에 로드하고 제어권을 사용자 프로그램으로 반환한다.

     

    이제 부터 bogus fault 는 lazy-loaded page인 경우만 생각한다. 만약 page fault가 lazy loading 에 대한 경우라면 커널은 segment에 lazy load를 하기위해 우리가 이전에 set한 vm_alloc_page_with_initializer를 호출한다.

     

    이를 위해userprog/process.c에 있는 lazy_load_segment를 구현해야한다.

     

    구현

    vm_alloc_page_with_initializer

    전달받는 vm_type 에 때라 적절한 initializer를 가져와 uninit_new를 호출, 초기화 되지 않은 페이지를 만들자.

    /* 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`. */
    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. */
    
    		// project3 | 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;
    }

    uninit_initialize

    uninit_initailalize 함수는 첫번째 오류 페이지를 초기화 한다. 먼저 vm)initializer 와 aux를 가져오고 함수 포인터를 통해 해당 page_initailizer를 호출한다.

    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. */
    	return uninit->page_initializer (page, uninit->type, kva) &&
    		(init ? init (page, aux) : true);
    }

    현재까지는 프로세스가 실행될 때 segment를 바로 실제 메모리에 직접 로드하는 방식이었다면 이제는 spt에 필요한 정보들만 넣어 page fault가 발생(페이지 요청)시 메모리를 로드하는 방식으로 변경해주어야 한다.

     

    기존 load_segment()와 lazy_load_segment()gkatndptj 요청되는 file만 kpage에 로드하도록 해본다.

    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;
    }
    
    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 (container로 대체)to pass information to the lazy_load_segment. */
    		// struct container *container;
    		// container = palloc_get_page(PAL_ZERO | PAL_USER);
    		struct container *container = (struct container *)malloc(sizeof(struct container));
    		container->file = file;
    		container->page_read_bytes = page_read_bytes;
    		container->offset = ofs;
    		// container->writable = writable;
    
    		if (!vm_alloc_page_with_initializer(VM_ANON, upage, writable, lazy_load_segment, container))
    		{
    			return false;
    		}
    
    		read_bytes -= page_read_bytes;
    		zero_bytes -= page_zero_bytes;
    		upage += PGSIZE;
    		ofs += page_read_bytes;
    	}
    	return true;
    }

    setup_stack 도 새론운 메모리 관리 시스템에 맞게 수정해 주어야 한다.

    setup_stack 함수를 찾아보면 project2까지 사용되는setup_stack과 project3부터 사용되는 setup_stack이 나누어져 있다. 여기서 project3 부터 사용되는 setup_stack을 수정해 준다.

    스택을 구별할 수 있도록 auxillary maker 로  VM_MARKER_0를 이용한다.

    /*process.c*/
    
    bool
    setup_stack (struct intr_frame *if_) {
    	bool success = false;
    	void *stack_bottom = (void *) (((uint8_t *) USER_STACK) - PGSIZE);
    
    	/* TODO: Map the stack on stack_bottom and claim the page immediately.
    	 * TODO: If success, set the rsp accordingly.
    	 * TODO: You should mark the page is stack. */
    	/* TODO: Your code goes here */
    	// stack 영역인 page MARK
    	if (vm_alloc_page(VM_ANON | VM_MARKER_0, stack_bottom, 1))
    	{ // type, upage, writable
    		success = vm_claim_page(stack_bottom);
    
    		if (success)
    		{
    			if_->rsp = USER_STACK;
    			thread_current()->stack_bottom = stack_bottom;
    		}
    	}
    	return success;
    }

     

     

    마지막으로 spt_find_page를 통해 보충 페이지 테이블을 참조하여 falult가 발생한 주소에 해당하는 페이지 구조를 해결하는 함수vm_try_handle_fault 를 수정한다.

    /*vm.c*/
    
    /* Find VA from spt and return page. On error, return NULL. */
    struct page *
    spt_find_page (struct supplemental_page_table *spt UNUSED, void *va UNUSED) {
    	//struct page *page = NULL;
    	/* TODO: Fill this function. */
    	struct page *page = (struct page *)malloc(sizeof(struct page));
    	struct hash_elem *e;
    
    	page->va = pg_round_down(va); // va가 가리키는 가상 페이지의 시작 포인트(오프셋이 0으로 설정된 va) 반환
    	e = hash_find(&spt->pages, &page->hash_elem);
    
    	free(page);
    
    	return e != NULL ? hash_entry(e, struct page, hash_elem) : NULL;
    }
    
    /* Return true on success */
    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;
    }

    check_address()수정

    project2까지의 check_address는 rsp 에 대한 유저메모리 영역을 체크하는 것이었다. 이것을 가상메모리 page를 사용하여 유효성을 검사 하도록 코드를 수정해준다.

    또한 read write의 경우 버퍼의 주소가 유효한 가상주소인지 아닌지 검사할 필요가 있으므로 check_balid_buffer를 구현한다. 구현시 check_address()함수를 사용한다.

    struct page *check_address(void *addr){
    	struct thread *cur = thread_current();
    	if (is_kernel_vaddr(addr))
    	{
    		exit(-1);
    	}
    	return spt_find_page(&thread_current()->spt, addr);
    }
    
    void check_valid_buffer(void *buffer, unsigned size, void *rsp, bool to_write)
    {
    	for (int i = 0; i < size; i++)
    	{
    		struct page *page = check_address(buffer + i); // 인자로 받은 buffer부터 buffer + size까지의 크기가 한 페이지의 크기를 넘을수도 있음
    		if (page == NULL)
    			exit(-1);
    		if (to_write == true && page->writable == false)
    			exit(-1);
    	}
    }

    supplemental_page_table_copy 

     

    자식프로세스가 부모프로세스의 context를 상속받을 때 필요한 함수로 uninit page를 할당하고 즉시 요청한다.

    /* Copy supplemental page table from src to dst */
    bool
    supplemental_page_table_copy (struct supplemental_page_table *dst UNUSED,
    		struct supplemental_page_table *src UNUSED) {
    	struct hash_iterator i;
    	hash_first(&i, &src->pages);
    	while (hash_next(&i))
    	{																				 // src의 각각의 페이지를 반복문을 통해 복사
    		struct page *parent_page = hash_entry(hash_cur(&i), struct page, hash_elem); // 현재 해시 테이블의 element 리턴
    		enum vm_type type = page_get_type(parent_page);								 // 부모 페이지의 type
    		void *upage = parent_page->va;												 // 부모 페이지의 가상 주소
    		bool writable = parent_page->writable;										 // 부모 페이지의 쓰기 가능 여부
    		vm_initializer *init = parent_page->uninit.init;							 // 부모의 초기화되지 않은 페이지들 할당 위해
    		void *aux = parent_page->uninit.aux;
    
    		if (parent_page->uninit.type & VM_MARKER_0)
    		{ // 부모 페이지가 할당되어 있다면????
    			setup_stack(&thread_current()->tf);
    		}
    		else if (parent_page->operations->type == VM_UNINIT)
    		{ // 부모 타입이 uninit인 경우
    			if (!vm_alloc_page_with_initializer(type, upage, writable, init, aux))
    				return false;
    		}
    		else
    		{
    			if (!vm_alloc_page(type, upage, writable))
    				return false;
    			if (!vm_claim_page(upage))
    				return false;
    		}
    
    		if (parent_page->operations->type != VM_UNINIT)
    		{ // UNIINIT이 아닌 모든 페이지(stack 포함)는 부모의 것을 memcpy
    			struct page *child_page = spt_find_page(dst, upage);
    			memcpy(child_page->frame->kva, parent_page->frame->kva, PGSIZE);
    		}
    	}
    	return true;
    }

    supplemental_page_table_kill

    Frees all the resources that were held by a supplemental page table

    이 함수는 process exits때 호출된다. 

    모든 페이지르 지우기 위해 테이블을 반복하면서destroy(page)를 호출한다.

    실제 pml4 와 palloc으로 할당한 물리메모리의 cleaning은 여기서 생각하지 않는다 이건 supplemental_page정리후 정리한다.

    /* Free the resource hold by the supplemental page table */
    void
    supplemental_page_table_kill (struct supplemental_page_table *spt UNUSED) {
    	/* TODO: Destroy all the supplemental_page_table hold by thread and
    	 * TODO: writeback all the modified contents to the storage. */
    	struct hash_iterator i;
    
    	hash_first(&i, &spt->pages);
    	while (hash_next(&i))
    	{
    		struct page *page = hash_entry(hash_cur(&i), struct page, hash_elem);
    
    		if (page->operations->type == VM_FILE)
    		{
    			do_munmap(page->va);
    			// destroy(page);
    		}
    	}
    	hash_destroy(&spt->pages, spt_destructor);
    }

     

    'OS > Pintos P.J_3' 카테고리의 다른 글

    Pintos project3_Swap In/Out  (0) 2022.01.25
    Pintos project3_Memory Mapped Files  (0) 2022.01.22
    Pintos project3_Stack Growth  (0) 2022.01.21
    Pintos project3_Memory management  (0) 2022.01.19
    [Project 3_Virtual Memory]_Intro  (0) 2022.01.11

    댓글

Designed by Tistory.