[pintos] Week4~5: Virtual Memory - Part.7 페이지 교체 및 구현 완료를 위한 수정

Swap In/Out을 구현했지만 페이지 교체 알고리즘을 구현해야 테스트를 통과할. 수 있다. 그리고 추가적인 조정을 통해 cow-simple 테스트를 제외한 모든 테스트를 통과할 수 있도록 구현할 예정이다.


페이지 교체 알고리즘

페이지 교체 알고리즘은 운영체제가 메모리가 부족할 때 어떤 페이지를 내보낼지 결정하는 방식이다. 즉 RAM에 올려둘 수 없는 페이지를 디스크(Swap)를 내보낼 때 어떤 것을 회생할지 선택하는 규칙이다.

페이지 교체 알고리즘이 필요한 이유

  • 운영체제는 가상 메모리를 통해 실제 메모리 보다 많은 메모리를 제공하는 것처럼 동작한다.
  • 하지만 실제 물리 메모리(RAM)는 제한되어 있어, 더 이상 올릴 수 없는 경우 기존 페이지를 내보내야(Swap Out) 한다.
  • 이때 어떤 페이지를 내보낼지 결정하는 것이 페이지 교체 알고리즘이다.
알고리즘 설명 장단점
FIFO (First-In, First-Out) 가장 오래된 페이지를 제거 단순하지만 성능 낮음
LRU (Least Recently Used) 가장 오랫동안 사용되지 않은 페이지 제거 실제 구현 어려움 (정확한 시간 추적 필요)
Clock (Second Chance) FIFO 개선형, 최근 사용 여부 비트(Accessed bit)로 판단 성능 좋고 구현 쉬움
Random 무작위 선택 예측 불가, 단순 실험용
NFU / Aging 사용 빈도에 따라 점수 매김 구현 가능, LRU 근사

나는 FIFO를 이용해 구현을 할 것이다. 이유는 단순하다는 것이다.


페이지 교체 구현

frame 테이블 선언

  • include/vm/vm.h
#endif

struct page_operations;
struct thread;
struct list frame_table;
  • 프레임을 관리하기 위한 리스트를 추가한다.
/* The representation of "frame" */
struct frame {
	void *kva;
	struct page *page;
	struct list_elem elem; 
};
  • 프레임을 리스트에 넣기 위해 list_elem을 프레임 구조체에 추가한다.

vm_init 함수 수정

  • 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);
}
  • 헤더 파일에 정의했던 리스트를 vm_init에서 초기화한다.

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;

	list_push_back(&frame_table, &frame->elem);
	return frame;
}

이전에 구현했던 vm_get_frame 함수에 물리 메모리 할당을 실패했을 때 페이지 교체를 발생하도록 수정을 해야 한다.

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;
}
  • 사용 가능한 페이지가 없을 경우, 페이지 교체 알고리즘을 통해 프레임을 회수한다.
  • vm_evict_frame() 함수는 희생 프레임을 선택하고, 스왑 아웃까지 수행하여 메모리를 확보한다.
  • evicted->page = NULL: 새롭게 재사용할 수 있도록 페이지 연결을 제거한다.
  • 반환 전에 프레임 테이블에 재등록한다 (frame_table은 모든 사용 중인 프레임을 추적하는 리스트).
  • 프레임 교체 실패 시, PANIC을 호출하여 시스템 중단.
frame->kva = kva;
frame->page = NULL;
list_push_back(&frame_table, &frame->elem);
  • frame_table에 이 프레임을 등록하여 전역 프레임 관리에 포함시킨다

vm_evict_frame 구현

  • vm/vm.c
/* 한 페이지를 교체(evict)하고 해당 프레임을 반환합니다.
 * 에러가 발생하면 NULL을 반환합니다.*/
static struct frame *
vm_evict_frame (void) 
{
	struct frame *victim = vm_get_victim ();
	/* TODO: swap out the victim and return the evicted frame. */
	if (victim == NULL)
        return NULL;

	struct page *page = victim->page;
    if (page == NULL)
        PANIC("Victim has no page");
	
	if (!swap_out(page))
        return NULL;
	
	victim->page = NULL;
    page->frame = NULL;

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

    return victim;
}

