[pintos] Week4~5: Virtual Memory - Part.9 Copy-On-Write

이 전 포스팅까지 VM 과제에서 구현하라고 과제로 준 기능들을 구현을 완료했다. 하지만 User Program처럼 ALL PASS를 받을 수는 없었다. VM에서 ALL PASS를 받기 위해서는 Extra 과제인 Copy-On-Write 기능을 구현해야 ALL PASS를 받을 수 있다. 지금부터 Copy-On-Write가 무엇인지 알아보고 구현해 보겠다.


Copy-On-Write 란?

우리가 이 전까지 구현한 fork 와 VM 기능들은 fork가 일어날 때 부모를 복사한다. user program까지에서는 부모의 FD 정도를 복사하는 느낌이었다. 하지만 VM에서는 부모의 페이지를 복사하면서 새로운 프레임에 할당을 했다. 이 말은 부모와 자식이 내용상 같은 페이지와 프레임을 생성한 것이고 물리 메모리에 같은 데이터가 2개씩 존재한다는 것이다.

자식이 생성되고 바로 실행되면 모를까 만약 자식이 생성되고 대기한다면 이는 메모리 낭비가 될 것이다. 그래서 나온 것이 Copy-On-Write 다. 정말 간단하게 말하면 자식이 생성되며 부모의 페이지를 복사해 프레임을 할당받는 것이 아니라 부모의 페이지와 데이터를 공유하여 사용하고 있다가 어느 한쪽에서든 수정이나 쓰기 작업이 일어났다면 새로운 프레임에 해당되는 페이지만 복사해서 사용한다는 것이다.


 구현 아이디어

그렇다면 pintos에서는 어떻게 구현을 해야 할까? 가장 먼저 생각나는 것은 supplemental_page_table_copy 함수를 수정해서 프레임도 복사하는 것이 아닌 공유하도록 구현하는 것이다.

그런데 만약 부모의 SPT를 복사해 가서 자녀가 삭제되면서 부모의 SPT와 프레임을 삭제시켜버리는 경우가 있을 수 있다. 그렇다면 이것을 어떻게 막을 수 있을까? 그 점은 일종의 카운트 변수를 추가해서 공유가 일어나서 복사되면 부모의 프레임들에 공유 카운트를 증가시키는 것이다. 이후 자녀가 해당 가상주소를 접근해 수정이나 쓰기 작업이 발생하면 별도로 복사를 진행하고 부모의 프레임에 공유 카운트를 1 감소하는 것이다.

그리고 방금 언급했듯이 수정이 일어났을 때 해당 페이지를 복사해서 할당하는 기능을 구현해야 할 것이다.


기능 구현

공유 카운터 필드 추가

  • include/vm/vm.h
/* The representation of "frame" */
struct frame {
	void *kva;
	struct page *page;
	struct list_elem elem;
	int ref_count; 
};
  • Copy-On_Write가 일어나 공유된 프레임들의 경우 공유 카운터 값을 증가시켜 현재 프레임이 공유되었는지 확인 할 수 있다.

프레임 락 추가

  • vm/vm.c
void 
vm_init (void)
{
	vm_anon_init();
	vm_file_init();
#ifdef EFILESYS /* For project 4 */
	pagecache_init();
#endif
	register_inspect_intr();
	/* 이 위쪽은 수정하지 마세요 !! */
	/* TODO: 이 아래쪽부터 코드를 추가하세요 */
	list_init(&frame_table);	
	lock_init(&frame_lock);		/* COW 추가 */
}

vm_get_frame 수정

  • vm/vm.c
 * palloc()을 사용하여 프레임을 할당합니다.
 * 사용 가능한 페이지가 없으면 페이지를 교체(evict)하여 반환합니다.
 * 이 함수는 항상 유효한 주소를 반환합니다. 즉, 사용자 풀 메모리가 가득 차면,
 * 이 함수는 프레임을 교체하여 사용 가능한 메모리 공간을 확보합니다.*/
