운영체제(OS)/with PintOS

(Project3-Virtual Memory) - Anonymous Page

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

vm_alloc_page_with_initializer()를 구현하세요. 전달된 vm_type에 따라 적절한 초기화 프로그램을 가져와서 uninit_new를 호출해야 합니다.

bool vm_alloc_page_with_initializer (enum vm_type type, void *va,
        bool writable, vm_initializer *init, void *aux);

주어진 유형의 초기화되지 않은 페이지를 생성합니다. 초기화되지 않은 페이지의 swap_in 핸들러는 자동으로 유형에 따라 페이지를 초기화하고 주어진 AUX로 INIT을 호출합니다. 페이지 구조체를 획득한 후에는 해당 페이지를 프로세스의 보충 페이지 테이블에 삽입합니다. vm.h에서 정의된 VM_TYPE 매크로를 사용하는 것이 편리할 수 있습니다.
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;
 
  /* Check wheter the upage is already occupied or not. */
  if (spt_find_page(spt, upage) == NULL)
  {
    /* TODO: Create the page, fetch the initialier according to the VM type,
     * TODO: and then create "uninit" page struct by calling uninit_new. You
     * TODO: should modify the field after calling the uninit_new. */
    struct page *pg = (struct page *)malloc(sizeof(struct page)); // 페이지 구조체를 생성
    if (pg == NULL)
      goto err;

    void *va_rounded = pg_round_down(upage); // upage로 페이지의 시작 주소 추출
    switch (VM_TYPE(type))
    {
    case VM_ANON:
      uninit_new(pg, va_rounded, init, type, aux, anon_initializer); // 익명 페이지로 형태 생성
      break;
    case VM_FILE:
      uninit_new(pg, va_rounded, init, type, aux, file_backed_initializer); // 파일 기반 페이지로 형태 생성
      break;
    default:
      NOT_REACHED();
      break;
    }

 

 

페이지 폴트 핸들러는 호출 체인을 따라가며 최종적으로 swap_in을 호출할 때 uninit_initialize에 도달합니다. 해당 함수에 대한 완전한 구현을 제공하지만, 디자인에 따라 uninit_initialize를 수정해야 할 수 있습니다.

static bool uninit_initialize (struct page *page, void *kva);

첫 번째 폴트에서 페이지를 초기화합니다. 템플릿 코드는 먼저 vm_initializer 및 aux를 가져와서 해당하는 페이지 초기화 프로그램을 함수 포인터를 통해 호출합니다. 디자인에 따라 함수를 수정해야 할 수 있습니다.
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);
}

 

 

vm/anon.c의 vm_anon_init 및 anon_initializer를 필요에 따라 수정할 수 있습니다.

void vm_anon_init (void);

익명 페이지 서브시스템을 위해 초기화합니다. 이 함수에서는 익명 페이지와 관련된 모든 설정을 수행할 수 있습니다.
void vm_anon_init(void) // 추후에 수정(스왑 구현 시)
{
    /* TODO: Set up the swap_disk. */
}
bool anon_initializer (struct page *page,enum vm_type type, void *kva);

이 함수는 먼저 페이지->operations에 대한 익명 페이지 핸들러를 설정합니다. 현재 비어 있는 구조체인 anon_page에서 일부 정보를 업데이트해야 할 수 있습니다. 이 함수는 익명 페이지의 초기화 함수로 사용됩니다 (즉, VM_ANON).

bool anon_initializer(struct page *page, enum vm_type type, void *kva)
{
    /* Set up the handler */
    page->operations = &anon_ops; // 페이지 구조체의 oprations를 설정

    struct anon_page *anon_page = &page->anon; // 페이지 타입 설정
    anon_page->sec_no = SIZE_MAX;
    anon_page->thread = thread_current();

    return true;
}

 

 

userprog/process.c에 있는 load_segment 및 lazy_load_segment 함수를 구현하세요. 실행 파일로부터 세그먼트를 로드하는 부분을 구현해야 합니다. 이들 페이지는 게으르게 로드되어야 합니다. 즉, 커널이 해당 페이지에 대한 페이지 폴트를 가로채기 전까지만 로드되어야 합니다.
프로그램 로더의 핵심인 userprog/process.c의 load_segment 루프를 수정해야 합니다. 루프 주변에서는 매번 vm_alloc_page_with_initializer를 호출하여 보류 중인 페이지 객체를 생성합니다. 페이지 폴트가 발생하면 여기서 파일에서 세그먼트가 실제로 로드됩니다.

static bool load_segment (struct file *file, off_t ofs, uint8_t *upage,
        uint32_t read_bytes, uint32_t zero_bytes, bool writable);

현재 코드는 주요 루프에서 파일에서 읽을 바이트 수 및 제로로 채울 바이트 수를 계산합니다.
그런 다음, vm_alloc_page_with_initializer를 호출하여 보류 중인 객체를 생성합니다.
여러분은 vm_alloc_page_with_initializer에 제공할 aux 인자로 aux 값을 설정해야 합니다. 이진 파일 로딩에 필요한 정보를 담은 구조체를 만드는 것이 좋을 수 있습니다.

