본문 바로가기
개발자 도전기/[OS] pintOS

pintOS | Project 3 | Memory Mapped Files

by 답수 2021. 10. 20.
728x90
SMALL

 

이전 파트에서는 anonymous page에 대해서 공부하고 구현했다. Memory maaped file 파트에서는 메모리가 매핑된 페이지에 대해 구현한다.

 

memory mappped page들은 file-backed 매핑이다. 이 페이지 안의 내용들은 이미 존재하는 파일의 데이터를 복제하기 때문에 페이지 폴트가 발생했을 때 즉시 프레임에 할당된다. 메모리가 해제되거나 스왑 아웃될 때 내용의 변경 사항들은 파일 안에 다 반영된다.

 

자 그럼 뭘 해야 하나. 메모리가 매핑된 파일을 호출하는 시스템 콜인 mmap() 과 munmap()을 구현하라고 한다. VM시스템은 mmap 영역에서 lazy load가 되도록 해야 하며, mmap 파일 자체를 매핑을 위한 백업 저장소로 사용해야 한다.

void *mmap (void *addr, size_t length, int writable, int fd, off_t 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;
    
    if (fd == 0 || fd == 1)
        exit(-1);
    
    // vm_overlap
    if (spt_find_page(&thread_current()->spt, addr))
        return NULL;

    struct file *target = find_file_by_fd(fd);

    if (target == NULL)
        return NULL;

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

    return ret;
}

 

각 파라미터는 아래와 같다.

  • addr: 매핑을 시작할 주소(page 단위로 정렬)
  • length: 매핑할 파일의 길이
  • writable: 파일 write 가능 여부
  • fd: 프로세스의 가상 주소 공간에 매핑할 파일
  • offset: 파일의 오프셋

length만큼의 바이트를 addr의 프로세스 가상 주소 공간에 매핑한다. 전체 파일은 addr에서 시작해서 연속된 가상 페이지로 매핑된다.

 

파일의 길이가 PGSIZE(8 bytes)의 배수가 아닐 경우, 최종 매핑된 페이지의 일부 바이트는 파일의 끝 부분에 "stick out"된다..? 

 

페이지가 fault됐을 때, 바이트를 0으로 설정하고 페이지가 디스크에 다시 기록된다면 이것을 삭제한다. 성공한다면 매핑된 가상 주소를 반환하고, 실패한다면 NULL값을 반환해야 한다.

 

파일 매핑 메커니즘

 

매핑된 파일의 관리

 

 

munmap() 함수는 지정된 주소 범위 addr에 대한 매핑을 해제하는 함수다. 이 주소는 아직 매핑 해제되지 않은 같은 프로세스에서 mmap에 대한 호출에 의해 반환된 가상주소여야 한다.

 

모든 매핑들은 프로세스가 exit될 때 암묵적으로 unmmap된다. 암묵적이든 명시적이든 매핑이 해제될 때, 프로세스에 쓰여진 모든 페이지들은 파일에 다시 쓰여지고, 쓰여지지 않은 페이지들은 쓰여지면 안 된다. 그 이후 프로세스의 가상 페이지 리스트에서 이 페이지들은 제거된다.

void munmap (void *addr) {
    do_munmap(addr);
}

 

munmap() 동작

 

 

 

pintos에서 이와 관련된 함수는 vm/file.c 안에 do_mmap, do_munmap 이 두 함수를 구현하면 된다.

 

do_mmap함수!

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

 

 

 

/* 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(사용되었던) bit 체크
        if(pml4_is_dirty(thread_current()->pml4, page->va)) {
            file_write_at(aux->file, addr, aux->page_read_bytes, aux->ofs);
            pml4_set_dirty (thread_current()->pml4, page->va, 0);
        }

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

 

 

파일을 close하거나 remove해도 매핑된 것이 해제되지 않는다. 일단 매핑이 되면 munmmap()이 호출되거나 프로세스가 종료될 때까지 유효하다. file_reopen() 함수를 사용하여 파일의 각각 매핑에 대해 분리되고 독립적인 참조를 얻어야 한다.

 

두 개 이상의 프로세스가 같은 파일을 매핑하는 경우, 일관된 데이터를 볼 필요가 없다. 유닉스는 두 매핑이 같은 물리 페이지를 공유하게 만들도록 처리하고, mmap 시스템 콜은 클라이언트가 페이지가 공유되는지 아닌지를 지정할 수 있게 한다. 

728x90
LIST

댓글