[PintOS] Project 3 - Memory Mapped Files

novxerim·2022년 1월 27일
0

SW-Jungle

목록 보기
46/59

- Memory Mapped Files

이 섹션에서는 메모리 매핑 페이지를 구현합니다. 메모리 매핑된 페이지는 익명 페이지와 달리 파일 백업 매핑입니다.
페이지의 내용은 일부 기존 파일의 데이터를 미러링합니다. 페이지 오류가 발생하면 물리적 프레임이 즉시 할당되고 파일로부터 메모리로 내용이 복사됩니다. 메모리 매핑된 페이지가 매핑 해제되거나 스왑 아웃되면 콘텐츠의 모든 변경 사항이 파일에 반영됩니다.

mmap and munmap System Call

메모리 매핑 파일에 대한 두 가지 시스템 호출인 mmap 와 munmap를 구현하세요.
VM 시스템은 mmap 영역에서 페이지를 느리게 로드해야 하며, mmap 파일 자체를 매핑을 위한 백업 저장소로 사용해야 합니다. 이 두 가지 시스템 호출을 구현하려면 vm/file.c에 정의된 do_mmapdo_munmap을 구현하고 사용해야 합니다.


- 구현1.

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

offset 바이트에서 시작하여 (fd로 열린 파일의) length 바이트를 / addr에 있는 프로세스의 가상 주소 공간에 매핑합니다.
Maps length bytes the file open as fd starting from offset byte into the process's virtual address space at addr.
전체 파일은 addr에서 시작하여 연속적인 가상 페이지로 매핑됩니다. 파일 길이가 PGSIZE의 배수가 아닌 경우, 매핑된 최종 페이지의 일부 바이트가 파일 끝을 넘어 "삐져나옵니다(stick out)". 
페이지에 오류가 발생하면 이 바이트를 0으로 설정하고 페이지를 디스크에 다시 쓸 때 바이트를 삭제합니다(discard). 
성공하면 이 함수는 파일이 매핑된 가상 주소를 반환합니다. 실패하면 파일을 매핑하는 데 유효한 주소가 아닌 NULL을 반환해야 합니다.

fd로 열린 파일의 길이가 0바이트(zero bytes)인 경우, mmap호출이 실패할 수 있습니다. 
addr가 페이지 정렬되지 않았거나, 매핑된 페이지 범위가 실행 가능한 로드 시간에 매핑된 스택 또는 페이지를 포함하여 매핑된 기존 페이지의 집합과 겹치면 실패해야 합니다. 
Linux에서 addr가 NULL이면 커널은 매핑을 생성할 적절한 주소를 찾습니다. 간단하게 하기 위해 주어진 addr의 위치에서 mmap을 시도할 수 있습니다. 
따라서, addr가 0이면 반드시 실패해야 하는데, 왜냐하면 일부 Pintos 코드는 가상 페이지 0이 매핑되지 않은 것으로 가정하기 때문입니다. 
length가 0일 때도 mmap이 실패해야 합니다. 마지막으로 콘솔 입력 및 출력을 나타내는 file descriptors는 매핑할 수 없습니다.

메모리 매핑된 페이지도 익명 페이지와 마찬가지로 게으른 방식(lazy manner)으로 할당되어야 합니다. vm_alloc_page_with_initializer 또는 vm_alloc_page 를 사용하여 페이지 객체를 만들 수 있습니다.

  • 코드
// [userprog>syscall.c]
// Project 3-3 mmap
void *mmap (void *addr, size_t length, int writable, int fd, off_t offset){
	// Fail : map to i/o console, zero length, map at 0, addr not page-aligned
	if(fd == 0 || fd == 1 || length == 0 || addr == 0 || pg_ofs(addr) != 0 || offset > PGSIZE)
		return NULL;

	// Find file by fd
	struct file *file = find_file_by_fd(fd);	

	// Fail : NULL file, file length is zero
	if (file == NULL || file_length(file) == 0)
		return NULL;

	return do_mmap(addr, length, writable, file, offset);
}
  • do_mmap