static struct frame *
vm_get_frame(void)
{
	/* TODO: Fill this function. */
	struct frame *frame = NULL;

	// 1. 유저 풀에서 새로운 페이지 할당
	void *kva = palloc_get_page(PAL_USER | PAL_ZERO);

	// 2. 할당 실패 시 PANIC
	if (kva == NULL) {
		struct frame *evicted = vm_evict_frame();
        if (evicted == NULL)
            PANIC("Eviction failed: No frame available");

        evicted->page = NULL;
		list_push_back(&frame_table, &evicted->elem);
		return evicted;
	}

	// 3. 프레임 구조체 할당 및 초기화
	frame = (struct frame *)malloc(sizeof(struct frame)); 
	if (frame == NULL)
        PANIC("Failed to allocate frame metadata");
	frame->kva = kva;
	frame->page = NULL;
	frame->ref_count = 1; 
	list_push_back(&frame_table, &frame->elem);
	return frame;
}
  • 기존 구현 코드에서 프레임이 할당될 때 공유 카운트를 1로 설정한다.
  • vm_get_frame을 통해 할당되는 프레임은 새로 할당이 되었다는 가정이기 때문에 공유카운트는 1이다.

vm_stack_growth 수정

  • vm/vm.c
 * 스택 최하단에 익명 페이지를 추가하여 사용
 * addr은 PGSIZE로 내림(정렬)하여 사용 
 * 페이지 주소관련 코드 수정 */
/* Growing the stack. */
static void
vm_stack_growth(void *addr UNUSED)
{
	// 스택 최하단에 익명 페이지 추가
	vm_alloc_page(VM_ANON | VM_MARKER_0 , pg_round_down(addr), 1); 
	vm_claim_page(pg_round_down(addr));
}
  • spt_find_page() → 페이지 검색
  • vm_do_claim_page() → 프레임을 할당하고, VA와 PA를 page table에 매핑
  • 이후 실제 스택 접근이 가능해짐

이전 구현까지는 문제가 없었지만 COW를 구현하다 보니 문제가 있어서 추가를 진행했다. 스택은 페이지 폴트로 인해 즉시 접근 가능한 상태로 만들어져야 하는 상황이다:

  1. user code에서 push 등을 통해 접근 시 페이지 폴트 발생
  2. vm_try_handle_fault()에서 스택 영역이면 vm_stack_growth() 호출
  3. 그 직후 다시 해당 주소에 접근함 → 즉시 접근 가능해야 오류 안 남

vm_try_handle_fault 수정

  • vm/vm.c
/* Return true on success 
 * 페이지 폴트 핸들러 - 페이지 폴트 발생시 제어권을 전달 받는다.
 * 물리 프레임이 존재하지 않아서 발생한 예외는 not_present 가 true다
 * 그 경우 물리 프레임 할당을 요청하는 vm_do_claim_page를 호출한다.
 * 반대로 not_present 가 false인 경우는 물리 프레임이 할당되어 있지만 폴트가 발생한 것이다.
 * read-only page에 write를 한경우 등 이 때에는 예외 처리를 하면 된다.
 * 그렇다고 해서 not_present가 true인 경우에서 read-only page에 요청을 할 수 있으니 이에
 * 대한 예외를 처리하라
 */