static 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);

    off_t dynamic_ofs = ofs;
    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 load_segment_aux *aux = (struct load_segment_aux *)malloc(sizeof(struct load_segment_aux));
        lock_acquire(&filesys_lock); // aux에 lazy_load_segment에 대한 정보를 입력
        aux->file = file_reopen(file);
        lock_release(&filesys_lock);
        aux->ofs = dynamic_ofs;
        aux->page_read_bytes = page_read_bytes;
        aux->page_zero_bytes = page_zero_bytes;

        if (!vm_alloc_page_with_initializer(VM_ANON, upage, writable, lazy_load_segment, (void *)aux))
        {
            file_close(aux->file);
            free(aux);
            return false;
        } // lazy_load_segment로 페이지 초기화

        /* Advance. */
        read_bytes -= page_read_bytes; // aux에 담을 정보 갱신
        zero_bytes -= page_zero_bytes;
        upage += PGSIZE;
        dynamic_ofs += PGSIZE;
    }
    return true;
}
static bool lazy_load_segment (struct page *page, void *aux);

 

lazy_load_segment 함수가 **load_segment**에서 **vm_alloc_page_with_initializer**의 네 번째 인자로 제공되는 것을 알아채셨을 것입니다. 이 함수는 실행 파일 페이지의 초기화 함수로, 페이지 폴트가 발생할 때 호출됩니다. 이 함수는 페이지 구조체와 aux를 인자로 받습니다. aux는 **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. */
    bool success = true;
    struct load_segment_aux *info = (struct load_segment_aux *)aux;
    if (file_read_at(info->file, page->va, info->page_read_bytes, info->ofs) != (off_t)info->page_read_bytes)
    {
        vm_dealloc_page(page);
        success = false; // 요청한 만큼 읽어오기 실패하면 false
    }
    else
        memset((page->va) + info->page_read_bytes, 0, info->page_zero_bytes);
        // 페이지에 파일이 로드되고 남은 부분은 0으로 세팅

    file_close(info->file); // 다 읽었으면 파일 닫기
    free(aux); // aux 할당 해제
    return success;
}

 

 

사용자 프로그램(process.c)의 setup_stack 함수를 조정하여 새로운 메모리 관리 시스템에 스택 할당을 맞출 필요가 있습니다. 첫 번째 스택 페이지는 느리게 할당될 필요가 없습니다. 명령 줄 인수로 이를 로드 시간에 할당하고 초기화할 수 있으며, 이를 느리게 구동되기를 기다릴 필요가 없습니다. 스택을 식별하기 위한 방법을 제공해야 할 수 있습니다. **vm_type**의 보조 마커(예: VM_MARKER_0)를 사용하여 페이지를 표시할 수 있습니다.

static bool setup_stack(struct intr_frame *if_)
{
    bool success = false;
    void *stack_bottom = (void *)(((uint8_t *)USER_STACK) - PGSIZE); // 늘려야하는 스택 위치 지정

    /* TODO: Map the stack on stack_bottom and claim the page immediately.
     * TODO: If success, set the rsp accordingly.
     * TODO: You should mark the page is stack. */
    /* TODO: Your code goes here */
    success = vm_alloc_page(VM_ANON, stack_bottom, true); // stack_bottom까지 페이지 할당
    if (success)
    {
        if (vm_claim_page(stack_bottom)) // 프레임 할당하고 매핑
            if_->rsp = (uintptr_t)USER_STACK; // 스택포인터 조정
    }
    return success;
}

 

마지막으로, vm_try_handle_fault 함수를 수정하여 페이지 부재로 인한 오류를 해결할 때 부가 페이지 테이블을 참고하여 해당 주소에 해당하는 페이지 구조체를 찾을 수 있도록 해야 합니다.

위의 요구 사항을 모두 구현한 후, fork를 제외한 프로젝트 2의 모든 테스트가 통과되어야 합니다.

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 (vm_do_claim_page(page)) // 프레임 할당 및 매핑 요청
     return true; // 성공

     return false; //  실패
}

 

보충 페이지 테이블(Supplemental Page Table)

 

인터페이스를 재방문하여 복사 및 정리 작업을 지원하도록 구현해야 합니다. 이러한 작업은 프로세스를 생성(구체적으로는 자식 프로세스를 생성)하거나 프로세스를 소멸시킬 때 필요합니다. 아래에 자세한 내용이 나와 있습니다. 위에서 구현한 초기화 함수 중 일부를 사용할 수도 있기 때문에 지금이 보충 페이지 테이블을 다시 살펴보는 이유입니다.

vm/vm.c 파일에 있는 **supplemental_page_table_copy**와 supplemental_page_table_kill 함수를 구현하세요.

bool supplemental_page_table_copy (struct supplemental_page_table *dst,
    struct supplemental_page_table *src);

