sw사관학교정글/PintOS(KAIST's CS330 class)

[week11] PintOS - Project 3(Virtual Memory) : Memory mapped files

D cron 2022. 1. 26. 00:39

Project 3 : Memory mapped files 구현

무엇을 하는가?

이 섹션에서는 memory-mapped pages를 구현한다. anonymous pages와 달리 memory-mapped pages는 file-backed mapping이다. page의 내용들은 현재 file의 data들을 복사해 온 내용들이다. page fault가 발생하면 physical frame이 즉시 할당되고 파일로부터 메모리로 내용이 복사된다. memory-mapped pages가 unmapped되거나 swap out 되면, contents의 수정사항이 file에 반영된다.

mmap의 장단점

장점

프로세스가 가지고 있는 읽기 혹은 쓰기 buffer에 복사해서 data를 가져오는 것이 아니라 바로 file에서 가져와서 메모리 상에 읽거나 쓸 수 있으므로 data buffer를 할당, 복사, 해제하는 비용이 줄어든다.

파일 전체를 모두 메모리에 올릴 필요가 없다(메모리 절약).

단점

대용량 file을 가상 메모리 공간에 mapping할 때 외부 단편화가 발생할 수 있다.

너무 작은 file을 읽어올 때 좋지 않다(page fault에 사용되는 비용 낭비).


system call로 mmap과 munmap을 사용하기 때문에 syscall handler를 수정해준다.

// userprog/syscall.c
void
syscall_handler (struct intr_frame *f UNUSED) {
    switch (f->R.rax)
    {
        ...
        // project 3
        case SYS_MMAP:
            f->R.rax = mmap(f->R.rdi, f->R.rsi, f->R.rdx, f->R.r10, f->R.r8);
            break;
        case SYS_MUNMAP:
            munmap(f->R.rdi);
            break;
        ...
    }

밑에 system call도 구현해준다.

// userprog/syscall.c
/*-------------project 3 syscall --------------------*/
void *mmap(void *addr, size_t length, int writable, int fd, off_t offset){
    // offset이 정렬되지 않았을 때
    if (offset % PGSIZE != 0){
        return NULL;
    }
    if(pg_round_down(addr) != addr || is_kernel_vaddr(addr) || addr == NULL || (long long)length <=0)
        return NULL;
    // console input, output은 mapping x
    if(fd == 0 || fd == 1)
        exit(-1);
    // overlap
    if(spt_find_page(&thread_current()->spt, addr))
        return NULL;

    struct file *target = process_get_file(fd);
    if(target == NULL)
        return NULL;

    void *ret = do_mmap(addr, length, writable, target, offset);

    return ret;
}

void munmap(void *addr){
    do_munmap(addr);
}
/*-------------project 3 syscall --------------------*/

mmap은 argument로 받은 내용들을 검사해서 적합한 접근이면 do_mmap을 호출한다.

munmapdo_munmap을 호출한다.

process_get_file 함수도 만들어주어야 한다.

// userprog/process.c
struct file* process_get_file(int fd){
    struct thread *curr = thread_current();
    struct file* fd_file = curr->fd_table[fd];

    if (fd_file)
        return fd_file;
    else
        return NULL;
}

이제 do_mmapdo_munmap을 구현해주자.

do_mmap

addr부터 시작하는 연속된 유저 가상 메모리 공간에 page들을 만들어 file의 offset부터 length에 해당하는 file의 정보를 각 page마다 저장한다. 프로세스가 이 page에 접근해서 page fault를 발생시키면 physical frame과 mapping하여 (claim) disk에서 file data를 frame에 복사함

// vm/file.c
/* Do the mmap */
void *
do_mmap (void *addr, size_t length, int writable,
        struct file *file, off_t offset) {
    struct file *mfile = file_reopen(file);

    void *ori_addr = addr;
    size_t read_bytes = length > file_length(file) ? file_length(file) : length;
    size_t zero_bytes = PGSIZE - read_bytes % PGSIZE;

    while(read_bytes > 0 || zero_bytes > 0){
        size_t page_read_bytes = read_bytes < PGSIZE ? read_bytes : PGSIZE;
        size_t page_zero_bytes = PGSIZE - page_read_bytes;

        struct container *container = (struct container*)malloc(sizeof(struct container));
        container->file = mfile;
        container->offset = offset;
        container->page_read_bytes = page_read_bytes;

        if(!vm_alloc_page_with_initializer(VM_FILE, addr, writable, lazy_load_segment, container)){
            return NULL;
        }
        read_bytes -= page_read_bytes;
        zero_bytes -= page_zero_bytes;
        addr += PGSIZE;
        offset += page_read_bytes;

    }
    return ori_addr;
}

file을 PGSIZE 크기로 잘라서 정보들을 container 구조체에 저장한다.

do_munmap

// vm/file.c
/* Do the munmap */
void
do_munmap (void *addr) {
    while(true){
        struct page* page = spt_find_page(&thread_current()->spt, addr);

        if(page == NULL)
            break;

        struct container *aux = (struct container *)page->uninit.aux;

        // dirty check
        if(pml4_is_dirty(thread_current()->pml4, page->va)){
            file_write_at(aux->file, addr, aux->page_read_bytes, aux->offset);
            pml4_set_dirty(thread_current()->pml4, page->va, 0);
        }

        pml4_clear_page(thread_current()->pml4, page->va);
        addr += PGSIZE;
    }
}

수정된 page(dirty bit 1)는 file에 update하고 dirty bit를 0으로 만든다.

supplemental_page_table_kill

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. */
    // project 3
    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_muncdmap(page->va);
        }
    }
    hash_destroy(&spt->pages, spt_destructor);
}

spt를 순회하면서 page의 type이 VM_FILE이라면, munmap을 실행시킨다.


결과