vm_evict_frame 함수는 페이지 교체 알고리즘을 통해 프레임 하나를 회수하고 해당 프레임을 재사용 가능한 상태로 반환하는 역할을 한다. 메모리가 부족할 때 vm_get_frame 이 호출하여 새로운 프레임을 확보할 수 있도록 돕는다.

struct frame *victim = vm_get_victim ();
if (victim == NULL)
    return NULL;
  • vm_get_victim() 함수는 페이지 교체 알고리즘(예: Clock 알고리즘)에 따라 회수할 프레임을 선택한다.
  • 회수할 수 있는 프레임이 없으면 NULL을 반환한다.
struct page *page = victim->page;
if (page == NULL)
    PANIC("Victim has no page");
  • 회수 대상 프레임이 실제로 연결된 페이지가 있는지 확인한다.
  • 프레임이 비어 있거나 잘못된 상태면 치명적인 오류로 간주하여 커널을 중단시킨다.
if (!swap_out(page))
    return NULL;
  • swap_out(page) 함수는 해당 페이지를 디스크로 내보낸다 (스왑 아웃).
  • 내부적으로 page->operations->swap_out(page)을 호출하여 anon 또는 file 페이지에 맞는 스왑 방식으로 처리된다.
  • 스왑 아웃이 실패하면 NULL을 반환한다.
victim->page = NULL;
page->frame = NULL;
  • 해당 페이지와 프레임 간의 연결을 끊는다.
  • 이 프레임은 이제 빈 프레임이 되며, 다른 페이지가 사용할 수 있는 상태가 된다.
pml4_clear_page(thread_current()->pml4, page->va);
  • 현재 프로세스의 페이지 테이블에서 해당 가상 주소에 대한 매핑을 제거한다.
  • 해당 주소로 접근하면 페이지 폴트가 발생하고, 그때 다시 스왑 인을 통해 복원된다.
return victim;
  • 회수 및 정리된 프레임을 반환하여 vm_get_frame() 등에서 재사용하게 한다.

vm_get_victim 구현

  • vm/vm.c
/* 교체될 struct frame을 가져옵니다. */
static struct frame *
vm_get_victim (void) 
{
	if (list_empty(&frame_table))
        return NULL;

	struct list_elem *e = list_pop_front(&frame_table);
	return list_entry(e, struct frame, elem);
}

vm_get_victim 함수는 페이지 교체 대상 프레임을 선택하여 반환하는 함수다. 현재 구현은 매우 단순한 형태고 페이지 교체 알고리즘 중 FIFO 방식으로 동작한다.

if (list_empty(&frame_table))
    return NULL;
  • frame_table은 현재 사용 중인 모든 프레임을 연결 리스트 형태로 관리하는 전역 리스트다.
  • 리스트가 비어 있다면, 교체할 수 있는 프레임이 없다는 뜻이므로 NULL을 반환한다.
struct list_elem *e = list_pop_front(&frame_table);
  • list_pop_front()는 리스트의 가장 앞에 있는 요소를 꺼낸다(pop) — 즉, 가장 먼저 들어온 프레임이다.
  • 이 방식은 FIFO 알고리즘이며, 가장 오래된 프레임을 희생 대상으로 간주한다.
return list_entry(e, struct frame, elem);
  • list_entry()는 리스트 노드(struct list_elem *)를 실제 구조체(struct frame *) 포인터로 변환한다.
  • elem은 struct frame 내부에 정의된 리스트 요소 멤버다.

여기까지 구현하면 아마 테스트를 잘 통과하지 않을 것이다. 특히 merge라고 되어있는 테스트들을 통과하지 못하고 있을 수 있다. 이 테스트들은 lock을 잘 설정해야 통과할 수 있다고 한다. 그래서 lock을 설정하거나 몇 가지 수정을 하겠다.


Lock 추가 및 세부 조정

supplemental_page_table_copy 수정

  • vm/vm.c