// [vm>file.c]
/* Do the mmap */
// similar design with load_segment - map consecutive pages until length runs out
void *
do_mmap (void *addr, size_t length, int writable,
		struct file *file, off_t offset) {

	// ASSERT((read_bytes + zero_bytes) % PGSIZE == 0);
	// ASSERT(ofs % PGSIZE == 0);


	void *start_addr = addr; // return value or in case of fail - free from this addr
	struct thread *t = thread_current();
	struct page *page;
	int page_cnt = 1; // How many consecutive pages are mapped with file?

	size_t flen = file_length(file) - offset; // file left for reading

	// file_seek(file, offset); // change offset itself
	
	// allocate at least one page
	// throw off data that sticks out 
	while(length > 0){
		// Fail : pages mapped overlaps other existing pages or kernel memory
		if(spt_find_page(&t->spt, addr) != NULL || is_kernel_vaddr(addr)){
			void *free_addr = start_addr; // get page from this user vaddr and destroy them
			while(free_addr < addr){
				// free allocated uninit page
				page = spt_find_page(&t->spt, free_addr);

				// destroy(page); // uninit destroy - free aux
				// free(page->frame);
				// free(page);
				// remove_page(page);
				spt_remove_page(&t->spt, page);


				free_addr += PGSIZE;
			}
			return NULL;
		}

		size_t page_read_bytes = MIN(length, flen) < PGSIZE ? MIN(length, flen) : PGSIZE;
		size_t page_zero_bytes = PGSIZE - page_read_bytes;

		#ifdef DBG
		printf("(do_mmap) File length %d - read length %d\n", file_length(file), length);
		#endif

		// file info to load onto memory once fault occurs
		struct lazy_load_info *lazy_load_info = malloc(sizeof(struct lazy_load_info));
		lazy_load_info->file = file_reopen(file); // mmap-close - closing file after mmap
		lazy_load_info->page_read_bytes = page_read_bytes;
		lazy_load_info->page_zero_bytes = page_zero_bytes;
		lazy_load_info->offset = offset;
		void *aux = lazy_load_info;
		if (!vm_alloc_page_with_initializer(VM_FILE, addr,
											writable, lazy_load_segment_for_file, aux))
			return NULL;

		// record page_cnt
		page = spt_find_page(&t->spt, addr);
		page->page_cnt = page_cnt;

		offset += page_read_bytes;
		flen -= page_read_bytes;
		length -= length < PGSIZE ? length : PGSIZE; // prevent unsigned underflow
		addr += PGSIZE;
		page_cnt++;
	}

	return start_addr;
}
  • find_file_by_fd (구현)
// Project 2-4. File descriptor
// Check if given fd is valid, return cur->fdTable[fd]
static struct file *find_file_by_fd(int fd)
{
	struct thread *cur = thread_current();

	// Error - invalid fd
	if (fd < 0 || fd >= FDCOUNT_LIMIT)
		return NULL;

	return cur->fd_table[fd]; // automatically returns NULL if empty
}
  • lady_load_segment_for_file (구현)
// used in lazy allocation - from process.c
// 나중에 struct file_page에 vm_initializer *init 같은거 만들어서 uninit의 init 함수 (lazy_load_segment) 넘겨주는 식으로 리팩토링 
// -> X uninit page 만드는거야
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 munmap (void *addr);

지정된 주소 범위 addr에 대한 매핑을 해제합니다. 이 주소(addr)는 아직 매핑 해제되지 않은(has not yet been unmmaped) 동일한 프로세스에서 / mmap에 대한 이전 호출에 의해 반환된 가상 주소여야 합니다.

모든 매핑은 프로세스가 종료될 때 exit또는 다른 수단을 통해 암시적으로(implicitly) 매핑 해제(unmapped)됩니다. 
매핑이 매핑 해제되면, 암시적이든 명시적이든, 프로세스에 의해 기록된 모든 페이지가 파일에 다시 기록되고, 기록되지 않은 페이지는 기록되지 않아야 합니다. 그런 다음 페이지는 프로세스의 가상 페이지 목록에서 제거됩니다.

파일을 닫거나 제거해도 매핑이 해제되지 않습니다. 일단 매핑이 생성되면, Unix 규칙에 따라, munmap이 호출되거나 or 프로세스가 종료 될 때까지 → 매핑이 유효합니다 . 자세한 내용은 Removing an Open File (열린 파일 제거) 를 참조하십시오. file_reopen 함수를 사용하여 파일의 각 매핑에 대해 별도의 독립적인 참조를 얻어야 합니다.

