이제 복사 및 정리 작업을 지원하기 위해 추가 페이지 테이블 인터페이스를 다시 방문합니다. 이러한 작업은 프로세스를 생성(자식 프로세스를 생성하는 것보다 구체적으로 생성)하거나 프로세스를 파괴할 때 필요합니다. 자세한 내용은 아래에 자세히 설명되어 있습니다. 이 시점에서 추가 페이지 테이블을 다시 방문하는 이유는 위에서 구현한 초기화 기능 중 일부를 사용하고 싶을 수 있기 때문입니다.
**`vm/vm.c` 의 `supplemental_page_table_copy`와 `supplemental_page_table_kill` 를 구현하세요.**
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;
}
void supplemental_page_table_kill (struct supplemental_page_table *spt);
추가 페이지 테이블이 보유한 모든 리소스를 해제(free)합니다. 이 함수는 (
process_exit()
inuserprog/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);
}
vm/anon.c
의 anon_destroy
와
vm/uninit.c
의 uninit_destroy
에 구현합니다.
이것은 초기화되지 않은 페이지에서 destroy
작업을 위한 핸들러입니다.
초기화되지 않은 페이지가 다른 페이지 객체로 변환(transmute)되더라도, 프로세스가 종료(exits)될 때 여전히 초기화되지 않은(uninit) 페이지가 있을 수 있습니다.
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'
}
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;
};