[TIL] [WEEK11-12] Pintos Project(3) Memory Mapped Files(1)

woo__j·2024년 8월 23일
0

Pintos Project

목록 보기
13/14

핀토스 정리를 다 끝내지 못 한 상태에서 나만무 주간이 시작되어 계속해서 작성을 미뤘다.
개인적인 욕심으로 핀토스만은 마지막까지 정리를 하고 싶었기 때문에 뜨문뜨문한 기억으로 정리를 마무리 지어보려고 한다ㅎ..
거의 세 달이 되어가기 때문에 그 때만큼 자세하게 작성하진 못 할 것 같다.
핀토스 과정을 돌이켜보면 어렵고 힘들긴 했지만 배운 게 많기도 하고, 은근 재미가 있었던 것 같기도 해서(기억미화 일수도) 나중에 한 번 다시 제대로 해보고 싶은 마음이 좀 들긴 한다.
그게 언제가 될 수 있을지는 모르겠다만ㅎ


📍 네 번째 과제, Memory Mapped Files

이전에 Anonymous Page를 구현했었는데, 이번 과제에선 Memory Mapped Page를 구현한다.

Memory Mapped Page? 🤔
이는 Anonymous Page와 반대로 메모리가 매핑된 페이지로, 파일 기반 페이지라고도 한다.
페이지의 콘텐츠는 일부 기존 파일의 데이터를 미러링한다.

1. 페이지 폴트가 발생했을 때

프로그램이 접근하려는 메모리 페이지가 현재 메모리에 로드되지 않았을 때 페이지 폴트가 발생한다.
이 때 해야 할 작업은 크게 두 가지가 있다.

  • 물리적 프레임 할당: 프로그램이 접근하려는 주소를 처리하기 위해 실제 물리적 공간 즉시 할당
  • 파일에서 메모리로 데이터 복사: 할당된 메모리 공간에 데이터를 읽어와 복사

해당 작업이 완료되면 프로그램이 할당된 메모리 공간을 통해 파일 데이터를 사용할 수 있게 된다.

2. 페이지가 unmapped 또는 swapped out 될 때

  • unmmaped?: 메모리에서 페이지를 더 이상 사용하지 않기로 결정되었을 때 페이지가 unmapped 됨
  • swapped out?: 메모리에 공간이 부족할 때, 사용 중인 페이지를 디스크로 내보내는 작업

메모리 매핑된 페이지의 데이터가 변경되었을 수 있기 때문에 파일의 내용이 업데이트 되어야 한다. 즉, 페이지가 더 이상 필요 없거나 메모리에서 쫒겨나게 되었을 때, 변경된 내용을 파일에 저장한다.

이와 같은 작업들을 위해 우리가 이번 과제에서 해야 할 일은 먼저 메모리 매핑에 대한 시스템 콜을 구현하는 것이다.


🛠️ 메모리 매핑 시스템 콜 - mmap & munmap 구현

VM 시스템은 mmap 영역에서 페이지를 lazy load 하고 mmap된 파일 자체를 매핑을 위한 백업 저장소로 사용해야 하기 때문에 do_mmap 과 do_munmap을 구현해서 사용해야 한다. (+ syscall_handler 수정)


해당 내용(git book)을 기반으로 추가적인 정리를 해보자면

mmap이 이루어질 수 없는 Cases

do_mmap()

이런 조건들로 구현을 하게 되면 총 3개의 파일을 수정하게 된다.


1. userprog/syscall.c

syscall_handler()에 sys_mmap을 추가해준다. 이 때 예외처리 해주는 것을 잊지 말자.

switch (syscall_num)
{
    ...
    	case SYS_MMAP:
 		f->R.rax = mmap(f->R.rdi, f->R.rsi, f->R.rdx, f->R.r10, f->R.r8);
 		break;
    ...
}

