[PintOS] Project3: Memory Mapped Files

김상호·2022년 6월 28일
0

Development Log

목록 보기
40/45

Memory Mapped Files

목표

이번에는 Anonymous Memory가 아닌 File-Backed memory, 다시 말해 Memory Mapped Page에 대해 구현해보도록 한다.

File-Backed page

File-Backed page는 파일에 기반한 맵핑을 한다. 안에 내용은 디스크에서 존재하고 있는 파일을 복사한 것이다. page fault가 발생하면 바로 물리 프레임을 할당하고 파일의 데이터를 물리 메모리에 복사한다. 이때 I/O를 통해 데이터를 복사하는 것이 아닌 DMA 방식으로 디스크에서 파일을 복사한다.

이 때 유저 가상 페이지를 미리 가상주소 공간에 할당 해주는 것이 mmap이고 페이지와 물리 메모리가 연결된 경우 그 연결을 끊어 주는 것을 munmap이라 한다.

  • mmap() 시스템 콜에 의해 맵핑된 가상 메모리는 스택과 힙 사이의 미할당 공간(스택도 힙도 아닌)에 할당된다

  • memory maping page 구현 시에도 lazy loading 방식으로 할당되어야 한다.

  • vm_alloc_page로 페이지를 할당하고 해당 페이지에 page fault가 발생하였을 때 frame을 맵핑하고 해당 page에 해당하는 file 데이터를 메인 메모리에 올려준다.

mmap 구현

void *mmap (void *addr, size_t length, int writable, int fd, off_t offset);

  • addr에서 시작하는 연속적인 가상 페이지를 맵핑한다.
  • mmap은 페이지 단위로 메모리를 할당받은 시스템 콜
  • offset으로 부터 length만큼 fd에 해당하는 파일을 addr에 연결된 물리 메모리에 올린다

만약 파일의 size가 PGSIZE보다 크면 마지막 페이지에는 남은 공간 만큼의 빈공간이 생긴다. 이 비어 있는 메모리를 0으로 초기화해주고 다시 디스크에 업데이트할 때는 버린다.

  • Q. mmap과 malloc의 차이 둘 다 메모리를 할당받는다는 점에서는 같다. mmap이 file-backed 메모리의 경우에만 해당하는 함수라고 오해한다는데, 그렇지 않은 경우도 많다. 그냥 mmap은 페이지 단위로 할당받으므로 malloc에 비해 큰 용량의 메모리를 할당할 수 있다는 것이 차이인듯.

syscall mmap

  • 이 함수에서는 다양한 예외 처리를 해준다. 유저가 입력한 값이 정상적인지 확인한다.
void *
mmap(void *addr, size_t length, int writable, int fd, off_t offset)
{
	if (addr == NULL || !is_user_vaddr(addr) || length == 0)
		return NULL;
	if (addr >= STACK_OUT_ADDR)
		return NULL;

	if (addr != pg_round_down(addr))
		return NULL;
	struct page *page = spt_find_page(&thread_current()->spt, addr);

	if (page)
		return NULL;

	if (offset % PGSIZE != 0)
		return NULL;

	if (fd == 0 || fd == 1)
		return NULL;

	struct file *file_obj = get_file_from_fd_table(fd);

	if (!file_obj || !filesize(fd))
		return NULL;
	return do_mmap(addr, length, writable, file_obj, offset);
}

do_mmap

  • do_mmap은 load_segment와 매우 유사한 면이 있음.
  • page를 할당할 때 VM_FILE로 하여 할당
  • 이렇게 만들어진 page는 page fault가 발생하면 페이지를 초기화 하고 frame과 연결하고 file_inf에 저장된 파일의 정보로 물리메모리에 file 데이터를 복사한다.
void *
do_mmap(void *addr_, size_t length, int writable,
				struct file *file, off_t offset)
{
	file = file_reopen(file);
	uint32_t read_bytes = file_length(file);
	uint32_t zero_bytes = (length - read_bytes);

	struct mmap_file mmap_file;
	mmap_file.file = file;
	list_init(&mmap_file.page_list);
	list_push_back(&thread_current()->mmap_list, &mmap_file.mmap_elem);
	void *addr = addr_;

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