둘 이상의 프로세스가 동일한 파일을 매핑하는 경우, 일관된 데이터를 볼 필요가 없습니다. Unix는 두 매핑이 동일한 물리적 페이지를 공유하도록 하여 이를 처리하고, mmap 시스템 호출(system call)에는 클라이언트가 페이지가 공유 또는 비공개(즉, copy-on-write)인지 지정할 수 있도록 하는 인수(argument)도 있습니다.

필요에 따라 vm/vm.c의 vm_file_init및 vm_file_initializer를 수정할 수 있습니다.

  • 코드
// [userprog>syscall.c]
// Project 3-3 mmap
void munmap (void *addr){
	do_munmap(addr);
}
  • do_munmap
// [vm>file.c]
/* Do the munmap */
void
do_munmap (void *addr) {
	struct thread *t = thread_current();
	struct page *page;

	page = spt_find_page(&t->spt, addr);
	//int prev_cnt = 0;
	int prev_cnt = page->page_cnt - 1; //if the file size is bigger than memmory space, first page of consecutive file-pages in memory is not the first page of the file.

	// Check if the page is file_page or uninit_page to be transmuted into file_page and then its consecutive
	while(page != NULL 
		&& (page->operations->type == VM_FILE 
			|| (page->operations->type == VM_UNINIT && page->uninit.type == VM_FILE))
		&& page->page_cnt == prev_cnt + 1){
		if(pml4_is_dirty(t->pml4, addr)){
			struct file *file = page->file.file;
			size_t length = page->file.length;
			off_t offset = page->file.offset;

			if(file_write_at(file, addr, length, offset) != length){
				// #ifdef DBG
				// TODO - Not properly written-back
			}
		}	

		prev_cnt = page->page_cnt;

		// removed from the process's list of virtual pages.
		// pml4_clear_page(thread_current()->pml4, page->va);
		// destroy(page);
		// free(page->frame);
		// free(page);
		//remove_page(page);
		spt_remove_page(&t->spt, page);

		addr += PGSIZE;
		page = spt_find_page(&t->spt, addr);
	}
}

- 구현3.

void vm_file_init (void);

파일 지원 페이지 하위 시스템을 초기화합니다. 
이 함수에서는, 파일 백업 페이지와 관련된 모든 것을 설정할 수 있습니다.

  • 코드
// [vm>file.c]
/* The initializer of file vm */
void vm_file_init (void) {
}

딱히 구현된 내용 없음


- 구현4.

bool file_backed_initializer (struct page *page, enum vm_type type, void *kva);

file-backed page를 초기화합니다. 이 함수는 먼저 page->operations의 파일 지원 페이지에 대한 핸들러를 설정합니다. 메모리를 백업(backing)하는 파일과 같은 페이지 구조(page struct)에 대한 일부 정보를 업데이트할 수 있습니다.

  • 코드
        // [vm>file.c]
        /* Initialize the file backed page */
        bool
        file_backed_initializer (struct page *page, enum vm_type type, void *kva) {
        	struct uninit_page *uninit = &page->uninit;
        	// vm_initializer *init = uninit->init;
        	void *aux = uninit->aux;
        
        	/* Set up the handler */
        	page->operations = &file_ops;
        
        	memset(uninit, 0, sizeof(struct uninit_page));
        
        	struct lazy_load_info *info = (struct lazy_load_info *)aux;
        	struct file_page *file_page = &page->file;
        	file_page->file = info->file;
        	file_page->length = info->page_read_bytes;
        	file_page->offset = info->offset;
        	return true;
        }

- 구현5.

static void file_backed_destroy (struct page *page);

연결된(associated) 파일을 닫아 파일 백업 페이지를 destroy합니다. 내용이 더러우면(If the content is dirty), 변경 사항을 파일에 다시 기록하십시오. 이 함수에서 페이지 구조(page struct)를 해제(free)할 필요가 없습니다. file_backed_destroy의 호출자(caller)가 처리(handle)해야 합니다.

  • 코드
/* Destory the file backed page. PAGE will be freed by the caller. */
static void
file_backed_destroy (struct page *page) {
	struct file_page *file_page UNUSED = &page->file;
	close(file_page->file);
}

profile
블로그 이전했습니다. https://yerimi11.tistory.com/

0개의 댓글