bool
vm_try_handle_fault (struct intr_frame *f UNUSED, void *addr UNUSED,
		bool user UNUSED, bool write UNUSED, bool not_present UNUSED) 
{
	struct supplemental_page_table *spt UNUSED = &thread_current ()->spt;
	struct page *page = NULL;
	/* TODO: Validate the fault */
	/* TODO: Your code goes here */

	// 1. 주소 유효성 검사
	// addr 주소 유효성 검사
	if (addr == NULL || is_kernel_vaddr(addr))
		return false;

	void *rsp = user ? f->rsp : thread_current()->rsp;
    if (not_present && USER_STACK - (1 << 20) <= rsp - 8 && rsp - 8 <= addr && addr <= USER_STACK) {
        vm_stack_growth(addr);
		return true;
    }

	page = spt_find_page(spt, addr);
    if (page == NULL)
        return false;

	if (write == true && !page->writable)
		return false;

   	if (write && page->frame != NULL && page->frame->ref_count > 1)
    	return vm_handle_wp(page);


	ASSERT(page->operations != NULL && page->operations->swap_in != NULL);

  	return vm_do_claim_page(page);
}
if (write && page->frame != NULL && page->frame->ref_count > 1)
    return vm_handle_wp(page);

COW 구현에서 가장 중요한 개념:

여러 프로세스가 같은 물리 페이지를 공유하다가, 그 중 하나가 쓰기 요청(write)을 하면 새 프레임을 할당하여 복사하고 독립적으로 사용하게 해야 함.

  • 접근이 write인지 확인
  • 해당 페이지가 실제로 이미 프레임을 갖고 있는지 확인
  • 그 프레임이 여러 프로세스에 공유되고 있는 중(ref_count > 1)인지 확인
  • 이 경우 vm_handle_wp()를 호출하여 COW 처리 수행

vm_handle_wp 구현

  • vm/vm.c
