운영체제(OS)/with PintOS

(Project3-Virtual Memory) - Copy-on-write (Extra)

스탠딩 2023. 12. 26. 21:24

Copy-on-write (Extra)

Pintos에서 Copy-on-Write 메커니즘을 구현하세요.

Copy-on-Write는 물리적 페이지의 동일한 인스턴스를 사용하여 더 빠른 복제 작업을 허용하는 리소스 관리 기술입니다. 한 리소스가 여러 프로세스에서 사용되면 각 프로세스는 일반적으로 충돌이 발생하지 않도록 리소스의 자체 복사본을 가져야 합니다. 그러나 리소스가 수정되지 않고 단순히 읽히는 경우에는 물리 메모리에 여러 복사본이 필요하지 않습니다.

 

예를 들어, fork를 통해 새 프로세스가 생성되면 자식은 부모의 리소스를 상속하여 데이터를 가상 주소 공간에 복제해야 합니다. 일반적으로 가상 메모리에 내용을 추가하려면 물리 페이지를 할당하고 데이터를 프레임에 쓰며 페이지 테이블에 가상->물리 매핑을 추가해야 합니다. 이러한 단계는 상당히 시간이 걸릴 수 있습니다.

 

그러나 copy-on-write 기술을 사용하면 새로운 리소스 복사본에 대해 새로운 물리 페이지를 할당하지 않습니다. 이는 기술적으로 이미 물리 메모리에 콘텐츠가 존재하기 때문입니다. 따라서 우리는 자식 프로세스의 페이지 테이블에만 가상->물리 매핑을 추가합니다. 여기서 가상 주소는 이제 자식의 메모리 공간에 있습니다. 그럼으로써 부모와 자식은 여전히 동일한 물리 페이지에서 동일한 데이터에 액세스하고 있습니다. 그러나 그들은 여전히 분리된 가상 주소 공간을 통해 격리되어 있으며, 오직 OS만이 그들이 동일한 프레임을 참조하고 있다는 것을 알고 있습니다. 프로세스 중 하나가 공유 리소스의 내용을 수정하려고 할 때만 새로운 물리 페이지에 대한 별도의 복사본이 생성됩니다. 따라서 실제 복사 작업은 첫 번째 쓰기로 연기됩니다.

즉, OS는 copy-on-write 페이지에서의 쓰기 시도를 감지할 수 있어야 합니다. 이 필요를 충족시키기 위해 OS는 "쓰기 보호" 메커니즘을 사용합니다. 이는 간단한 아이디어입니다: 쓰기 액세스에서 페이지 폴트를 발생시킵니다. 이는 메모리 관리 시스템의 지원을 통해 구현할 수 있으며, 단순히 쓰기 보호된 페이지를 완전히 쓰기 불가능하게 표시하면 됩니다.

복사본 생성은 fork에 대해서만 구현하면 됩니다. 자식 프로세스가 부모 프로세스로부터 리소스를 상속할 때, 리소스는 자식이 수정하려고 할 때까지 동일한 물리 데이터를 참조할 수 있습니다. 모든 쓰기 보호된 페이지는 퇴출 대상입니다.

우리는 복사본 생성에 대한 기본 테스트 케이스만 제공합니다. 모든 가능한 경우를 고려해야 합니다 (작은 힌트: 파일 지원 페이지의 공유를 구현해야 합니다). 이 추가 프로젝트의 평가는 숨겨진 테스트 케이스로 이루어질 것입니다.

bool supplemental_page_table_copy(struct supplemental_page_table *dst, struct supplemental_page_table *src)
{
    ... 생략
 
    case VM_ANON:
      vm_alloc_page(tmp->operations->type, tmp->va, tmp->writable);
      cpy = spt_find_page(dst, tmp->va);

      if (cpy == NULL)
        return false;

      cpy->copy_writable = tmp->writable; // 포크할 때 쓰기 가능한지 저장
      struct frame *cpy_frame = malloc(sizeof(struct frame));
      cpy->frame = cpy_frame;
      cpy_frame->page = cpy;
      cpy_frame->kva = tmp->frame->kva;
 
      // 이하 생략
}
bool vm_try_handle_fault(struct intr_frame *f, void *addr, bool user, bool write, bool not_present)
{
  struct supplemental_page_table *spt = &thread_current()->spt;
  struct page *page = NULL;
  /* TODO: Validate the fault */
  /* TODO: Your code goes here */
  if (is_kernel_vaddr(addr) && user)
    return false;

  page = spt_find_page(spt, addr);
  if (write && !not_present && page->copy_writable && page) // 이미 존재하는 쓰기 가능한 페이지에 쓰기를 요청하면
    return vm_handle_wp(page); // 처리하는 함수 호출

  if (page == NULL)
  {
    struct thread *current_thread = thread_current();
    void *stack_bottom = pg_round_down(thread_current()->user_rsp);
    if (write && (addr >= pg_round_down(thread_current()->user_rsp - PGSIZE)) && (addr < USER_STACK))
    {
      vm_stack_growth(addr);
      return true;
    }
    return false;
  }

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

  if (vm_do_claim_page(page))
    return true;

  return false;
}

 

static bool vm_handle_wp(struct page *page)
{
  void *parent_kva = page->frame->kva; // 부모의 가상 주소 저장
  page->frame->kva = palloc_get_page(PAL_USER); // 자식 페이지를 할당하고 새로운 가상 주소 반환
 
  memcpy(page->frame->kva, parent_kva, PGSIZE); // 부모의 페이지를 자식의 페이지에 복사
  pml4_set_page(thread_current()->pml4, page->va, page->frame->kva, page->copy_writable); // 페이지 테이블에 매핑

  return true;
}