fork‐read.c 테스트 오류 - sihyun10/pintos-lab-vm GitHub Wiki

fork-read.c 테스트 코드 Fail 해결

Acceptable output

이 테스트의 핵심은
부모가 open 한 파일을, fork로 생성된 자식이 동일한 fd로 이어서 읽을 수 있는가?이다.

  • 자식은 fork 이후에도 handle을 가지고 있고
  • 자식이 해당 fdread()를 수행하는 것
  • read()가 제대로 동작하고, 부모와 자식이 동일한 파일을 독립적으로 공유할 수 있어야 테스트가 통과된다.

supplemental_page_table_copy() 함수 수정

supplemental_page_table_copy()함수는 __do_fork()에서 호출된다.

static void
__do_fork(void *aux)
{
/* ... */
#ifdef VM
  supplemental_page_table_init(&current->spt);
  if (!supplemental_page_table_copy(&current->spt, &parent->spt))
    goto error;

supplemental_page_table_copy()함수는 부모의 spt를 자식에게 복사하려는 것이다.

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->spt_hash);
  while (hash_next(&i))
  {
    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;

    if (type == VM_UNINIT)
    {
      vm_initializer *init = src_page->uninit.init;
      void *aux_copy = src_page->uninit.aux;

      if (!vm_alloc_page_with_initializer(src_page->uninit.type, upage, writable, init, aux_copy))
        return false;
    }
    else
    {
      if (!vm_alloc_page_with_initializer(src_page->operations->type, upage, writable, NULL, NULL))
        return false;

      if (!vm_claim_page(upage))
        return false;

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

부모 프로세스의 메모리는 두 가지 타입으로 나뉘어져 있다.

페이지 타입 의미
VM_UNINIT 아직 실제 물리 메모리를 할당받지 않은 lazy loading상태의 페이지
그 외 실제로 메모리에 올라간 페이지들

따라서 복사 방식이 다르다.


1) VM_UNINIT인 경우

아직 물리 메모리를 할당받지 않은 lazy loading 상태의 페이지이다.
페이지 접근 전까지는 메모리를 할당하면 안되므로,
부모 페이지의 init, aux만 그대로 복사한다.

즉, 페이지 접근 전까지 메모리 할당을 미루기 위해 lazy loading 상태 그대로 복사한다.

if (type == VM_UNINIT) {
  vm_initializer *init = src_page->uninit.init;
  void *aux_copy = src_page->uninit.aux;

  if (!vm_alloc_page_with_initializer(src_page->uninit.type, upage, writable, init, aux_copy))
    return false;
}

2) 이미 메모리에 올라온 경우

else
{
  if (!vm_alloc_page_with_initializer(src_page->operations->type, upage, writable, NULL, NULL))
    return false;

  if (!vm_claim_page(upage))
    return false;

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

이미 부모에서 물리 메모리 할당되어있다면, 자식도 페이지를 만들고 claim 한 후에
frame 내용을 그대로 복사(memcpy)해야 한다.

왜 이렇게 해야 할까?

  • 복사해서 독립적인 메모리를 확보해야함
    • 부모와 자식이 같은 주소 공간을 갖지만, 독립적인 프레임을 가져야한다.
  • Lazy Loading 유지
    • VM_UNINIT 페이지는 복사해서 즉시 물리 메모리를 할당하지 않는다.
    • 여전히 page fault 시점에 로딩 될 수 있도록 해야한다.

주의점 - auxfree하면 안된다!!!!

| userprog/process.c 파일에서의 lazy_load_segment()함수

static bool
lazy_load_segment(struct page *page, void *aux)
{
  /* TODO: Load the segment from the file */
  /* TODO: This called when the first page fault occurs on address VA. */
  /* TODO: VA is available when calling this function. */
  struct load_info *info = (struct load_info *)aux;

  struct file *file = info->file;
  off_t ofs = info->ofs;
  size_t page_read_bytes = info->read_bytes;
  size_t page_zero_bytes = info->zero_bytes;

  // 실제 프레임의 커널 가상 주소 얻기
  uint8_t *kva = page->frame->kva;

  // 파일에서 필요한 만큼 읽기
  if (file_read_at(file, kva, page_read_bytes, ofs) != (int)page_read_bytes)
  {
    free(info);
    return false;
  }

  // 남은 영역은 0으로 채우기
  memset(kva + page_read_bytes, 0, page_zero_bytes);
  
  // free(info); ➔ 이렇게 해주면 안된다!!
  return true;
}

Q. 왜 free(info)를 해주면 안되는걸까?

아직 해당 페이지가 접근되지 않아서, lazy_load_segment() 실행 시, 정보가 필요하다.
실제로 페이지에 접근해서 lazy_load_segment()가 성공할 때까지 필요하다.

상황 : 부모, 자식이 레고를 맞추고있는 중

  • 부모 프로세스는 레고를 맞추려고 레고 설명서(aux) 를 들고 있다.
    설명서에는 어떻게 레고를 조립하고, 어떤 레고를 사용하는지 적혀있다.
  • 그런데 부모가 "어렵다. 나중에 조립하자~"하고
    레고 상자만 냅두고 간거야. (lazy loading 상태)
  • 그리고 자식 프로세스가 생겼고, 자식도 똑같이 레고를 조립하려면
    레고 설명서(aux) 를 복사해서 가져가야한다.

하지만, 부모가 레고를 조립하기 전에 설명서를 찢어서 버렸다면?

  • 자식은 레고를 조립할 때 설명서가 없으니까 어떻게 조립할지 모른다.
  • 그래서 자식이 레고를 조립하려고 시도하다가 실패(프로세스 종료)를 해버린다.

따라서 테스트가 실패했었던 것이다.


해결한 후에 테스트 결과는 33 of 141 tests 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
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
FAIL 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
FAIL 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
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
33 of 141 tests failed.
../../tests/Make.tests:28: recipe for target 'check' failed
make: *** [check] Error 1