/* Handle the fault on write_protected page */
static bool
vm_handle_wp (struct page *page UNUSED) 
{
	lock_acquire(&frame_lock);
	struct frame *src_frame = page->frame;

    // 공유 중인 경우 복사
    if (src_frame->ref_count > 1) {
        // 새로운 프레임 확보
        struct frame *new_frame = vm_get_frame();
        memcpy(new_frame->kva, src_frame->kva, PGSIZE);

        // 참조 카운터 감소
        src_frame->ref_count--;

        // 매핑 교체
        new_frame->page = page;
        page->frame = new_frame;

		lock_release(&frame_lock);

        pml4_set_page(thread_current()->pml4, page->va, new_frame->kva, true);
    } else {
		lock_release(&frame_lock);
        // 이 페이지만 쓰는 경우라면 그냥 writable 다시 설정
        pml4_set_page(thread_current()->pml4, page->va, page->frame->kva, true);
    }

    return true;
}
if (src_frame->ref_count > 1) {
	struct frame *new_frame = vm_get_frame();
	memcpy(new_frame->kva, src_frame->kva, PGSIZE);
  • 공유된 경우 (ref_count > 1) 인 경우
  • 새로운 물리 프레임을 PAL_USER에서 할당하고,
  • 기존 프레임의 내용을 그대로 복사
src_frame->ref_count--;
  • 공유했던 프레임의 참조 수 1 감소 (이 프로세스가 더는 공유하지 않음)
  • 페이지가 새 프레임을 가리키도록 변경
pml4_set_page(thread_current()->pml4, page->va, new_frame->kva, true);
  • 이 프로세스의 가상 주소 page->va → 새로운 물리 프레임 new_frame->kva로 다시 매핑
  • 이제 이 프로세스는 독립적인 쓰기 가능한 페이지를 가짐
else {
    pml4_set_page(thread_current()->pml4, page->va, page->frame->kva, true);
}
  • 공유되지 않은 경우 (ref_count == 1)
  • 이 프로세스만 해당 프레임을 사용 중이면, 별도 복사 없이 쓰기 권한만 다시 설정
  • 이 과정은 보호된 페이지였지만 단독 사용자였던 경우에 해당

vm_do_claim_page 수정

  • vm/vm.c
static bool
vm_do_claim_page (struct page *page) 
{
	void *temp = page->operations->swap_in;
 	struct frame *frame = vm_get_frame();

	/* TODO: vm_get_frame이 실패하면 swap_out */
	
	/* Set links */
	frame->page = page;
	page->frame = frame;

	if (!swap_in(page, frame->kva))
        return false;

	/* TODO: Insert page table entry to map page's VA to frame's PA. */
	/* 페이지의 VA와 프레임의 KVA를 페이지 테이블에 매핑 */
    if (!pml4_set_page(thread_current()->pml4, page->va, frame->kva, page->writable))
      return false;

    return true;
}

supplemental_page_table_copy 구현

  • vm/vm.c
bool vm_copy_claim_page(void *va, struct page *parent, struct supplemental_page_table *parent_spt)
{
	struct page *page = spt_find_page(&thread_current()->spt, va);
	if (page == NULL)
		return false;

	void *temp = page->operations->swap_in;
	struct frame *frame = parent->frame;
	frame->ref_count++;

	if (frame->ref_count == 1)
		return true;

	/* Set links */
	page->frame = frame;

	/* TODO: Insert page table entry to map page's VA to frame's PA. */
	if (!pml4_set_page(thread_current()->pml4, page->va, frame->kva, false))
		return false;

	return swap_in(page, frame->kva);
}


/* 25.06.01 고재웅 작성 */
/* 25.06.03 고재웅 수정 */
/* Copy supplemental page table from src to dst */
bool
supplemental_page_table_copy (struct supplemental_page_table *dst UNUSED,
		struct supplemental_page_table *src UNUSED) 
{
	struct hash_iterator i;
	hash_first(&i, &src->pages);
	while (hash_next(&i))
	{
		// src_page 정보
		struct page *src_page = hash_entry(hash_cur(&i), struct page, hash_elem);
		enum vm_type type = src_page->operations->type;
		void *upage = src_page->va;
		bool writable = src_page->writable;

		/* 1) type이 uninit이면 */
		if (type == VM_UNINIT)
		{ // uninit page 생성 & 초기화
			vm_initializer *init = src_page->uninit.init;
			void *aux = src_page->uninit.aux;
			if (!vm_alloc_page_with_initializer(VM_ANON, upage, writable, init, aux))
                return false;
			continue;
		}
		if (type == VM_FILE){
			struct lazy_load_arg *file_aux = malloc(sizeof(struct lazy_load_arg));
			file_aux->file = src_page->file.file;
			file_aux->ofs = src_page->file.ofs;
			file_aux->read_bytes = src_page->file.read_bytes;
			file_aux->zero_bytes = src_page->file.zero_bytes;
			
			if (!vm_alloc_page_with_initializer(type, upage, writable, lazy_load_segment, file_aux))
				return false;
			if (!vm_copy_claim_page(upage, src_page, dst))
            	return false;
			continue;
		}
		if (VM_TYPE(type) == VM_ANON) {
			// 페이지 구조 생성
			if (!vm_alloc_page(type, upage, writable)) 
				return false;
			if (!vm_copy_claim_page(upage, src_page, dst))
         		return false;

			struct page *dst_page = spt_find_page(dst, upage);
      		// memcpy(dst_page->frame->kva, src_page->frame->kva, PGSIZE);
		}
	}
	return true;
}

vm_copy_claim_page는 COW 상황에서 페이지를 복사하지 않고 부모의 프레임을 자식에게 공유시키는 기능을 담당한다.

struct frame *frame = parent->frame;
frame->ref_count++;
  • 부모가 사용하는 프레임을 자식도 공유 → ref_count를 증가시켜 추후 관리
page->frame = frame;
  • 자식 프로세스의 페이지가 공유된 프레임을 가리키게 설정자식 프로세스의 페이지 테이블에 read-only로 매핑
    → 쓰기 접근 시 page fault → COW 발생 → vm_handle_wp() 호출
return swap_in(page, frame->kva);
  • lazy page인 경우 실제 데이터를 프레임에 로딩

supplemental_page_table_copy 함수는 부모 프로세스의 SPT를 자식에게 복사하는 용도이다.

if (!vm_alloc_page(type, upage, writable)) return false;
if (!vm_copy_claim_page(upage, src_page, dst)) return false;
  • 부모의 프레임을 공유
  • ref_count++ 하고 read-only로 매핑 → page fault 시 vm_handle_wp()에서 복사
if (!vm_copy_claim_page(upage, src_page, dst)) return false;
  • 역시 ref_count++ 하고 read-only 매핑
  • page fault 시 복사 수행 → COW 처리 방식 통일

file_backed_swap_in 수정

  • vm/file.c
/* Swap in the page by read contents from the file. */
static bool
file_backed_swap_in (struct page *page, void *kva) {
	struct file_page *file_page UNUSED = &page->file;
	file_read_at (file_page->file, kva, file_page->read_bytes, file_page->ofs);
	memset(kva + file_page->read_bytes, 0, page->file.zero_bytes);
	return true;
}
memset(kva + file_page->read_bytes, 0, page->file.zero_bytes);
  • 파일에서 읽어 들인 나머지 페이지 공간을 0으로 초기화하기 위해 추가

file_backed_swap_out 수정

  • vm/file.c
static bool
file_backed_swap_out (struct page *page) {
	struct file_page *file_page UNUSED = &page->file;
	if (pml4_is_dirty(thread_current()->pml4, page->va) && page->writable) {
		lock_acquire(&filesys_lock);
		file_write_at(file_page->file, page->frame->kva, file_page->read_bytes, file_page->ofs);
		lock_release(&filesys_lock);
		pml4_set_dirty(thread_current()->pml4, page->va, false);
	}
	page->frame->page = NULL;
	page->frame = NULL;
	pml4_clear_page(thread_current()->pml4, page->va);

	return true;
}
  • lock을 추가해 파일의 접근에 있는 경쟁상태를 제어한다.

file_backed_destroy 수정

  • vm/file.c
/* 25.06.03 고재웅 작성 */
/* 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;
	// TODO: 해당 페이지가 dirty 상태인지 확인
    // - pml4_is_dirty() 또는 page->frame->is_dirty 사용
	if (pml4_is_dirty(thread_current()->pml4, page->va) && page->writable){
		// TODO: dirty라면, 파일에 해당 내용을 file_write_at()으로 저장
		// - page->va, aux->file, aux->offset 등에서 정보 추출
		// - writable 여부도 확인
		lock_acquire(&filesys_lock);
		file_write_at(file_page->file, page->frame->kva, file_page->read_bytes, file_page->ofs);
		lock_release(&filesys_lock);
		pml4_set_dirty(thread_current()->pml4, page->va, 0);
	}
	// hash_delete(&thread_current()->spt.pages, &page->hash_elem);
	if (page->frame != NULL) {
		struct frame *f = page->frame;

		if (--f->ref_count == 0) {
			list_remove(&f->elem);
			palloc_free_page(f->kva);
			free(f);
		}
		page->frame = NULL;
	}

	pml4_clear_page(thread_current()->pml4, page->va);
}
if (page->frame != NULL) {
	struct frame *f = page->frame;

	if (--f->ref_count == 0) {
		list_remove(&f->elem);
		palloc_free_page(f->kva);
		free(f);
	}
	page->frame = NULL;
}
  • 프레임을 해제하기 전에 ref_count를 감소시키고,
  • 해당 프레임을 참조 중인 프로세스가 더 이상 없을 경우 (ref_count == 0)만 실제 물리 프레임을 해제한다.

anon_swap_in 수정

  • vm/anon.c
/* Swap in the page by read contents from the swap disk. */
static bool
anon_swap_in (struct page *page, void *kva) {
	struct anon_page *anon_page = &page->anon;
	
	size_t swap_idx = anon_page->swap_idx;
	if (swap_idx == BITMAP_ERROR) {
		PANIC("swap_in: swap_idx == BITMAP_ERROR. Not swapped out.");
		return false;
	}

	if (!bitmap_test(swap_table, swap_idx)) {
		PANIC("swap_in: bitmap_test failed. Invalid swap_idx.");
		return false;
	}

	for (int i = 0; i < 8; i++) {
		disk_read(swap_disk, swap_idx * 8 + i, kva + i * DISK_SECTOR_SIZE);
	}


	lock_acquire(&bitmap_lock);
	bitmap_set(swap_table, swap_idx, false);
	lock_release(&bitmap_lock);

	return true;
}
  • page->frame->kva = kva; 제거

anon_destroy 수정

  • vm/anon.c
/* Destroy the anonymous page. PAGE will be freed by the caller. */
static void
anon_destroy (struct page *page) {
	struct anon_page *anon_page = &page->anon;
	
	// 스왑 테이블에서 스왑 인덱스 해제
	if (anon_page->swap_idx != BITMAP_ERROR) {
		lock_acquire(&bitmap_lock);
		bitmap_reset(swap_table, anon_page->swap_idx);
		lock_release(&bitmap_lock);
	}

	// 프레임이 존재하면 프레임을 리스트에서 제거하고 해제
	if (page->frame != NULL) {
		struct frame *f = page->frame;

		if (--f->ref_count == 0) {
			list_remove(&f->elem);
			palloc_free_page(f->kva);
			free(f);
		}
		page->frame = NULL;
	}

	pml4_clear_page(thread_current()->pml4, page->va);

}
  • file_backed_destroy와 같이 설정한다.

check_address 수정 && check_valid_buffer 추가

  • userprog/syscall.c
struct page *check_address(void *addr) {
    struct thread *curr = thread_current();

     if (addr == NULL || !is_user_vaddr (addr))
        exit (-1);

    /* Return the page if it exists. */
    return spt_find_page (&thread_current ()->spt, addr);
}

void check_valid_buffer(void *buffer, size_t size, bool writable) {
    for (size_t i = 0; i < size; i++) {
        void *addr = (char *) buffer + i;
        struct page *page = spt_find_page (&thread_current ()->spt, addr);

        if (!is_user_vaddr (addr))
            exit (-1);

        if (writable && page && !page->writable)
            exit (-1);
    }
}

사용자 프로그램이 넘겨주는 포인터 인자의 유효성 검사를 수행하기 위해서 위 코드를 추가한다. 이 검사는 매우 중요하며, 운영체제(Pintos)가 사용자와 커널 공간을 안전하게 분리하여 커널의 안정성과 보안을 보장하기 위한 핵심 절차다.

check_address 수정

  • 사용자 주소 공간 내에 있는 단일 주소 addr이 유효한지 검사하고,
    supplemental page table(SPT)에서 해당 주소에 매핑된 page가 있는지 확인함.
  • NULL 여부
  • is_user_vaddr() → 커널 주소 접근 방지
  • spt_find_page() → 현재 프로세스의 SPT에서 이 주소에 대응되는 페이지가 있는지 확인

check_valid_buffer 구현

  • 유저가 넘겨주는 버퍼 영역(buffer부터 buffer + size)이 전부 유효한지 검사
  • read(), write(), mmap() 같은 시스템 콜에서 사용됨

전체 주소 범위에 대해 한 바이트씩 순회하며:

  • 유저 주소 공간 내에 있는지 (is_user_vaddr())
  • SPT에 매핑된 페이지가 존재하는지 (spt_find_page())
  • writable 요구 시 해당 페이지가 실제 쓰기 가능한지 확인
int write(int fd, const void *buffer, unsigned size) {
	check_valid_buffer(buffer, size, false);
int read(int fd, void *buffer, unsigned size) {
	check_valid_buffer(buffer, size, true);

구현한 check_valid_buffer 코드는 write와 read에 기존 check_address 대신하여 추가한다.


pass tests/vm/cow/cow-simple
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
pass tests/vm/swap-file
pass tests/vm/swap-anon
pass 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
pass tests/vm/cow/cow-simple
All 141 tests passed.

이렇게 해서 pintos VM 프로젝트 ALL PASS 를 달성했다.