[TIL/크래프톤 정글] DAY 93

배재준·2025년 6월 10일

크래프톤 정글 - TIL

목록 보기
85/93
post-thumbnail

2025.06.10

TIL(TODAY I LEARN)


  • 오늘한 내용 : PintOS - Project3: Virtual Memory - mmap 구현 중

  • WEEK 13 : 정글 끝까지(PintOS) - Virtual Memory


6) Memory Mapped Files 구현

6. mmap/munmap(do_mmap, do_munmap) (vm/file.c + userprog/syscall.c):

사용자 API(시스템콜)로 파일 매핑 기능 추가.

  1. load_segment (VM_ANON + lazy_load_segment)
    • 주로 실행 파일(ELF)의 코드·데이터 세그먼트를 프로세스 시작 시점에 “lazy”하게 올려 줄 때 씁니다.
    • 내부적으로는 vm_alloc_page_with_initializer(VM_ANON, …, lazy_load_segment, aux) 를 써서, 페이지 폴트 때 파일에서 읽어 들이되 “익명(anonymous)” 페이지로 취급합니다.
    • 즉, 수정된 내용은 디스크의 실행 파일에 절대 반영되지 않고, 프로세스 종료 후 사라집니다.
  2. mmap / do_mmap (VM_FILE + file_backed_initializer)
    • 임의의 파일을 가상 메모리 영역에 매핑(mapping)할 때 씁니다.
    • vm_alloc_page_with_initializer(VM_FILE, …, file_backed_initializer, aux) 를 써서, 페이지 폴트 시 파일에서 읽고, 쓰기된 페이지만 원본 파일에 다시 써 줍니다.
    • 프로세스가 munmap 하거나 페이지가 스왑아웃될 때 더티 비트가 설정된 페이지만 파일에 기록되는, 진정한 “파일 백업(file-backed)” 매핑입니다.

구분load_segmentmmap / do_mmap
타입VM_ANONVM_FILE
초기화 콜백lazy_load_segmentfile_backed_initializer
쓰기 반영반영 안 함 (익명 페이지)반영 함 (파일에 쓰기)
사용처실행 파일(ELF) 세그먼트 로딩프로그램 임의 파일 매핑
  • file_page 구조체 구현
struct file_page
{
	struct file *file; /* file_reopen() 으로 얻은 파일 핸들 */
	off_t ofs;		   /* 이 페이지가 파일에서 읽어올 시작 오프셋 */
	size_t read_bytes; /* 이 페이지에 실제로 읽어들일 바이트 수 */
	size_t zero_bytes; /* 페이지의 나머지 부분(읽을 데이터 이후)을 0으로 채울 바이트 수 */
	bool writale;	   /* 쓰기 권한 */
};
  • do_mmap() 구현
/* Do the mmap */
void *
do_mmap(void *addr, size_t length, int writable,
		struct file *file, off_t offset)
{

	off_t total_len = file_length(file);
	uint32_t read_bytes = total_len - offset;
	uint32_t zero_bytes = (PGSIZE - (read_bytes % PGSIZE)) % PGSIZE;

	off_t ofs = offset;
	uint8_t *upage = (uint8_t *)addr;

	/* 기본 검증 */
	if (addr == 0 || length == 0 || file == NULL || offset < 0 || pg_ofs(addr) != 0 /* 페이지 경계인지 검사 */
		|| !is_user_vaddr(addr) || total_len == 0 || offset > total_len)
		return NULL;

	/* 매핑 메타정보 생성·등록 */
	struct mmap_info *mi = malloc(sizeof *mi);
	if (!mi)
		return NULL;
	mi->start_addr = addr;
	mi->page_cnt = (length + PGSIZE - 1) / PGSIZE;
	mi->file = file_reopen(file);
	mi->writable = writable;
	list_push_front(&thread_current()->mmap_list, &mi->elem);

	/* load_segmet와 비슷함 */
	while (read_bytes > 0 || zero_bytes > 0)
	{
		/* Do calculate how to fill this page.
		 * We will read PAGE_READ_BYTES bytes from FILE
		 * and zero the final PAGE_ZERO_BYTES bytes. */
		size_t page_read_bytes = read_bytes < PGSIZE ? read_bytes : PGSIZE;
		size_t page_zero_bytes = PGSIZE - page_read_bytes;

		/* TODO: Set up aux to pass information to the lazy_load_segment. */
		struct file_page *aux = (struct file_page *)malloc(sizeof(*aux));
		if (aux == NULL)
		{
			do_munmap(addr);
			return NULL;
		}
		aux->file = mi->file;
		aux->ofs = ofs;
		aux->read_bytes = page_read_bytes;
		aux->zero_bytes = page_zero_bytes;
		aux->writable = writable;

		if (!vm_alloc_page_with_initializer(VM_FILE, upage,
											writable, file_backed_initializer, aux))
		{
			free(aux);
			do_munmap(addr);
			return NULL;
		}

		/* Advance. */
		/* 4) 다음 반복을 위해 각 값 갱신 */
		read_bytes -= page_read_bytes;
		zero_bytes -= page_zero_bytes;
		upage += PGSIZE;
		/* 파일에서 연속적으로 다음 페이지를 읽어 오기 위함 */
		ofs += page_read_bytes; /* ofs를 읽어들인 만큼만 증가 */
	}
	return addr;
}
  • file_backed_initializer() 구현