		struct file_information *file_inf = (struct file_information *)malloc(sizeof(struct file_information));
		file_inf->file = file;
		file_inf->ofs = offset;
		file_inf->read_bytes = page_read_bytes;

		if (!vm_alloc_page_with_initializer(VM_FILE, addr, writable, file_lazy_load_segment, file_inf))
			return NULL;
		struct page *file_page = page_lookup(addr);
		file_page->file_inf = file_inf;
		list_push_back(&mmap_file.page_list, &file_page->mmap_elem);
		/* Advance. */
		read_bytes -= page_read_bytes;
		zero_bytes -= page_zero_bytes;
		addr += FISIZE;
		offset += page_read_bytes;
	}

	return addr_;

file_lazy_load_segment

  • file_back_page에서 page fault 발생 시 맵핑된 물리 메모리에 파일 데이터를 복사하는 함수
  • file의 offset을 aux로 받아온 offset으로 옮기고 물리 메모리에 파일에서 read_bytes만큼 데이터를 복사한다.
  • PGSIZE가 되지 않는 read_bytes인 경우 남은 부분은 0으로 채워준다. memset()
static bool
file_lazy_load_segment(struct page *page, void *aux)
{
	struct file *file = ((struct file_information *)aux)->file;
	off_t offset = ((struct file_information *)aux)->ofs;
	size_t page_read_bytes = ((struct file_information *)aux)->read_bytes;
	size_t page_zero_bytes = FISIZE - page_read_bytes;

	file_seek(file, offset); // file의 오프셋을 offset으로 바꾼다. 이제 offset부터 읽기 시작한다.
	/* 페이지에 매핑된 물리 메모리(frame, 커널 가상 주소)에 파일의 데이터를 읽어온다. */
	/* 제대로 못 읽어오면 페이지를 FREE시키고 FALSE 리턴 */
	off_t read_off = file_read(file, page->frame->kva, page_read_bytes);
	if (read_off != (int)page_read_bytes)
	{
		palloc_free_page(page->frame->kva);
		return false;
	}
	/* 만약 1페이지 못 되게 받아왔다면 남는 데이터를 0으로 초기화한다. */
	memset(page->frame->kva + page_read_bytes, 0, page_zero_bytes);
	return true;
}

mummap(addr)

  • Dirty Bit

    • 해당 페이지가 수정되었는지를 저장하는 비트이다. 페이지가 변경될 때마다 이 비트는 1이된다.

    • 디스크에 변경 내용을 기록하고 나면 해당 페이지의 더티 비트는 다시 0으로 초기화 된다.

💡 ADDR으로부터 연속된 유저 가상 페이지들의 변경 사항을 디스크의 파일에 업데이트하고 맵핑 정보를 지운다. 페이지를 지우는 것이 아닌 Present Bit을 0으로 만들어준다.
  • 해당 page의 dirty bit가 1이면 수정사항이 있는 것임으로 file에 물리메모리에 있는 데이터를 write하여 준다. 그리고 해당 페이지의 dirty bit을 0으로 만들어준다.
void do_munmap(void *addr)
{
	struct page *page = spt_find_page(&thread_current()->spt, addr);

	if (pml4_is_dirty(thread_current()->pml4, page->va))
	{
		file_write_at(page->file_inf->file, page->frame->kva, page->file_inf->read_bytes, page->file_inf->ofs);
		pml4_set_dirty(thread_current()->pml4, page->va, false);
	}

	struct mmap_file *mmap_file = list_entry(&page->mmap_elem, struct mmap_file, mmap_elem);
	struct list_elem *elem = list_begin(&mmap_file->page_list);

	while (elem)
	{
		struct page *free_page = list_entry(elem, struct page, mmap_elem);
		if (pml4_is_dirty(thread_current()->pml4, free_page->va))
		{
			file_write_at(free_page->file_inf->file, free_page->frame->kva, free_page->file_inf->read_bytes, free_page->file_inf->ofs);
			pml4_set_dirty(thread_current()->pml4, free_page->va, false);
		}

		// vm_dealloc_page(free_page);
		elem = elem->next;
		pml4_clear_page(thread_current()->pml4, free_page->va);
	}
}



PintOS Project3 GIthub 주소 PintOS

0개의 댓글