/* 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;
			if (!vm_alloc_page_with_initializer(type, upage, writable, NULL, file_aux))
				return false;
			struct page *file_page = spt_find_page(dst, upage);
			file_backed_initializer(file_page, type, NULL);
			pml4_set_page(thread_current()->pml4, file_page->va, src_page->frame->kva, src_page->writable);
			continue;
		}

		/* 2) type이 uninit이 아니면 */
		if (!vm_alloc_page(type, upage, writable))
			return false;	

		// vm_claim_page으로 요청해서 매핑 & 페이지 타입에 맞게 초기화
		if (!vm_claim_page(upage))
			return false;

		// 매핑된 프레임에 내용 로딩
		struct page *dst_page = spt_find_page(dst, upage);
		memcpy(dst_page->frame->kva, src_page->frame->kva, PGSIZE);
	}
	return true;
}

수정사안

if (!vm_alloc_page_with_initializer(VM_ANON, upage, writable, init, aux))
                return false;
  • 에러를 처리하기 위한 방어 코드를 추가한다.
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;
    if (!vm_alloc_page_with_initializer(type, upage, writable, NULL, file_aux))
        return false;
    struct page *file_page = spt_find_page(dst, upage);
    file_backed_initializer(file_page, type, NULL);
    pml4_set_page(thread_current()->pml4, file_page->va, src_page->frame->kva, src_page->writable);
    continue;
}

이전 코드에 있던 

file_page->frame = src_page->frame;

이 코드를 제거했다.


do_mmap 수정

  • vm/file.c
/* Do the mmap */
void *
do_mmap (void *addr, size_t length, int writable,
		struct file *file, off_t offset) {
	
    // TODO: 2. fd에 대응하는 struct file * 구하기
    // - 열린 파일 디스크립터 테이블에서 찾고, 실패 시 NULL 반환
    // - file을 reopen하여 별도 참조를 유지 (중복 닫힘 방지)
	lock_acquire(&filesys_lock);
	struct file *f = file_reopen(file);
	lock_release(&filesys_lock);
	if (file == NULL) {
		return NULL;
	}

	int total_page_count = length / PGSIZE;
	if (length % PGSIZE != 0)
		total_page_count += 1;
	void *start_addr = addr;

	/* 여는 파일이 length보다 작으면 그냥 file_length 사용
	 * 만약 5000바이트 짜리를 매핑해야 한다면 첫 페이지에 4096바이트 두번째 페이지에 904 바이트를 읽고
	 * 나머지 3192 바이트는 0으로 채워야 한다. 
	 */
	size_t read_bytes = file_length(f) < length? file_length(f) : length;
	size_t zero_bytes = PGSIZE - read_bytes % PGSIZE;		

	// TODO: 3. 파일 길이 검사 및 전체 매핑 길이 조정
    // - 파일 길이를 구하고, 파일 끝까지 매핑 가능한지 확인
    // - length가 파일 길이보다 크면 남는 부분은 0으로 채우도록 기록
	ASSERT ((read_bytes + zero_bytes) % PGSIZE == 0); 	// 전체 매핑 크기가 페이지 크기의 배수여야 함을 보장한다.
	ASSERT (pg_ofs (addr) == 0);					  	// 페이지 오프셋이 0 즉 page_aligned address 임을 보장
	ASSERT (offset % PGSIZE == 0);							// 파일 내의 오프셋 ofs도 페이지 크기의 배수여야한다.
    

    // TODO: 4. 페이지 단위로 loop를 돌며 각 가상 페이지를 uninit으로 등록
    // - vm_alloc_page_with_initializer() 사용
    // - 이 때 lazy_load_file을 initializer로 넘김
    // - struct file_info(aux)에 file, offset, read_bytes 등 저장

    // TODO: 5. 모든 페이지가 성공적으로 매핑되었으면 addr 반환
    // - 실패 시 중간에 등록한 페이지들을 모두 해제하고 NULL 반환

	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 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; // 이 페이지에서 read_bytes만큼 읽고 공간이 남아 0으로 채워야 하는 바이트 수

		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_count = total_page_count;
		
		/* Advance. */
		read_bytes -= page_read_bytes;
		zero_bytes -= page_zero_bytes;
		addr += PGSIZE;
		offset += page_read_bytes;
	}
	return start_addr;
}
lock_acquire(&filesys_lock);
struct file *f = file_reopen(file);
lock_release(&filesys_lock);
if (file == NULL) {
    return NULL;
}
  • file_reopen을 하는 기능 위아래로 락을 추가하고 에러 방어 코드를 추가했다.

