
2025.06.10
오늘한 내용 : PintOS - Project3: Virtual Memory - mmap 구현 중
WEEK 13 : 정글 끝까지(PintOS) - Virtual Memory
6. mmap/munmap(do_mmap, do_munmap) (
vm/file.c+userprog/syscall.c):사용자 API(시스템콜)로 파일 매핑 기능 추가.
vm_alloc_page_with_initializer(VM_ANON, …, lazy_load_segment, aux) 를 써서, 페이지 폴트 때 파일에서 읽어 들이되 “익명(anonymous)” 페이지로 취급합니다.vm_alloc_page_with_initializer(VM_FILE, …, file_backed_initializer, aux) 를 써서, 페이지 폴트 시 파일에서 읽고, 쓰기된 페이지만 원본 파일에 다시 써 줍니다.munmap 하거나 페이지가 스왑아웃될 때 더티 비트가 설정된 페이지만 파일에 기록되는, 진정한 “파일 백업(file-backed)” 매핑입니다.| 구분 | load_segment | mmap / do_mmap |
|---|---|---|
| 타입 | VM_ANON | VM_FILE |
| 초기화 콜백 | lazy_load_segment | file_backed_initializer |
| 쓰기 반영 | 반영 안 함 (익명 페이지) | 반영 함 (파일에 쓰기) |
| 사용처 | 실행 파일(ELF) 세그먼트 로딩 | 프로그램 임의 파일 매핑 |
struct file_page
{
struct file *file; /* file_reopen() 으로 얻은 파일 핸들 */
off_t ofs; /* 이 페이지가 파일에서 읽어올 시작 오프셋 */
size_t read_bytes; /* 이 페이지에 실제로 읽어들일 바이트 수 */
size_t zero_bytes; /* 페이지의 나머지 부분(읽을 데이터 이후)을 0으로 채울 바이트 수 */
bool writale; /* 쓰기 권한 */
};
/* 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;
}
/* 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;
}
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;
}