void *mmap(void *addr, size_t length, int writable, int fd, off_t offset)
 {
 	/* addr이 NULL이거나 페이지 경계에 있지 않은 경우 */
 	if (!addr || addr != pg_round_down(addr))
 		return NULL;

 	/* offset이 페이지 경계에 있지 않은 경우 */
 	if (offset != pg_round_down(offset))
 		return NULL;

 	/* addr과 addr+length가 user 영역이 아닌 경우 */
 	if (!is_user_vaddr(addr) || !is_user_vaddr(addr + length))
 		return NULL;

 	/* addr에 할당된 페이지가 이미 있는 경우 */
 	if (spt_find_page(&thread_current()->spt, addr))
 		return NULL;

 	struct file *f = process_get_file(fd);

 	/* fd에 해당하는 파일이 NULL인 경우 */
 	if (f == NULL)
 		return NULL;

 	/* 파일의 길이가 0이거나, 읽어올 length가 0보다 작은 경우 */
 	if (file_length(f) == 0 || (int)length <= 0)
 		return NULL;

 	/* 위처럼 mmap이 이루어질 수 없는 case들을 제외하고는 do_mmap을 호출해 매핑 후 매핑된 가상주소 반환 */
 	return do_mmap(addr, length, writable, f, offset);
 }

2. include/vm/vm.h

page 구조체에 매핑에 필요한 총 페이지 수를 저장할 멤버 변수를 추가해주기

struct page
{
	...
    int mapped_page_cnt; /* 매핑에 사용한 총 페이지 수 */
    ...
}

3. vm/file.c

do_mmap 구현하기

/* Do the mmap */
 void *
 do_mmap (void *addr, size_t length, int writable,
 		struct file *file, off_t offset) {
 			/* reopen()으로 해당 파일에 대한 새로운 파일 디스크립터 얻음 */
 	struct file *f = file_reopen(file);
 	/* 매핑 성공 시 반환할 가상 주소 */
 	void *start_addr = addr;

 	/* 매핑에 쓰인 총 페이지 수 */
 	int total_page_count;

 	/* 읽어올 길이가 PGSIZE보다 작을 시 페이지 수 1개 */
 	if (length <= PGSIZE)
 		total_page_count = 1;
 	/* 일어올 길이가 PGSIZE로 나누어 떨어지지 않는다면 남은 부분을 적재하기 위해 나눈 몫에 +1 */
 	else if (length % PGSIZE != 0)
 		total_page_count = length / PGSIZE + 1;
 	/* 일어올 길이가 PGSIZE로 나누어 떨어진다면 페이지 수는 나눈 몫 */
 	else
 		total_page_count = length / PGSIZE;

 	size_t read_bytes = file_length(f) < length ? file_length(f) : length;
 	size_t zero_bytes = PGSIZE - read_bytes % PGSIZE;

 	ASSERT((read_bytes + zero_bytes) % PGSIZE == 0);
 	ASSERT(pg_ofs(addr) == 0);	  // upage 페이지 정렬 확인
 	ASSERT(offset % PGSIZE == 0); // offset 페이지 정렬 확인

 	while (read_bytes > 0 || zero_bytes > 0)
 	{
 		/* 페이지 채우기 */
 		/* page_read_bytes만큼을 읽고, page_zero_bytes만큼을 0으로 채움 */
 		size_t page_read_bytes = read_bytes < PGSIZE ? read_bytes : PGSIZE;
 		size_t page_zero_bytes = PGSIZE - page_read_bytes;

 		struct lazy_load_arg *lazy_load_arg = (struct lazy_load_arg *)malloc(sizeof(struct lazy_load_arg));
 		lazy_load_arg->file = f;
 		lazy_load_arg->ofs = offset;
 		lazy_load_arg->read_bytes = page_read_bytes;
 		lazy_load_arg->zero_bytes = page_zero_bytes;

 		/* vm_alloc_page_with_initializer로 lazy loading할 페이지 생성 (type은 file-backed) */
 		if (!vm_alloc_page_with_initializer(VM_FILE, addr, writable, lazy_load_segment, lazy_load_arg))
 			return NULL;

 		struct page *p = spt_find_page(&thread_current()->spt, start_addr);
 		p->mapped_page_cnt = total_page_count;

 		/* read bytes와 zero_bytes 감소시키고 가상 주소 증가 */
 		read_bytes -= page_read_bytes;
 		zero_bytes -= page_zero_bytes;
 		addr += PGSIZE;
 		offset += page_read_bytes;
 	}
 	return start_addr;
 }

이렇게 하면 Memory Mapped Files 파트에서 Mapping에 관한 내용은 끝난다.
다음 글에서 Unmapping에 관한 내용을 다루면서 해당 파트를 끝내보겠다!

0개의 댓글

관련 채용 정보