do_munmap 구현

  • vm/file.c
/* Do the munmap */
void
do_munmap (void *addr) {
	struct supplemental_page_table *spt = &thread_current()->spt;
	struct page *p = spt_find_page(spt, addr);
    int count = p->mapped_page_count;
	for (int i = 0; i < count; i++){
		if (p)
			spt_remove_page(spt, p);
			// destroy(p);
		addr += PGSIZE;
		p = spt_find_page(spt, addr);
	}
}
  • 기존에 destroy를 spt_remove_page로 변경했다.
  • spt_remove_page를 호출하면 결국 file_backed_destory도 호출된다. 

load_segment 수정

  • userprog/process.c
/* Loads an ELF executable from FILE_NAME into the current thread.
 * Stores the executable's entry point into *RIP
 * and its initial stack pointer into *RSP.
 * Returns true if successful, false otherwise. */
static bool
load (const char *file_name, struct intr_frame *if_) {
	struct thread *t = thread_current ();
	struct ELF ehdr;
	struct file *file = NULL;
	off_t file_ofs;
	bool success = false;
	int i;

	/* Allocate and activate page directory. */
	t->pml4 = pml4_create ();
	if (t->pml4 == NULL)
		goto done;
	process_activate (thread_current ());
	lock_acquire(&filesys_lock);
	/* Open executable file. */
	file = filesys_open (file_name);
	if (file == NULL) {
		printf ("load: %s: open failed\n", file_name);
		goto done;
	}
	
	/* Read and verify executable header. */
	if (file_read (file, &ehdr, sizeof ehdr) != sizeof ehdr
			|| memcmp (ehdr.e_ident, "\177ELF\2\1\1", 7)
			|| ehdr.e_type != 2
			|| ehdr.e_machine != 0x3E // amd64
			|| ehdr.e_version != 1
			|| ehdr.e_phentsize != sizeof (struct Phdr)
			|| ehdr.e_phnum > 1024) {
		printf ("load: %s: error loading executable\n", file_name);
		goto done;
	}

	/* Read program headers. */
	file_ofs = ehdr.e_phoff;
	for (i = 0; i < ehdr.e_phnum; i++) {
		struct Phdr phdr;

		#ifdef WSL
				// WSL 전용 코드
				off_t phdr_ofs = ehdr.e_phoff + i * sizeof(struct Phdr);
				file_seek(file, phdr_ofs);
				if (file_read(file, &phdr, sizeof phdr) != sizeof phdr)
					goto done;
		#else
				// docker(기본) 전용 코드
				if (file_ofs < 0 || file_ofs > file_length(file))
					goto done;
				file_seek(file, file_ofs);
		#endif

		if (file_read (file, &phdr, sizeof phdr) != sizeof phdr)
			goto done;
		file_ofs += sizeof phdr;
		switch (phdr.p_type) {
			case PT_NULL:
			case PT_NOTE:
			case PT_PHDR:
			case PT_STACK:
			default:
				/* Ignore this segment. */
				break;
			case PT_DYNAMIC:
			case PT_INTERP:
			case PT_SHLIB:
				goto done;
			case PT_LOAD:
				if (validate_segment (&phdr, file)) {
					bool writable = (phdr.p_flags & PF_W) != 0;
					uint64_t file_page = phdr.p_offset & ~PGMASK;
					uint64_t mem_page = phdr.p_vaddr & ~PGMASK;
					uint64_t page_offset = phdr.p_vaddr & PGMASK;
					uint32_t read_bytes, zero_bytes;
					if (phdr.p_filesz > 0) {
						/* Normal segment.
						 * Read initial part from disk and zero the rest. */
						read_bytes = page_offset + phdr.p_filesz;
						zero_bytes = (ROUND_UP (page_offset + phdr.p_memsz, PGSIZE)
								- read_bytes);
					} else {
						/* Entirely zero.
						 * Don't read anything from disk. */
						read_bytes = 0;
						zero_bytes = ROUND_UP (page_offset + phdr.p_memsz, PGSIZE);
					}
					if (!load_segment (file, file_page, (void *) mem_page,
								read_bytes, zero_bytes, writable))
						goto done;
				}
				else
					goto done;
				break;
		}
	}
	
	t->running = file;
	file_deny_write(file); /** Project 2: Denying Writes to Executables */


	/* Set up stack. */
	if (!setup_stack (if_))
		goto done;

	/* Start address. */
	if_->rip = ehdr.e_entry;

	/* TODO: Your code goes here.
	 * TODO: Implement argument passing (see project2/argument_passing.html). */

	success = true;

done:
	lock_release(&filesys_lock);
	return success;
}
  • file을 오픈하기 전에 락을 추가하고 종료 직전에 락을 해제하도록 수정했다.

