[PintOS] Project 3 - Anonymous Page (2)

yerimii11·2022년 1월 27일
0

SW-Jungle

목록 보기
44/59

- Supplemental Page Table - Revisit (구현)

이제 복사 및 정리 작업을 지원하기 위해 추가 페이지 테이블 인터페이스를 다시 방문합니다. 이러한 작업은 프로세스를 생성(자식 프로세스를 생성하는 것보다 구체적으로 생성)하거나 프로세스를 파괴할 때 필요합니다. 자세한 내용은 아래에 자세히 설명되어 있습니다. 이 시점에서 추가 페이지 테이블을 다시 방문하는 이유는 위에서 구현한 초기화 기능 중 일부를 사용하고 싶을 수 있기 때문입니다.

**`vm/vm.c` 의 `supplemental_page_table_copy`와  `supplemental_page_table_kill` 를 구현하세요.**

- 구현1.

    bool supplemental_page_table_copy (struct supplemental_page_table *dst, struct supplemental_page_table *src);

src에서 dst로 추가 페이지 테이블을 복사합니다. 이것은 자식이 부모의 실행 컨텍스트를 상속해야 할 때 사용됩니다(예: fork()). 
src의 추가 페이지 테이블에 있는 각 페이지를 반복하고 dst의 추가 페이지 테이블에 있는 항목의 정확한 복사본을 만듭니다. uninit page를 할당하고 즉시 요청(claim)해야 합니다.

  • 코드
        /* 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) {
        	src->spt_hash.aux = dst; // pass 'dst' as aux to 'hash_apply'
        	hash_apply(&src->pages, hash_action_copy);
        	return true;
        }
  • hash_apply
            /* Calls ACTION for each element in hash table H in arbitrary order.
               Modifying hash table H while hash_apply() is running, using any of the functions 
            	 hash_clear(), hash_destroy(), hash_insert(), hash_replace(), or hash_delete(), yields undefined behavior, 
            	 whether done from ACTION or elsewhere. */
            void
            hash_apply (struct hash *h, hash_action_func *action) {
            	size_t i;
            
            	ASSERT (action != NULL);
            
            	for (i = 0; i < h->bucket_cnt; i++) {
            		struct list *bucket = &h->buckets[i];
            		struct list_elem *elem, *next;
            
            		for (elem = list_begin (bucket); elem != list_end (bucket); elem = next) {
            			next = list_next (elem);
            			action (list_elem_to_hash_elem (elem), h->aux);
            		}
            	}
            }
  • hash_action_copy (구현)
void hash_action_copy (struct hash_elem *e, void *hash_aux){
	struct thread *t = thread_current();
	ASSERT(&t->spt == (struct supplemental_page_table *)hash_aux); // child's SPT

	struct page *page = hash_entry(e, struct page, hash_elem);
	enum vm_type type = page->operations->type; // type of page to copy

	if(type == VM_UNINIT){
		struct uninit_page *uninit = &page->uninit;
		vm_initializer *init = uninit->init;
		void *aux = uninit->aux;
	
		// copy aux (struct lazy_load_info *)
		struct lazy_load_info *lazy_load_info = malloc(sizeof(struct lazy_load_info));
		if(lazy_load_info == NULL){
			// #ifdef DBG
			// malloc fail - kernel pool all used
		}
		memcpy(lazy_load_info, (struct lazy_load_info *)aux, sizeof(struct lazy_load_info));

		lazy_load_info->file = file_reopen(((struct lazy_load_info *)aux)->file); // get new struct file (calloc)
		vm_alloc_page_with_initializer(uninit->type, page->va, page->writable, init, lazy_load_info);
		
		// uninit page created by mmap - record page_cnt
		if(uninit->type == VM_FILE){
			struct page *newpage = spt_find_page(&t->spt, page->va);
			newpage->page_cnt = page->page_cnt;
		}
	}
	if(type & VM_ANON == VM_ANON){ // include stack pages
		//when __do_fork is called, thread_current is the child thread so we can just use vm_alloc_page
		vm_alloc_page(type, page->va, page->writable);

		struct page *newpage = spt_find_page(&t->spt, page->va); // copied page
		vm_do_claim_page(newpage);

		ASSERT(page->frame != NULL);
		memcpy(newpage->frame->kva, page->frame->kva, PGSIZE);
	}
	if(type == VM_FILE){
		struct lazy_load_info *lazy_load_info = malloc(sizeof(struct lazy_load_info));

		struct file_page *file_page = &page->file;
		lazy_load_info->file = file_reopen(file_page->file);
		lazy_load_info->page_read_bytes = file_page->length;
		lazy_load_info->page_zero_bytes = PGSIZE - file_page->length;
		lazy_load_info->offset = file_page->offset;
		void *aux = lazy_load_info;
		vm_alloc_page_with_initializer(type, page->va, page->writable, lazy_load_segment_for_file, aux);

		struct page *newpage = spt_find_page(&t->spt, page->va); // copied page
		vm_do_claim_page(newpage);
		
		newpage->page_cnt = page->page_cnt;
		newpage->writable = false;
	}
}
  • struct lazy_load_info
