가상 메모리 페이지(Virtual Page)와 물리 메모리 프레임 (Physical Frame)를 효과적으로 관리해야한다.
→ 가상 / 물리 메모리 영역을 누가(프로세스?)가 사용했고, 어떤 목적(?)으로 사용했는지 기억해야한다.
기존 page table에서 다루지 않는 정보들(누가, 어떤 목적)을 위한 부수적인 테이블이 필요하다. 그래서 supplement page table을 다루고, 이후 물리 메모리 Page Frame을 다루어야한다.
페이지는 크게 세가지 종류로 나누어 다룬다.(VM_UINIT, VM_ANON, VM_FILE) 그리고 하나의 페이지는 swap_in, swap_out, destroy를 호출한다. 세가지 함수의 동작이 페이지의 종류에 따라 다르기 때문에 타입별로 호출하는 함수를 swich-case함수를 통해 불러야한다.
c에서 사용하는 Struct의 경우 멤버 변수, 즉 특정 값만 가질 수 있기 때문에 함수의 포인터를 저장하여 타입별로 포인터가 가리키는 함수를 다르게 하여 타입별 실행하는 함수들을 다르게 적용할 수 있다.
struct page {
const struct page_operations *operations;
void *va; /* Address in terms of user space */
struct frame *frame; /* Back reference for frame */ // 프레임 역참조
/* Your implementation */
/* Per-type data are binded into the union.
* Each function automatically detects the current union */
union {
struct uninit_page uninit;
struct anon_page anon;
struct file_page file;
#ifdef EFILESYS
struct page_cache page_cache;
#endif
};
};
세가지 타입에 대해서 페이지 초기화 과정이 모두 다르다.
이 부분 이후 Anonymous Page, Memory Mapped Flie 파트에서 순차적으로 다룬다.
현재 가상 메모리와 물리 메모리를 매핑하는 pml4라는 페이지 테이블을 가지고 있지만, Page Fault 처리를 위해 추가적인 정보를 담고 있는 supplemental table을 구현해야한다.
/* Initialize new supplemental page table */
void supplemental_page_table_init (struct supplemental_page_table *spt UNUSED) {
hash_init(&spt -> vm, supplement_hash_func, supplement_less_func, NULL);
}
uint64_t supplement_hash_func ( const struct hash_elem *e, void *aux) {
/* `hash_entry()로 element에 대한 vm_entry 구조체 검색 */
struct page *p = hash_entry(e, struct page, elem);
/* hash_int()를 이용해서 vm_entry의 멤버 vaddr에 대한 해시값을 구하고 반환 */
return hash_bytes(&p -> va, sizeof p->va);
}
bool supplement_less_func ( const struct hash_elem *a, const struct hash_elem *b) {
/* hash_entry()로 각각의 element에 대한 vm_entry 구조체를 얻은 후 vaddr 비교(b가 크다면 True, a가 크다면 False) */
struct page *p_a = hash_entry(a, struct page, elem);
struct page *p_b = hash_entry(b, struct page, elem);
return p_a -> va < p_b -> va;
}
/* Find VA from spt and return page. On error, return NULL. */
struct page * spt_find_page (struct supplemental_page_table *spt UNUSED, void *va UNUSED) {
struct page *page = (struct page *)malloc(sizeof(struct page));
page -> va = pg_round_down(va);
struct hash_elem *e = hash_find(&spt -> vm, &page -> elem);
return e != NULL ? hash_entry(e, struct page, elem) : NULL;
}
/* Insert PAGE into spt with validation. */
bool spt_insert_page (struct supplemental_page_table *spt UNUSED,
struct page *page UNUSED) {
return hash_insert(&spt -> vm, &page -> elem) == NULL ? true : false; // hase_insert의 결과 값이 NULL인 경우 true
}
그리고 이후 해시 테이블을 사용할 때 필요한 두 함수도 일부 수정을 해주었다.
현재 PintOS에서 사용하는 페이지는 페이지가 할당 되었을 때, 할당된 메모리에 대한 정보(메타 정보: 페이지의 타입, 위치, 권한 등)을 포함하지 않고 있다. include/vm/vm.h
에 있는 struct frame
를 활용하여 물리 메모리 프레임을 관리하고, 이후 각 페이지들이 프레임과 매핑될 수 있는 함수들을 만들어야한다.
static struct frame *vm_get_frame (void)
→ 메모리 풀에서 새로운 물리 메모리로 쓸 페이지를 가져온다./* palloc() and get frame. If there is no available page, evict the page
* and return it. This always return valid address. That is, if the user pool
* memory is full, this function evicts the frame to get the available memory
* space.*/
static struct frame *vm_get_frame (void) {
struct frame *frame = NULL;
/* 메모리 풀은 유저 풀과 커널 풀로 반씩 공간을 차지하고 있다.
* 무분별하게 페이지를 할당받을 경우 커널 풀의 페이지를 할당 받을 수 도 있다.
* 이런 경우, Page Fault가 발생했을 때 커널 풀의 페이지가 swap out 되는 경우가 발생할 수 있다.
* 이를 방지하기 위해서 palloc_get_page의 인자로 PAL_USER를 받아서
* 사용자 풀에 한하여 페이지를 할당 받을 수 있도록 한다.
*/
void *kernel_page = palloc_get_page(PAL_USER); // 사용자 풀에서 페이지를 얻어온다.
if (kernel_page == NULL) {
PANIC("TODO:");
}
struct frame *f = malloc(sizeof(struct frame)); // 프레임 정보를 저장할 메모리 공간을 동적 할당한다.
if (f == NULL) {
palloc_free_page(kernel_page);
PANIC("TODO:");
}
// frame관련 기본 정보를 설정
frame -> kva = kernel_page;
frame -> page = NULL;
ASSERT (frame != NULL);
ASSERT (frame->page == NULL);
return frame;
}
static bool vm_do_claim_page (struct page *page)
→ 인자로 주어진 page에 물리 메모리 프레임을 매핑/*
* 구체적으로, vm_do_claim_page 함수는 다음과 같은 작업을 수행합니다:
* 1. 물리 프레임 할당: vm_get_frame 함수를 호출하여 물리 프레임을 할당받습니다.
* 2. 페이지와 프레임 연결: 페이지 구조체와 할당받은 프레임을 연결합니다.
* 3. 페이지 테이블 설정: 가상 주소와 물리 주소의 매핑 정보를 페이지 테이블에 추가합니다.
*/
static bool vm_do_claim_page (struct page *page) {
/* vm_get_frame 함수를 호출함으로써 프레임 하나를 얻습니다. */
struct frame *frame = vm_get_frame ();
/* page와 frame을 연결한다. */
frame->page = page;
page->frame = frame;
struct thread *t = thread_current();
/* MMU를 세팅해야하는데, 이는 가상 주소와 물리 주소를 매핑한 정보를 페이지 테이블에 추가해야 한다는 것을 의미합니다. */
/* 해당 주소에 페이지를 매핑합니다. */
pml4_set_page(t->pml4, page -> va, frame -> kva, page -> writable);
return swap_in (page, frame->kva);
}
bool vm_claim_page (void *va UNUSED)
→ 인자로 받은 va를 페이지에 할당하고, 해당 페이지를 프레임에 매핑(vm_do_claim_page)한다./* Claim the page that allocate on VA. */
bool vm_claim_page (void *va UNUSED) {
struct page *page = spt_find_page(&thread_current() -> spt, va);
if (page == NULL)
return false;
return vm_do_claim_page (page); // 해당 페이지에 프레임 할당
}
디스크에 백업 파일이 없는 페이지, 이름이 없는 페이지를 의미하는 Anonymouse Page를 구현해야한다. Anonymous 페이지는 실행 가능한 파일에서 스택, 힙 영역에서 사용된다.
페이지는 Anonymous Page, File-Backed Page로 나뉘는데, 파일에 의해 매핑된 페이지를 File-Backed File이라고 하며, 매핑되지 않고 컨널로 부터 임의로 할당된 페이지를 Anonymous Page라고 한다.
디스크에 있는 파일이라고 코드와 데이터는 File-Backed Page로 로드 되지만 실행될 때, Stack과 Heap에는 메모리로 할당되게 된다.
지연 로딩은 필요 시점까지 메모리에 로딩을 지연시키는 것을 의미한다. 지연 로딩을 사용하는 경우 페이지는 페이지의 구조체는 존재하지만 매핑된 프레임이 아직 존재하지 않고, 페이지의 콘텐츠도 로드되지 않은 상태를 의미한다. Page Fault가 발생해 페이지의 콘텐츠가 필요할 때 비로소 로드된다.
커널에서 페이지 요청을 받으면, vm_alloc_page_with_initializer
가 호출된다. 인자로 전달받는 타입에 따라 다르게 초기화 함수와 초기화 함수 실행에 필요한 인자를 세팅하여 페이지를 초기화한다. 이후 유저 프로그램으로 제어권을 넘긴다.
이후 로드되지 않은 페이지에 접근하게 되는 경우, Page Fault가 발생하고 이 Page Fault를 처리하는 과정에서 uninit_initialize
를 호출하고, 이때 앞서 설정해두었던 초기화 함수를 호출하여 페이지에 컨텐츠를 채우게 된다.
페이지는 초기에 모두 VM_UNINIT
타입으로 만들어진다. 코드의 uninit_page
구조체로 초기화 되지 않은 페이지를 위한 이후 초기화에 필요한 정보등를 가지고 있다.
// include/vm/uninit.c
/* Uninitlialized page. The type for implementing the
* "Lazy loading". */
struct uninit_page {
vm_initializer *init;
enum vm_type type;
void *aux;
bool (*page_initializer) (struct page *, enum vm_type, void *kva); /* Initiate the struct page and maps the pa to the va */
};
Page Fault가 일어났을때, 일단 vm_try_handle_fault
를 실행하여 현재 page fault의 상태를 확인한다.
Page Fault 유형
- 소프트웨어 Page Fault - 프로그램이 실행될 때 요청한 페이지가 현재 메모리에 없는 경우 발생. 이 경우 운영체제가 페이지를 디스크에서 메모리로 가져옴. (gitbook 기준 bogus fault)
- 하드웨어 Page Fault - 메모리 자체의 문제로 발생해. 예를 들어, 메모리 모듈의 손상이나 잘못된 연결로 인한 문제 등이 여기에 해당된다.
bogus fault인 경우, 앞서 lazy loading을 기다리고 있던 페이지라고 생각하여 페이지의 컨텐츠를 로드해서 유저 프로그램에 반환한다.
bool vm_alloc_page_with_initializer (enum vm_type type, void *va, bool writable, vm_initializer *init, void *aux);
/* Create the pending page object with initializer. If you want to create a
* page, do not create it directly and make it through this function or
* `vm_alloc_page`. */
bool vm_alloc_page_with_initializer (enum vm_type type, void *upage, bool writable,
vm_initializer *init, void *aux) {
ASSERT (VM_TYPE(type) != VM_UNINIT)
struct supplemental_page_table *spt = &thread_current ()->spt;
/* 메모리 주소에 이미 페이지가 있는지 확인 */
if (spt_find_page (spt, upage) != NULL) {
return false;
}
/* 페이지 구조체 생성 및 초기화 */
struct page *new_page = (struct page *)malloc(sizeof(struct page));
if (new_page == NULL)
return false;
switch (VM_TYPE(type)) {
case VM_ANON:
uninit_new(new_page, upage, init, type, aux, anon_initializer);
break;
case VM_FILE:
uninit_new(new_page, upage, init, type, aux, file_backed_initializer);
break;
}
// uninit_new 내부에 writable 관련 항목이 없기 때문에 호출 후에 수정
new_page -> writable = writable;
/* supplement page table에 삽입 */
if(!spt_insert_page(spt, new_page)) {
free(new_page);
return false;
}
return true;
err:
return false;
}
void
uninit_new (struct page *page, void *va, vm_initializer *init,
enum vm_type type, void *aux,
bool (*initializer)(struct page *, enum vm_type, void *)) {
ASSERT (page != NULL);
*page = (struct page) {
.operations = &uninit_ops,
.va = va,
.frame = NULL, /* no frame for now */
.uninit = (struct uninit_page) {
.init = init,
.type = type,
.aux = aux,
.page_initializer = initializer,
}
};
}
→ uninit_new
함수를 통해 페이지 초기화 데이터/함수를 가지고 있는 uninit page
를 만든다.
static bool uninit_initialize (struct page *page, void *kva);
/* Initalize the page on first fault */
static bool uninit_initialize (struct page *page, void *kva) {
struct uninit_page *uninit = &page->uninit;
/* Fetch first, page_initialize may overwrite the values */
vm_initializer *init = uninit->init;
void *aux = uninit->aux;
/* TODO: You may need to fix this function. */
return uninit->page_initializer (page, uninit->type, kva) &&
(init ? init (page, aux) : true);
}
실행파일의 세그먼트를 로드하는 부분의 구현이 필요하다. 이 함수들은 Lazy Loading에서 사용되고, page fault가 발생했을 때 실행된다. 로드 단계에서 컨텐츠를 페이지 단위로 로드하면서 앞서 만들었던 vm_alloc_page_with_initializer
를 호출한다. 이때 Page Fault가 발생한다.
static bool load_segment (struct file *file, off_t ofs, uint8_t *upage, uint32_t read_bytes, uint32_t zero_bytes, bool writable);
→ 초기 파일 데이터를 페이징하는 단계에서 uninit 페이지를 적재하는 과정으로, 이후 Lazy Loading 될 때, aux 인자를 활용하여 초기화 할 수 있도록 각 페이지의 aux인자에 페이지 초기화에 사용할 데이터를 적재한다.struct aux_struct
)에 데이터를 적재vm_alloc_page_with_initializer
를 호출하여 페이지를 할당 및 초기화 → 해당 페이지는 이후 page fault가 발생했을때 lazy_load_segment
함수와 함수에 사용할 인자 aux
를 통해 페이지 컨텐츠를 Lazy Loadingstatic bool load_segment(struct file *file, off_t ofs, uint8_t *upage,
uint32_t read_bytes, uint32_t zero_bytes, bool writable)
{
ASSERT((read_bytes + zero_bytes) % PGSIZE == 0);
ASSERT(pg_ofs(upage) == 0);
ASSERT(ofs % PGSIZE == 0);
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; // 파일 크기와 읽을 사이즈를 비교하여 실제 읽을 파일 사이즈를 결정 (최대 PGSIZE)
size_t page_zero_bytes = PGSIZE - page_read_bytes; // 0으로 채울 나머지 부분 크기 계산
struct aux_struct *aux = (struct aux_struct *)malloc(sizeof(struct aux_struct)); // aux( lazy_load_segment를 위한 데이터 )를 저장할 공간을 동적할당.
if (aux == NULL) {
return false;
}
// aux 데이터 저장
aux -> file = file;
aux -> offset = ofs;
aux -> read_bytes = page_read_bytes;
aux -> zero_bytes = page_zero_bytes;
if (!vm_alloc_page_with_initializer(VM_ANON, upage, writable, lazy_load_segment, aux)) // 페이지를 할당하고 초기화. Page Fault발생시 lazy_load_segment가 호출
return false;
/* 다음 페이지 처리를 위해 바이트 수, 시작 주소 갱신 */
read_bytes -= page_read_bytes;
zero_bytes -= page_zero_bytes;
upage += PGSIZE;
ofs += page_read_bytes;
}
return true;
}
static bool lazy_load_segment (struct page *page, void *aux)
→ Page Fault가 발생했을 때, 이전에 uninit page를 초기화 할 때 적재했던 데이터를 바탕으로 실제 페이지 컨텐츠를 로드하는 함수static bool lazy_load_segment(struct page *page, void *aux)
{
struct aux_struct *aux_info = (struct aux_struct *)aux; // aux로 페이지 할당을 위한 정보를 가져옴
struct file *file = aux_info -> file;
off_t offset = aux_info -> offset;
size_t read_bytes = aux_info -> read_bytes;
size_t zero_bytes = aux_info -> zero_bytes;
// 커널 가상 주소
void *kva = page -> frame -> kva;
// 파일 데이터 읽기
if (file_read_at(file, kva, read_bytes, offset) != (int) read_bytes) {
palloc_free_page(page -> frame -> kva);
return false;
}
memset(kva + read_bytes, 0, zero_bytes); // 나머지 부분 0으로 초기화
return true;
}
static bool **setup_stack**(struct intr_frame *if_)
→ 사용자 프로그램이 실행될 때 사용할 스택을 초기화 하고 설정 → 프로그램 실행시 필요한 함수, 변수에 대한 정보를 미리 Process 메모리의 Stack 영역에 Lazy Loading 없이 바로 Load한다.스텍 페이지를 할당하고 스택 포인터를 설정
스택에 삽입한 페이지를 다른 페이지와 구별할 수 있도록 VM_MARKER_0
을 활용하여 표기
static bool setup_stack(struct intr_frame *if_)
{
bool success = false;
void *stack_bottom = (void *)(((uint8_t *)USER_STACK) - PGSIZE);
if (vm_alloc_page(VM_ANON | VM_MARKER_0, stack_bottom, 1)) {
success = vm_claim_page(stack_bottom); // stak bottom의 주소에 페이지를 할당하고 프레임과 매핑
if (success)
if_ -> rsp = USER_STACK; // 이후 프로세스에서 사용하기 위해 stack 주소 초기화
}
return success;
}
bool **vm_try_handle_fault** (struct intr_frame *f UNUSED, void *addr UNUSED, bool user UNUSED, bool write UNUSED, bool not_present UNUSED)
→ 페이지 폴트 원인을 파악하여, Page Fault에 해당하는 페이지를 가지고 Supplemental Page Table를 참조하여 대응하는 물리 가상 프레임을 찾고, 해당 프레임을 통해 Page의 컨텐츠를 Load하는 함수Page Fault 원인을 검사
Supplemetal Page Table 참조
Page Fault 대상 page 컨텐츠 load
페이지 테이블 갱신
/* Return true on success */
bool vm_try_handle_fault (struct intr_frame *f UNUSED, void *addr UNUSED,
bool user UNUSED, bool write UNUSED, bool not_present UNUSED) {
// addr 주소 유효성 검사
if (addr == NULL || is_kernel_vaddr(addr))
return false;
/* Page Fault 원인을 검사 */
if ( !not_present )
return false;
/* Supplemetal Page Table 참조 */
struct supplemental_page_table *spt UNUSED = &thread_current ()->spt;
/* Page Fault 대상 page 컨텐츠 load */
struct page *page = spt_find_page(spt, addr);
if (page == NULL) {
return false;
}
/* write 불가능한 페이지에 접근한 경우 */
if (write == 1 && page -> writable == 0) {
return false;
}
/* 페이지 테이블 갱신 */
return vm_do_claim_page (page);
}
위의 초기화 관련 함수를 사용한 Supplemental Page Table 관련 함수 이어서 구현
supplemental_page_table_copy
→ src의 supplemental page table을 순회하면서 dst의 supplemental page table에 항목을 복사/* 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) {
// src의 해시 테이블을 순회하기 위한 구조체 선언
struct hash_iterator i;
// hash의 첫번째 요소를 hash_iterator에 설정
hash_first (&i, &src -> vm);
// hash table 내부를 순회
while (hash_next (&i))
{
struct page *curr_page = hash_entry(hash_cur (&i), struct page, elem);
if ( curr_page == NULL )
return false;
enum vm_type type = curr_page -> operations -> type;
void *upage = curr_page -> va;
bool writable = curr_page -> writable;
// uninit 타입 페이지는 spt에 페이지만 로드 되어있을 뿐, 관련된 프레임과는 매핑되어 있지 않은 상태
// src의 spt entry의 값을 통해 새로운 페이지를 생성
if ( type == VM_UNINIT ) {
vm_alloc_page_with_initializer(VM_ANON, upage, writable, curr_page -> uninit.init, curr_page -> uninit.aux);
// uninit 페이지가 생성되었다면 다음 테이블 엔트리 처리
continue;
}
// anonymous, file_mapped 타입인 경우 uninit 상태에서 pagefault로 컨텐츠가 로드되었거나, 직접 로드된 상태의 페이지를 의미
// 해당 페이지는 lazy loading 없이 직접 페이지를 로드
if ( !vm_alloc_page_with_initializer(type, upage, writable, NULL, NULL) )
return false;
// 페이지 로드 후 프레임과 매핑 ( 매핑만 )
if ( !vm_claim_page(upage) )
return false;
// dst 해시 테이블 내부에 페이지가 들어있는지 확인
struct page *new_p = spt_find_page(dst, upage);
if (new_p == NULL || new_p->frame == NULL) {
return false;
}
// 프레임 내부에 정보를 페이지 한 개만큼 복사
memcpy(new_p -> frame -> kva, curr_page -> frame -> kva, PGSIZE);
}
return true;
}
supplemental_page_table_kill
→ supplemental page table에 의해 유지되던 모든 리소스를 해제. 각 페이지의 destroy를 호출하여 페이지를 free/* Free the resource hold by the supplemental page table */
void
supplemental_page_table_kill (struct supplemental_page_table *spt UNUSED) {
/* TODO: Destroy all the supplemental_page_table hold by thread and
* TODO: writeback all the modified contents to the storage. */
}
페이지 타입에 맞는 destroy 함수가 각각 필요하다.
static void uninit_destroy (struct page *page)
static void anon_destroy (struct page *page)
is_kernel_vaddr
로 체크하는 부분에서 뻑이감...
enum vm_type type = curr_page -> operations -> type;
...
-> 페이지 타입을 의미하는 멤버 변수를 잘못 사용. 현재 페이지의 타입을 사용해야함.
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
**FAIL** tests/vm/pt-grow-stack
pass tests/vm/pt-grow-bad
**FAIL** tests/vm/pt-big-stk-obj
pass tests/vm/pt-bad-addr
pass tests/vm/pt-bad-read
pass tests/vm/pt-write-code
**FAIL** tests/vm/pt-write-code2
**FAIL** tests/vm/pt-grow-stk-sc
pass tests/vm/page-linear
pass tests/vm/page-parallel
pass tests/vm/page-merge-seq
**FAIL** tests/vm/page-merge-par
**FAIL** tests/vm/page-merge-stk
**FAIL** tests/vm/page-merge-mm
pass tests/vm/page-shuffle
**FAIL** tests/vm/mmap-read
**FAIL** tests/vm/mmap-close
**FAIL** tests/vm/mmap-unmap
**FAIL** tests/vm/mmap-overlap
**FAIL** tests/vm/mmap-twice
**FAIL** tests/vm/mmap-write
pass tests/vm/mmap-ro
**FAIL** tests/vm/mmap-exit
**FAIL** tests/vm/mmap-shuffle
**FAIL** tests/vm/mmap-bad-fd
**FAIL** tests/vm/mmap-clean
**FAIL** tests/vm/mmap-inherit
**FAIL** tests/vm/mmap-misalign
**FAIL** tests/vm/mmap-null
**FAIL** tests/vm/mmap-over-code
**FAIL** tests/vm/mmap-over-data
**FAIL** tests/vm/mmap-over-stk
**FAIL** tests/vm/mmap-remove
pass tests/vm/mmap-zero
**FAIL** tests/vm/mmap-bad-fd2
**FAIL** tests/vm/mmap-bad-fd3
**FAIL** tests/vm/mmap-zero-len
**FAIL** tests/vm/mmap-off
**FAIL** tests/vm/mmap-bad-off
**FAIL** tests/vm/mmap-kernel
**FAIL** tests/vm/lazy-file
pass tests/vm/lazy-anon
**FAIL** tests/vm/swap-file
**FAIL** tests/vm/swap-anon
**FAIL** tests/vm/swap-iter
**FAIL** 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
**FAIL** tests/filesys/base/syn-read
pass tests/filesys/base/syn-remove
**FAIL** 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
**38** of **141** tests failed.