mmap 수정

  • userprog/syscall.c
void *mmap (void *addr, size_t length, int writable, int fd, off_t offset){
    // TODO: 1. 유효성 검사
    // - addr이 NULL이 아니고 page-aligned인지 확인
    if (!addr || addr != pg_round_down(addr))
		return NULL;

	if (offset != pg_round_down(offset))
		return NULL;
        
    if (!is_user_vaddr(addr) || !is_user_vaddr(addr + length))
		return NULL;
    
    if (spt_find_page(&thread_current()->spt, addr))
		return NULL;

	struct file *f = process_get_file(fd);
	if (f == NULL)
		return NULL;

    if (file_length(f) == 0 || (int)length <= 0)
		return NULL;

	return do_mmap(addr, length, writable, f, offset);
}
  • mmap 검증 조건들을 수정했다.

remove 수정

  • userprog/syscall.c
bool remove (const char *file) {
	check_address(file);
    lock_acquire(&filesys_lock);
	bool is_success = filesys_remove(file);
    lock_release(&filesys_lock);
    return is_success;
}
  • 락을 추가했다.

write 수정

  • userprog/syscall.c
int write(int fd, const void *buffer, unsigned size) {
	check_address(buffer);
    lock_acquire(&filesys_lock);

    off_t bytes = -1;

    if (fd <= 0){  // stdin에 쓰려고 할 경우 & fd 음수일 경우
        lock_release(&filesys_lock);
        return -1;
    }
    if (fd < 3) {  // 1(stdout) * 2(stderr) -> console로 출력
        putbuf(buffer, size);
        lock_release(&filesys_lock);
        return size;
    }

    struct file *file = process_get_file(fd);

    if (file == NULL){
        lock_release(&filesys_lock);
        return -1;
    }

    bytes = file_write(file, buffer, size);
    lock_release(&filesys_lock);

    return bytes;
}
  • 락을 추가했다.

read 수정

  • userprog/syscall.c
int read(int fd, void *buffer, unsigned size) {
	check_address(buffer);
    lock_acquire(&filesys_lock);
    if (fd == 0) {  // 0(stdin) -> keyboard로 직접 입력
        int i = 0;  // 쓰레기 값 return 방지
        char c;
        unsigned char *buf = buffer;

        for (; i < size; i++) {
            c = input_getc();
            *buf++ = c;
            if (c == '\0')
                break;
        }
        lock_release(&filesys_lock);
        return i;
    }
    // 그 외의 경우
    if (fd < 3)  // stdout, stderr를 읽으려고 할 경우 & fd가 음수일 경우
    {
        lock_release(&filesys_lock);
        return -1;
    }

    struct file *file = process_get_file(fd);
    off_t bytes = -1;

    if (file == NULL)  // 파일이 비어있을 경우
    {
        lock_release(&filesys_lock);
        return -1;
    }

#ifdef VM
    struct page *page = spt_find_page(&thread_current()->spt, buffer);
    if (page && !page->writable){
        lock_release(&filesys_lock);
        exit(-1);
    }
#endif
    bytes = file_read(file, buffer, size);
    lock_release(&filesys_lock);

    return bytes;

}
  • 락을 추가했다. 
  • 가상 메모리 시스템(VM)이 활성화된 상황에서 read()의 대상 주소(buffer)가 유효하고, 쓰기 가능한 주소인지 확인하기 위해서 추가했다.

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
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
FAIL tests/vm/cow/cow-simple
1 of 141 tests failed.
  • cow-simple을 제외하고 모두 통과하면 구현이 완료된 것이다.