// [includ>vm>vm.h]
/* P3 추가 */
struct lazy_load_info {
	struct file *file;
	size_t page_read_bytes;
	size_t page_zero_bytes;
	off_t offset;
};
  • lazy_load_segment_for_file (구현)
// [vm>file.c]
bool
lazy_load_segment_for_file(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 lazy_load_info * lazy_load_info = (struct lazy_load_info *)aux;
	struct file * file = lazy_load_info->file;
	size_t page_read_bytes = lazy_load_info->page_read_bytes;
	size_t page_zero_bytes = lazy_load_info->page_zero_bytes;
	off_t offset = lazy_load_info->offset;

	file_seek(file, offset);

	//vm_do_claim_page(page);
	ASSERT (page->frame != NULL); 	//이 상황에서 page->frame이 제대로 설정돼있는가?
	void * kva = page->frame->kva;
	if (file_read(file, kva, page_read_bytes) != (int)page_read_bytes)
	{
		//palloc_free_page(page); // #ifdef DBG Q. 여기서 free해주는거 맞아?
		free(lazy_load_info);
		return false;
	}

	memset(kva + page_read_bytes, 0, page_zero_bytes);
	free(lazy_load_info);

	file_seek(file, offset); // may read the file later - reset fileobj pos

	return true;
}

- 구현2.

void supplemental_page_table_kill (struct supplemental_page_table *spt);

추가 페이지 테이블이 보유한 모든 리소스를 해제(free)합니다. 이 함수는 (process_exit() in userprog/process.c)로 프로세스가 종료될 때 호출됩니다. 페이지 항목(entries)을 반복하고 테이블의 페이지에 destroy(page)를 호출해야 합니다. 
(추가 페이지 테이블이 정리된 후 호출자(caller)가 이를 정리하는)이 함수에서는 actual page table(pml4)과 physical memory(palloc-ed memory)에 대해 걱정할 필요가 없습니다.

  • 코드
/* 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. */
	hash_destroy(&spt->pages, hash_action_destroy); /* P3 추가 */
}
  • hash_action_destroy (구현)
void hash_action_destroy (struct hash_elem *e, void *aux){
	struct thread *t = thread_current();
	struct page *page = hash_entry(e, struct page, hash_elem);
	
	// mmap-exit - process exits without calling munmap; unmap here
	if(page->operations->type == VM_FILE){
		if(pml4_is_dirty(t->pml4, page->va)){
			struct file *file = page->file.file;
			size_t length = page->file.length;
			off_t offset = page->file.offset;

			ASSERT(page->frame != NULL);

			if(file_write_at(file, page->frame->kva, length, offset) != length){
				// #ifdef DBG
				// TODO - Not properly written-back
			}
		}
	}
	
	if (page->frame != NULL){
		page->frame->page = NULL;		
	}
	
	// destroy(page);
	// free(page->frame);
	// free(page);
	remove_page(page);
}
  • remove_page (구현)