/* Initialize the file backed page */
bool file_backed_initializer(struct page *page, enum vm_type type, void *kva)
{
	/* Set up the handler */
	page->operations = &file_ops;

	/* aux 구조체로 캐스트 */
	struct file_aux *aux = (struct file_aux *)kva;
	if (aux == NULL)
		return false;

	/* aux의 정보를 page->file 에 채워넣기 */
	page->file.file = aux->file;
	page->file.ofs = aux->ofs;
	page->file.read_bytes = aux->read_bytes;
	page->file.zero_bytes = aux->zero_bytes;

	/* 페이지가 쓰기 가능해야 할 경우 */
	page->writable = aux->writable;

	/* uninit 단계 aux는 더 이상 필요 없으니 해제 */
	free(aux);

	return true;
}
  • munmap() 구현
    • 역할 : mmap(addr, length, …) 로 잡아놓은 “파일 백업” 매핑을 해제하면서, 그동안 메모리에 올라갔던 페이지들을 정리하고, 수정된 내용만 파일에 다시 써 주는 것
/* Do the munmap */
void do_munmap(void *addr)
{
	struct thread *cur = thread_current();
	struct list_elem *e, *next;
	struct mmap_info *mi = NULL;
	/* 1) mmap_info 찾기 */
	for (e = list_begin(&cur->mmap_list); e != list_end(&cur->mmap_list); e = list_next(e))
	{
		struct mmap_info *m = list_entry(e, struct mmap_info, elem);
		if (m->start_addr == addr)
		{
			mi = m;
			break;
		}
	}
	if (mi == NULL)
		return; /* 해당 주소로 매핑된 정보가 없으면 아무 것도 하지 않음 */

	/* 2) 매핑된 각 페이지 언매핑 */
	for (int i = 0; i < mi->page_cnt; i++)
	{
		void *upage = mi->start_addr + i * PGSIZE;
		struct page *p = spt_find_page(&cur->spt, upage);
		if (p == NULL)
			continue;

		/* swap_out 내부에서 dirty 체크 및 write-back, 프레임 해제 */
		swap_out(p);

		/* 페이지 구조체도 완전 제거 */
		destroy(p);
	}

	/* 3) 파일 핸들 닫기 */
	file_close(mi->file);

	/* 4) mmap_info 리스트에서 제거 및 해제 */
	list_remove(&mi->elem);
	free(mi);
}
  • 여러 구조체 추가
thread.h
-------------------
struct thread
{
	...
	struct list mmap_list; // 추가
	...
}

--------------
file.h
--------------
struct file_page
{
	struct file *file; /* file_reopen() 으로 얻은 파일 핸들 */
	off_t ofs;		   /* 이 페이지가 파일에서 읽어올 시작 오프셋 */
	size_t read_bytes; /* 이 페이지에 실제로 읽어들일 바이트 수 */
	size_t zero_bytes; /* 페이지의 나머지 부분(읽을 데이터 이후)을 0으로 채울 바이트 수 */
	bool writable;	   /* 쓰기 권한 */
};

struct mmap_info
{
	void *start_addr;	   /* mmap() 시작 가상주소 */
	size_t page_cnt;	   /* 매핑된 페이지 수 */
	struct file *file;	   /* file_reopen() 으로 얻은 핸들 */
	bool writable;		   /* 쓰기 가능 여부 */
	struct list_elem elem; /* thread->mmap_list 연결용 */
};

--------------------
syscall.c
--------------------
void *sys_mmap(void *addr, size_t length, int writable, int fd, off_t offset)
{
	struct file *f = &thread_current()->fd_table[fd];
	return do_mmap(addr, length, writable, f, offset);
}
void sys_munmap(void *addr)
{
	do_munmap(addr);
}

syscall_handler() 케이스 추가
	case SYS_MMAP:
	{
		void *addr = (void *)f->R.rdi;
		size_t len = (size_t)f->R.rsi;
		int writable = (int)f->R.rdx;
		int fd = (int)f->R.r10;
		off_t offset = (off_t)f->R.r8;
		f->R.rax = (uint64_t)sys_mmap(addr, len, writable, fd, offset);
		break;
	}
	case SYS_MUNMAP:
	{
		void *addr = (void *)f->R.rdi;
		sys_munmap(addr);
		break;
	}

  • 위 처럼 구현을 하고, 몇개의 락을 수정한 뒤 make check를 돌려보니, 21개의 fail이 떴다.
    코어타임 이후에 같은 조 형님이 mmap 구현을 하신게 있는데 13 fail 뜨셨다고 하셔서 흡수했다.
  • 조금 코드 흐름이 달랐다. 나는 mmap_info 구조체를 만들었는데 형님은 그냥 spt_find를 계속 수행해서 do_munmap()을 수행하더라.
  • 일단은 형님의 코드로 진행하기로 했다. 화이팅!
  • 자잘한 버그 수정 후 swap을 얼른 진행해야겠다.

0개의 댓글