src에서 dst로 보충 페이지 테이블을 복사합니다. 이는 자식이 부모의 실행 컨텍스트를 상속해야 할 때 사용됩니다(즉, fork()). src의 보충 페이지 테이블의 각 페이지를 반복하고 dst의 보충 페이지 테이블에 해당 항목의 정확한 사본을 만들어야 합니다. 초기화되지 않은 페이지를 할당하고 즉시 그 페이지를 차지해야 할 것입니다.
bool supplemental_page_table_copy(struct supplemental_page_table *dst, struct supplemental_page_table *src)
{
  struct hash_iterator iter;
  hash_first(&iter, &(src->spt));
  while (hash_next(&iter))
  {
    struct page *tmp = hash_entry(hash_cur(&iter), struct page, page_elem);
    struct page *cpy = NULL;

    switch (VM_TYPE(tmp->operations->type))
    {
    case VM_UNINIT:
      if (VM_TYPE(tmp->uninit.type) == VM_ANON)
      {
        struct load_segment_aux *info = (struct load_segment_aux *)malloc(sizeof(struct load_segment_aux));
        memcpy(info, tmp->uninit.aux, sizeof(struct load_segment_aux));

        info->file = file_duplicate(info->file);

        vm_alloc_page_with_initializer(tmp->uninit.type, tmp->va, tmp->writable, tmp->uninit.init, (void *)info);
      }
      break;
    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;

      struct thread *t = thread_current();
      lock_acquire(&lru_lock);
      list_push_back(&lru, &cpy_frame->lru_elem);
      lock_release(&lru_lock);

      if (pml4_set_page(t->pml4, cpy->va, cpy_frame->kva, 0) == false)
        return false;

      swap_in(cpy, cpy_frame->kva);
 
      break;
    case VM_FILE
      break;
    default:
      break;
    }
  }
  return true;
}
void supplemental_page_table_kill (struct supplemental_page_table *spt);

 

보충 페이지 테이블이 보유하고 있던 모든 리소스를 해제합니다. 이 함수는 프로세스가 종료될 때 호출됩니다(userprog/process.c의 process_exit()). 페이지 항목을 반복하고 테이블 내의 각 페이지에 대해 destroy(page)를 호출해야 합니다. 이 함수에서는 실제 페이지 테이블(pml4)과 물리적 메모리(palloc으로 할당된 메모리)에 대해 걱정할 필요가 없습니다. 이러한 요소들은 보충 페이지 테이블이 정리된 후에 호출자가 정리합니다.

void supplemental_page_table_kill(struct supplemental_page_table *spt)
{
  /* TODO: Destroy all the supplemental_page_table hold by thread */
  lock_acquire(&kill_lock);
  hash_destroy(&(spt->spt), spt_destroy_func); // spt 삭제
  lock_release(&kill_lock);

  /* TODO: writeback all the modified contents to the storage. */
}
static void spt_destroy_func(struct hash_elem *e, void *aux)
{
  const struct page *pg = hash_entry(e, struct page, page_elem);
  vm_dealloc_page(pg); // 페이지 할당 해제
}

 

페이지 클린업

 

vm/uninit.c 파일에 uninit_destroy 함수를, 그리고 vm/anon.c 파일에 anon_destroy 함수를 구현하세요. 이는 초기화되지 않은 페이지에 대한 파괴 작업을 처리하는 핸들러입니다. 초기화되지 않은 페이지가 다른 페이지 객체로 변환되더라도, 프로세스가 종료될 때 여전히 초기화되지 않은 페이지가 존재할 수 있습니다.

static void uninit_destroy (struct page *page);

 

page 구조체에 의해 보유된 리소스를 해제합니다. 페이지의 vm type을 확인하고 그에 따라 적절히 처리해야 할 것입니다.

static void
uninit_destroy(struct page *page) // 별도로 수정하지 않음.
{
    struct uninit_page *uninit UNUSED = &page->uninit;
    /* TODO: Fill this function.
     * TODO: If you don't have anything to do, just return. */
}

 

 

현재는 익명 페이지만 처리할 수 있습니다. 나중에 파일로 지원되는 페이지를 정리하려면 이 함수를 다시 살펴보게 될 것입니다.

static void anon_destroy (struct page *page);

익명 페이지에 의해 보유된 리소스를 해제합니다. 페이지 구조체를 명시적으로 해제할 필요는 없습니다. 이 작업은 호출자가 수행해야 합니다.

static void
anon_destroy(struct page *page)
{
    struct anon_page *anon_page = &page->anon;
    if (page->frame != NULL)
    {
        // printf("anon_destroy: %s\n", thread_current()->name);
        // printf("remove: %p, kva:%p\n", page->va, page->frame->kva);
        // printf("list_size: %d, list: %p\n", list_size(&lru), &lru);

        lock_acquire(&lru_lock);
        list_remove(&page->frame->lru_elem); // 프레임 리스트에서 삭제
        lock_release(&lru_lock);

        // printf("anon_destroy: list: %p\n", &lru);

        // pte write bit 1 -> free
        free(page->frame); // 프레임 구조체 해제
    }
    if (anon_page->sec_no != SIZE_MAX)
        bitmap_set_multiple(disk_bitmap, anon_page->sec_no, 8, false); // 사용 가능한 프레임에 대한  bitmap 설정
}

 

이제 프로젝트 2의 모든 테스트가 통과되어야 합니다.