// [vm>vm.c]
// same as spt_remove_page except that it doesn't delete the page from SPT hash
// only free page, not frame - just break the page-frame connection 
void remove_page(struct page *page){
	struct thread *t = thread_current();
	pml4_clear_page(t->pml4, page->va);
	// if(page->frame)
	// 	free(page->frame);
	if (page->frame != NULL){
		page->frame->page = NULL;
	}
	vm_dealloc_page (page);
	// destroy(page); // uninit destroy - free aux
	// free(page);
}
  • pml4_is_dirty
// [threads>mmu.c]
/* Returns true if the PTE for virtual page VPAGE in PML4 is dirty,
 * that is, if the page has been modified since the PTE was installed. 
 * Returns false if PML4 contains no PTE for VPAGE. */
bool
pml4_is_dirty (uint64_t *pml4, const void *vpage) {
	uint64_t *pte = pml4e_walk (pml4, (uint64_t) vpage, false);
	return pte != NULL && (*pte & PTE_D) != 0;
}
  • file_write_at
/* Writes SIZE bytes from BUFFER into FILE, starting at offset FILE_OFS in the file.
 * Returns the number of bytes actually written, which may be less than SIZE if end of file is reached.
 * (Normally we'd grow the file in that case, but file growth is not yet implemented.)
 * The file's current position is unaffected. */
off_t
file_write_at (struct file *file, const void *buffer, off_t size, off_t file_ofs) {
	return inode_write_at (file->inode, buffer, size, file_ofs);
}

- Page Cleanup

vm/anon.c 의 anon_destroy
vm/uninit.c
uninit_destroy에 구현합니다. 

이것은 초기화되지 않은 페이지에서 destroy 작업을 위한 핸들러입니다. 
초기화되지 않은 페이지가 다른 페이지 객체로 변환(transmute)되더라도, 프로세스가 종료(exits)될 때 여전히 초기화되지 않은(uninit) 페이지가 있을 수 있습니다.


- 구현3.

static void uninit_destroy (struct page *page);

page struct가 보유한 리소스를 해제(free)합니다. 
우리는 페이지의 vm유형을 확인하고 그에 따라 처리할 수 있습니다.

지금은 익명 페이지만 처리할 수 있습니다. 나중에 파일 백업 페이지(file-backed pages)를 정리하기 위해서 이 함수를 다시 방문할(revisit) 것입니다.

  • 코드
/* Free the resources hold by uninit_page. Although most of pages are transmuted
 * to other page objects, it is possible to have uninit pages when the process
 * exit, which are never referenced during the execution.
 * PAGE will be freed by the caller. */
static void
uninit_destroy (struct page *page) {
	struct uninit_page *uninit UNUSED = &page->uninit;
	/* TODO: Fill this function.
	 * TODO: If you don't have anything to do, just return. */
	struct lazy_load_info * info = (struct lazy_load_info *)(uninit->aux);
	file_close(&info->file);
	
	// 'file_close' frees 'info'
	//free(info); // malloc in 'process.c load_segment' or 'hash_action_copy'
}

- 구현4.

static void anon_destroy (struct page *page);

익명 페이지가 보유한 리소스를 해제합니다. page struct를 명시적으로 해제할 필요는 없으며, 호출자(caller)가 수행해야 합니다.

이제 프로젝트2의 모든 테스트를 통과해야 합니다.

  • 코드 (수정사항 없음)
        /* Destroy the anonymous page. PAGE will be freed by the caller. */
        static void
        anon_destroy (struct page *page) {
        	struct anon_page *anon_page = &page->anon;
        }

- 최종 수정사항

    // [include>vm>file.h] 추가 수정
    struct file_page {
    	struct file *file;
    	size_t length;
    	off_t offset;
    };

profile
better

0개의 댓글