
2025.06.11
오늘한 내용 : PintOS - Project3: Virtual Memory - mmap 구현 완료
WEEK 13 : 정글 끝까지(PintOS) - Virtual Memory
9 of 141 tests failed.
lazy_load_segment()안에서aux를 해제하면, 그load_info구조체가 초기화 함수(initializer) 역할이 아닌 페이지 폴트 핸들러(swap-in)에서도 재사용된다는 점에서 문제가 생깁니다.구체적으로:
초기화 vs. 페이로드(load) 시점
vm_alloc_page_with_initializer(VM_ANON, upage, writable, lazy_load_segment, aux)는
- 초기화 함수(initializer) 로
lazy_load_segment를 넘긴 것이 아니라, 첫 페이지 폴트 시 호출될 콜백으로aux와 함께 등록하는 겁니다.- 즉, 초기화 단계가 따로 있고, 그 다음 첫 접근(페이로드) 단계에서
lazy_load_segment가 호출됩니다.
aux메모리의 생명 주기
aux로 넘긴struct load_info *는,
- 초기화 함수에서
page->uninit.aux에 보관하고,- swap-in 콜백(
lazy_load_segment)가 호출될 때 인자로 전달됩니다.- 따라서 이
aux구조체는swap_in단계가 끝난 뒤(첫 페이지 폴트 처리 후)에도,
- *언매핑(destroy)**이나
- 스왑아웃(swap_out) 시점까지 page->uninit.aux 또는
page->file등 내부 자료구조에서 읽혀야 할 수도 있습니다.
free(info)를 여기서 하면 안 되는 이유
lazy_load_segment는 첫 페이지 폴트에만 호출되는 게 아니라, 페이지가 스왑아웃됐다가 다시 스왑인되는 여러 차례 호출될 수 있습니다.
aux를 즉시 해제해 버리면, 이후 스왑인/언매핑 시에page->uninit.aux를 참조할 수 없게 되어 힙 오염이나 읽기 실패를 일으킵니다.fork_read테스트는 자식 프로세스가 부모로부터load_info를 물려받고, 여러 번 페이지 폴트를 발생시키면서 코드를 읽어야 통과하는데,aux가 사라져 버리면 그 다음 폴트에서 로딩에 실패해 버립니다.올바른 패턴
- 초기화 함수(
file_backed_initializer또는anon_initializer) 에서만aux를 꺼내서 내장 필드(page->file또는page->anon등)에 복사한 뒤, 그때free(aux)해 줍니다.- swap-in 콜백(
file_backed_swap_in또는lazy_load_segment) 에서는 절대aux를 해제하지 않습니다.- 언매핑(
destroy) 시점이나 스왑아웃(swap_out) 시점에서 필요한 모든 정보가 이미page구조체 내부에 복사되어 있고, 그때aux를 해제할 필요도 없습니다.
static bool
lazy_load_segment(struct page *page, void *aux)
{
...
/* 2) 파일 위치 설정 후, read_bytes만큼 읽어서 kva에 복사 */
file_seek(file, offset);
if (file_read(file, kva, read_bytes) != (int)read_bytes)
{
free(info);
return false;
}
/* 3) 나머지 부분(zero_bytes)만큼 0으로 채움 */
memset(kva + read_bytes, 0, zero_bytes);
// free(info); <-------------------------여기 주석처리!
return true;
}
size_t page_cnt = (length + PGSIZE - 1) / PGSIZE;
if (is_kernel_vaddr(addr + (page_cnt * PGSIZE) - 1)) // mmap-kernel 테스트
return NULL;
if (offset < 0 || file_length(file) < offset || offset % PGSIZE != 0)
return NULL;
void process_exit(void)
{ ...
// TODO: fd_table 순회하여 file_close()
...
#ifdef VM
/* 커널 스레드가 아닌, 사용자 프로세스 페이지만 */
if (curr->pml4 != NULL) /* 또는 SPT가 초기화된 스레드만 */
{
/* 1) SPT 해시 전체 순회하며 file-backed 페이지만 골라 do_munmap 호출 */
void **starts = NULL;
size_t nstarts = 0;
struct hash_iterator hi;
hash_first(&hi, &curr->spt.spt_hash);
while (hash_next(&hi))
{
struct page *p = hash_entry(hash_cur(&hi), struct page, hash_elem);
/* 2) 파일 매핑된 페이지만 처리 (VM_FILE 타입) */
if (p->operations->type == VM_FILE)
{
struct file_page *aux = p->uninit.aux;
/* 3) 매핑의 첫 페이지(start_addr)에서만 수집 */
if (p->va == aux->start_addr)
{
/* 4) 중복 저장 방지: 이미 수집된 주소인지 확인 */
bool seen = false;
for (size_t i = 0; i < nstarts; i++)
if (starts[i] == aux->start_addr)
{
seen = true;
break;
}
/* 5) 새로 발견된 시작 주소이면 배열에 추가 */
if (!seen)
{
void **tmp = realloc(starts, sizeof(void *) * (nstarts + 1));
if (tmp)
starts = tmp;
else
break; /* OOM—give up early */
starts[nstarts++] = aux->start_addr;
}
}
}
}
/* 2) Now unmap each mapping (this mutates the hash, but it’s safe because
we’re no longer iterating it) */
for (size_t i = 0; i < nstarts; i++)
do_munmap(starts[i]);
free(starts);
}
#endif
process_cleanup();
}
struct file_page
{
struct file *file; /* file_reopen() 으로 얻은 파일 핸들 */
off_t offset; /* 이 페이지가 파일에서 읽어올 시작 오프셋 */
uint32_t read_bytes; /* 이 페이지에 실제로 읽어들일 바이트 수 */
uint32_t zero_bytes; /* 페이지의 나머지 부분(읽을 데이터 이후)을 0으로 채울 바이트 수 */
bool writable; /* 쓰기 권한 */
------------------------------------- 추가
void *start_addr; /* 이 매핑의 시작 가상주소 */
size_t length; /* 매핑 전체 길이 (바이트) */
};
struct file_page *aux = (struct file_page *)malloc(sizeof(*aux));
if (aux == NULL)
return NULL;
aux->file = file_cp;
aux->offset = offset;
aux->read_bytes = page_read_bytes;
aux->zero_bytes = page_zero_bytes;
aux->writable = writable;
---------------------------------- 추가
aux->start_addr = addr;
aux->length = length;
/* Do the munmap */
void do_munmap(void *addr)
{
struct thread *cur = thread_current();
addr = pg_round_down(addr);
/* 첫 페이지에서 file 핸들 꺼내기 */
struct page *page = spt_find_page(&cur->spt, addr);
if (!page)
return;
struct file_page *first_aux = (struct file_page *)page->uninit.aux;
struct file *file = first_aux->file;
while (true)
{
/* 매핑 정보 조회*/
struct page *page = spt_find_page(&thread_current()->spt, addr);
if (page == NULL)
break;
struct file_page *aux = (struct file_page *)page->uninit.aux;
/* 수정된 페이지(dirty bit == 1)는 파일에 업데이트해놓는다. 이후에 dirty bit을 0으로 만든다. */
if (pml4_is_dirty(thread_current()->pml4, page->va)) // pml4_is_dirty함수는 페이지의 dirty bit이 1이면 true를, 0이면 false를 리턴한다.
{
/* 물리 프레임에 변경된 데이터를 다시 디스크 파일에 업데이트해주는 함수. buffer에 있는 데이터를 size만큼, file의 file_ofs부터 써준다 */
file_write_at(aux->file, page->frame->kva, aux->read_bytes, aux->offset);
/* 인자로 받은 dirty의 값이 1이면 page의 dirty bit을 1로, 0이면 0으로 변경해준다. */
pml4_set_dirty(thread_current()->pml4, page->va, 0);
}
/* pml4 페이지 안에서 va와 매핑된거 지우는 함수 */
pml4_clear_page(thread_current()->pml4, page->va);
/* c) physical frame 해제 */
if (page->frame)
{
palloc_free_page(page->frame->kva);
free(page->frame);
}
/* d) SPT 엔트리 & aux 해제 */
spt_remove_page(&cur->spt, page);
free(aux);
addr += PGSIZE;
}
/* 4) 파일 닫기 */
file_close(file);
}
/* Exit the process. This function is called by thread_exit (). */
void process_exit(void)
{
/* TODO: Your code goes here.
* TODO: Implement process termination message (see
* TODO: project2/process_termination.html).
* TODO: We recommend you to implement process resource cleanup here. */
struct thread *curr = thread_current();
struct thread *parent;
struct list_elem *e;
struct child_status *c;
/* --- USERPROG 에서만 종료메시지 찍게 설정하기 --- */
if (curr->parent_tid != TID_ERROR)
{
/* 1) 종료 메시지 출력 */
printf("%s: exit(%d)\n", curr->name, curr->exit_status);
#ifdef VM
/* 커널 스레드가 아닌, 사용자 프로세스 페이지만 */
if (curr->pml4 != NULL) /* 또는 SPT가 초기화된 스레드만 */
{
/* 1) SPT 해시 전체 순회하며 file-backed 페이지만 골라 do_munmap 호출 */
void **starts = NULL;
size_t nstarts = 0;
struct hash_iterator hi;
hash_first(&hi, &curr->spt.spt_hash);
while (hash_next(&hi))
{
struct page *p = hash_entry(hash_cur(&hi), struct page, hash_elem);
/* 2) 파일 매핑된 페이지만 처리 (VM_FILE 타입) */
if (p->operations->type == VM_FILE)
{
struct file_page *aux = p->uninit.aux;
/* 3) 매핑의 첫 페이지(start_addr)에서만 수집 */
if (p->va == aux->start_addr)
{
/* 4) 중복 저장 방지: 이미 수집된 주소인지 확인 */
bool seen = false;
for (size_t i = 0; i < nstarts; i++)
if (starts[i] == aux->start_addr)
{
seen = true;
break;
}
/* 5) 새로 발견된 시작 주소이면 배열에 추가 */
if (!seen)
{
void **tmp = realloc(starts, sizeof(void *) * (nstarts + 1));
if (!tmp)
break;
starts = tmp;
starts[nstarts++] = aux->start_addr;
}
}
}
}
/* 2) Now unmap each mapping (this mutates the hash, but it’s safe because
we’re no longer iterating it) */
for (size_t i = 0; i < nstarts; i++)
do_munmap(starts[i]);
free(starts);
}
#endif
/* 2) 부모에게 exit 상태 전달 및 sema_up() */
if (curr->parent_tid != TID_ERROR)
{
parent = thread_by_tid(curr->parent_tid);
if (parent != NULL)
{
for (e = list_begin(&parent->children);
e != list_end(&parent->children);
e = list_next(e))
{
c = list_entry(e, struct child_status, elem);
if (c->tid == curr->tid)
{
c->exit_status = curr->exit_status;
c->has_exited = true;
sema_up(&c->sema);
break;
}
}
}
}
// TODO: fd_table 순회하여 file_close()
for (int fd = 0; fd < MAX_FD; fd++)
{
struct file *f = curr->fd_table[fd];
if (f != NULL && f != &console_in /* stdin 예외 */
&& f != &console_out) /* stdout 예외 */
{
file_close(curr->fd_table[fd]);
curr->fd_table[fd] = NULL;
}
}
}
process_cleanup();
}
FAIL tests/vm/cow/cow-simple
run: two phys addrs should be the same.: FAILED
pass tests/userprog/args-none
pass tests/userprog/args-single
pass tests/userprog/args-multiple
pass tests/userprog/args-many
pass tests/userprog/args-dbl-space
pass tests/userprog/halt
pass tests/userprog/exit
pass tests/userprog/create-normal
pass tests/userprog/create-empty
pass tests/userprog/create-null
pass tests/userprog/create-bad-ptr
pass tests/userprog/create-long
pass tests/userprog/create-exists
pass tests/userprog/create-bound
pass tests/userprog/open-normal
pass tests/userprog/open-missing
pass tests/userprog/open-boundary
pass tests/userprog/open-empty
pass tests/userprog/open-null
pass tests/userprog/open-bad-ptr
pass tests/userprog/open-twice
pass tests/userprog/close-normal
pass tests/userprog/close-twice
pass tests/userprog/close-bad-fd
pass tests/userprog/read-normal
pass tests/userprog/read-bad-ptr
pass tests/userprog/read-boundary
pass tests/userprog/read-zero
pass tests/userprog/read-stdout
pass tests/userprog/read-bad-fd
pass tests/userprog/write-normal
pass tests/userprog/write-bad-ptr
pass tests/userprog/write-boundary
pass tests/userprog/write-zero
pass tests/userprog/write-stdin
pass tests/userprog/write-bad-fd
pass tests/userprog/fork-once
pass tests/userprog/fork-multiple
pass tests/userprog/fork-recursive
pass tests/userprog/fork-read
pass tests/userprog/fork-close
pass tests/userprog/fork-boundary
pass tests/userprog/exec-once
pass tests/userprog/exec-arg
pass tests/userprog/exec-boundary
pass tests/userprog/exec-missing
pass tests/userprog/exec-bad-ptr
pass tests/userprog/exec-read
pass tests/userprog/wait-simple
pass tests/userprog/wait-twice
pass tests/userprog/wait-killed
pass tests/userprog/wait-bad-pid
pass tests/userprog/multi-recurse
pass tests/userprog/multi-child-fd
pass tests/userprog/rox-simple
pass tests/userprog/rox-child
pass tests/userprog/rox-multichild
pass tests/userprog/bad-read
pass tests/userprog/bad-write
pass tests/userprog/bad-read2
pass tests/userprog/bad-write2
pass tests/userprog/bad-jump
pass tests/userprog/bad-jump2
pass tests/vm/pt-grow-stack
pass tests/vm/pt-grow-bad
pass tests/vm/pt-big-stk-obj
pass tests/vm/pt-bad-addr
pass tests/vm/pt-bad-read
pass tests/vm/pt-write-code
pass tests/vm/pt-write-code2
pass tests/vm/pt-grow-stk-sc
pass tests/vm/page-linear
pass tests/vm/page-parallel
pass tests/vm/page-merge-seq
pass tests/vm/page-merge-par
pass tests/vm/page-merge-stk
pass tests/vm/page-merge-mm
pass tests/vm/page-shuffle
pass tests/vm/mmap-read
pass tests/vm/mmap-close
pass tests/vm/mmap-unmap
pass tests/vm/mmap-overlap
pass tests/vm/mmap-twice
pass tests/vm/mmap-write
pass tests/vm/mmap-ro
pass tests/vm/mmap-exit
pass tests/vm/mmap-shuffle
pass tests/vm/mmap-bad-fd
pass tests/vm/mmap-clean
pass tests/vm/mmap-inherit
pass tests/vm/mmap-misalign
pass tests/vm/mmap-null
pass tests/vm/mmap-over-code
pass tests/vm/mmap-over-data
pass tests/vm/mmap-over-stk
pass tests/vm/mmap-remove
pass tests/vm/mmap-zero
pass tests/vm/mmap-bad-fd2
pass tests/vm/mmap-bad-fd3
pass tests/vm/mmap-zero-len
pass tests/vm/mmap-off
pass tests/vm/mmap-bad-off
pass tests/vm/mmap-kernel
pass tests/vm/lazy-file
pass tests/vm/lazy-anon
FAIL tests/vm/swap-file
FAIL tests/vm/swap-anon
FAIL tests/vm/swap-iter
pass tests/vm/swap-fork
pass tests/filesys/base/lg-create
pass tests/filesys/base/lg-full
pass tests/filesys/base/lg-random
pass tests/filesys/base/lg-seq-block
pass tests/filesys/base/lg-seq-random
pass tests/filesys/base/sm-create
pass tests/filesys/base/sm-full
pass tests/filesys/base/sm-random
pass tests/filesys/base/sm-seq-block
pass tests/filesys/base/sm-seq-random
pass tests/filesys/base/syn-read
pass tests/filesys/base/syn-remove
pass tests/filesys/base/syn-write
pass tests/threads/alarm-single
pass tests/threads/alarm-multiple
pass tests/threads/alarm-simultaneous
pass tests/threads/alarm-priority
pass tests/threads/alarm-zero
pass tests/threads/alarm-negative
pass tests/threads/priority-change
pass tests/threads/priority-donate-one
pass tests/threads/priority-donate-multiple
pass tests/threads/priority-donate-multiple2
pass tests/threads/priority-donate-nest
pass tests/threads/priority-donate-sema
pass tests/threads/priority-donate-lower
pass tests/threads/priority-fifo
pass tests/threads/priority-preempt
pass tests/threads/priority-sema
pass tests/threads/priority-condvar
pass tests/threads/priority-donate-chain
FAIL tests/vm/cow/cow-simple
4 of 141 tests failed.
디스크 스왑 남았다.
근데 내일 발표다.
핀토스 주차가 마무리 되었고 난 기간 내에 끝내지 못했다. 따로 더 해야지...
지금 오전 3시 19분인데 사실 아직 발표자료도 못 만들었다. 얼른 해야지..
6.12 추가) 같은 조 예찬 형님 코드를 들으면서 2/ 141 까지 발전 시켰다.
FAIL tests/vm/swap-iter
FAIL tests/vm/cow/cow-simple
2 of 141 tests failed.