09_pintos_VM_Anonymous_page

신치우·2022년 12월 11일

data_structure_and_Pintos

목록 보기
29/36

여기서는 anonymous page라고 불리는 디스크가 아닌 image를 구현함
anonymous mapping에는 백업파일 혹은 장치가 없음

익명 페이지는 실행가능한 파일에서 스택과 힙 영역에서 사용됨

이 구간에서는 load_segment와 lazy_load_segment 도 같이 구현이 될 것임.

lazy load란
지연 로딩은 필요 시점까지 메모리의 로딩을 지연시키는 방법입니다. 페이지가 할당되었다는 것은 대응되는 페이지 구조체는 있지만 연결된 물리 메모리 프레임은 아직 없고 페이지에 대한 실제 콘텐츠들이 아직 로드되지 않았다는 것을 의미합니다. 콘텐츠는 페이지 폴트로 인해 실제로 콘텐츠가 필요하다는 시그널을 받을 때 로드됩니다.

kernel이 page를 요청하고 page fault가 발생하고 init이 되는 순서

페이지는 초기화→(페이지 폴트 → 지연 로딩 → 스왑 인 → 스왑 아웃 → …) → 삭제 라는 생명 주기를 갖습니다.

lazy load의 장점
lazy loading은 동시에 모든 바이너리 이미지들을 로드하는 즉시 로딩(eager loading)보다 상대적으로 오버헤드를 줄일 수 있습니다.

모든 페이지들은 초기에 VM_UNINIT type의 페이지로 생성됨
page fault가 발생한다면 page fault handler는 vm_try_handler_fault에게 제어권을 넘김.
그리고 진짜 page fault인지 Bogus page fault 인지 판단을 해야함.
진짜 page fault라면 exit_handler를 통해서 바로 process를 종료하고
bogus page fault라면 page 에서 content를 load한 후 user program에게 제어권을 반환함.

kernel은 page_fault 후 lazy_load 를 하면 vm_alloc_page_with_initializer 를 호출함

  1. vm_alloc_page_with_initializer
    초기화 함수를 호출

    // 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`. 
    // Initializer를 사용하여 보류 중인 페이지 개체를 만듦.
    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) { // 만일 page가 spt안에 이미 존재하고 있는지 아닌지 확인
       // 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 *page = (struct page *) malloc (sizeof (struct page)); // page가 없기 때문에 새로 하나 열어주고
       typedef bool (*initializer) (struct page *, enum vm_type, void *); // initializer 함수를 만들어서 initializer 정보를 받아줌
    
       initializer initializer_vm = NULL; // initializer 함수에 initializer_vm 으로 명칭을 선언한 후 NULL로 선언해줌
    
       switch (VM_TYPE (type)) { // input 받은 type에 따라서 anon, file 에 따라서 초기화함
       case VM_ANON:
         initializer_vm = anon_initializer;
         break;
       case VM_FILE:
         initializer_vm = file_backed_initializer;
         break;
       default:
         PANIC ("vm initial fail");
         break;
       }
    
       uninit_new (page, upage, init, type, aux, initializer_vm);  // 그 이후 uninit_new 를 사용하여 uninit 페이지 구조를 생성함
    
       page->writable = writable; // 할당받은 page의 writable에 input 된 writable 정보를 넣어줌
    
       // TODO: Insert the page into the spt.
       return spt_insert_page (spt, page); // 새로만들어진 page를 spt 에 insert를 해줌
    
     }
    err:
     return false;
    }
  2. vm_anon_init
    anonymous page를 사용하기 위해서 anon_init을 사용해서 disk를 할당 받는다
    anon을 사용하기 위하여 전역변수를 설정해준다

    //anon.c
    /* project for 3 - start */
    struct bitmap *swap_table;
    const size_t SECTORS_PER_PAGE = PGSIZE / DISK_SECTOR_SIZE; // 256MB/512B
    /* project for 3 - end */
    //anon.c
    /* Initialize the data for anonymous pages */
    // anonymous page를 위한 data를 초기화함
    void
    vm_anon_init (void) {
    	/* TODO: Set up the swap_disk. */
    	swap_disk = disk_get(1,1); // swap disk를 받아줌
    
    	size_t swap_size = disk_size(swap_disk) / SECTORS_PER_PAGE; // 할당된 swap_disk의 사이즈를 512로 나눔
    	swap_table = bitmap_create(swap_size); // swap_size에 맞는 bitmap을 만들어서 swap_table로 반환함
    }
  3. load_segment

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

  while (read_bytes > 0 || zero_bytes > 0) { // read byte 혹은 zero byte가 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; // read byte가 PGSIZE보다 작으면 read byte를 크다면 PGSIZE를 사용 --> 둘중 작은걸 사용하고
    size_t page_zero_bytes = PGSIZE - page_read_bytes; // zero byte = PGSIZE - read byte 임 GITBOOK 에서 read byte + zero byte ==PGSIZE 라는 내용이 나옴

    /* project for 3 - start */
    struct container *container = (struct container *)malloc(sizeof(struct container)); // load에 필요한 데이터를 운반하는 container
    container->file = file; // file을 저장하는 container가 있고
    container->offset = ofs; // offset 을 저장
    container->read_byte = page_read_bytes; // read byte를 저장
    
    /* project for 3 - end */

    /* TODO: Set up aux to pass information to the lazy_load_segment. */
    // void *aux = NULL;
    if (!vm_alloc_page_with_initializer (VM_ANON, upage, writable, lazy_load_segment, container)) // vm_alloc_page_with_initializer를 통해서 할당받은 page를 초기화함
      return false;

    /* Advance. */
    read_bytes -= page_read_bytes; // page 단위로 위에서 할당을 받았기 때문에 받은만큼 계속 while이 돌면서 빼줌 -- 내가 읽은 만큼 빼줌
    zero_bytes -= page_zero_bytes; // zero 도 위와 동일
    upage += PGSIZE; // upage는 PGSIZE로 읽으니 PGSIZE로 이동함
    ofs += page_read_bytes; // 내가 읽은 만큼 ofs를 이동 --> 그 다음부터 읽는다
  }
  return true;
}

lazy_load_segment

bool
lazy_load_segment (struct page *page, void *aux) { // 실행 파일로부터 segment 가 load 됨
  /* 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 file *file = ((struct container *)aux) ->file; // 앞에서 aux에 container를 넣어서 정보를 옮겨받았으니 다시 분해해서 넣어줌
  off_t aux_offset = ((struct container *)aux)->offset;
  size_t aux_read_byte = ((struct container *)aux)->read_byte;

  size_t page_zero_byte = PGSIZE - aux_read_byte; // zero_byte 도 계산해주고

  file_seek(file, aux_offset); // file_seek를 통해서 file의 읽기를 시작할 부분으로 offset 만큼 이동 시켜줌

  if(file_read(file, page->frame->kva, aux_read_byte) != (int)aux_read_byte){ // kva에서 file을 aux_read_byte 만큼 읽은 크기가 input된 aux_read_byte의 크기랑 동일하지 않다면 --> 읽은게 이상하다
    palloc_free_page(page->frame->kva); // 해당 page free 해버림
    return false;
  }
  memset(page->frame->kva + aux_read_byte, 0, page_zero_byte); // 잘 읽어왔다면 버퍼에 read_byte를 더한 공간을 page_zero_byte 만큼 0으로 채워넣어줌

  return true;
}
  1. setup_stack
    첫 stack page는 lazy load가 될 필요가 없음. 바로 load 되면됨
//thread.h
  struct supplemental_page_table spt;
  void *stack_bottom; // setup stack에서 bottom의 정보를 가지고 있기위해서 너놓음
  void *rsp_stack; 
bool
setup_stack (struct intr_frame *if_) {
  bool success = false;
  void *stack_bottom = (void *) (((uint8_t *) USER_STACK) - PGSIZE); // stack bottom은 USER_STACK에서 PGSIZE를 빼서 bottom을 맞춤

  /* 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 */

if(vm_alloc_page(VM_ANON|VM_MARKER_0, stack_bottom, 1)){ // page를 할당을 하는데 VM_ANON 이고 VM_MARKER_0(스택 체크) 으로 비트 마스킹을 하고 위에서 얻은 stack_bottom과 writable 정보로 page를 할당받음
    success = vm_claim_page(stack_bottom); // 위에서 성공하면 해당 stack_bottom 정보로 vm_claim_page를 동작해서 page와 frame을 할당

    if(success){ // 만약 성공했다면
      if_->rsp = USER_STACK; // frame의 rsp를 USER_STACK으로 고정해주고
      thread_current()->stack_bottom = stack_bottom; // thread의 구조체인 stack_bottom에 stack_bottom을 넣어줌
    }
  }

  return success;
}

여기까지 잘 끝났다면
Project 2부분은 전부 통과가 되어야 한다.

  1. supplemental_page_table_copy
    hash 순회를 위해서 아래와 같은 구조를 사용한다 --> pintos에서 추천하는 방법
    uninit 상태가 아닌 page만을 src에서 dst로 복사한다 (전부)
struct hash_iterator i;

   hash_first (&i, h);
   while (hash_next (&i))
   {
   struct foo *f = hash_entry (hash_cur (&i), struct foo, elem);
   ...do something with f...
   }
bool
supplemental_page_table_copy (struct supplemental_page_table *dst UNUSED, struct supplemental_page_table *src UNUSED) {

  struct hash_iterator i; // hash를 순회하기위해서 사용

  hash_first (&i, &src->pages); // hash 순회를 위한 준비를 함 초기화와 비슷
  while (hash_next (&i)) { // hash를 하나씩 next로 옮기면서 순회함
    struct page *parent_page = hash_entry (hash_cur (&i), struct page, elem_hash); // input된 hash를 이용해서 page로 확장을 함
    enum vm_type type = page_get_type (parent_page); // 해당 page의 정보를 전부 꺼내서 저장함
    void *upage = parent_page->va;
    bool writable = parent_page->writable;

    vm_initializer *init = parent_page->uninit.init;

    struct container *child_aux = (struct container *) malloc (sizeof (struct container)); // 복사를 위한 child_aux를 선언해주고

    struct container *aux = (struct container *) parent_page->uninit.aux; // 부모 aux를 사용하기 위하여 container로 연결

    if (child_aux == NULL)
      return false;
    if (aux != NULL) { // 부모의 aux를 child_aux의 container에 저장해줌
      child_aux->file = aux->file;
      child_aux->offset = aux->offset;
      child_aux->read_byte = aux->read_byte;
    }
   
    if (parent_page->uninit.type & VM_MARKER_0) { // 부모의 type 에 stack 이 check 되어있다면
        setup_stack (&thread_current ()->tf); // lazy load가 아닌 바로 setup_stack으로 보내서 동작함
      } else if (parent_page->operations->type == VM_UNINIT) { // type이 초기 상태이면
        if (!vm_alloc_page_with_initializer (type, upage, writable, init, (void *) child_aux)) // vm_alloc을 이용하여 초기화하고 page를 할당받음
          return false;
      } else {
        if (!vm_alloc_page (type, upage, writable)) // 초기 상태가 아닌 ANON 혹은 FILE type 이라면 vm_alloc_page로 page를 할당 받고
          return false;
        if (!vm_claim_page (upage)) // page를 할당하고 frame도 연결함
          return false;
      }

      if (parent_page->operations->type != VM_UNINIT) { // 위 작업이 정상동작하였다면 type은 uninit이 아닐테니
        struct page *child_page = spt_find_page (dst, upage); // child_page를 dst 에서 찾고
        if (child_page == NULL)
          return false;
        memcpy (child_page->frame->kva, parent_page->frame->kva, PGSIZE); // parent page의 정보를 child page 에 복사해넣음
      }

       // if (parent_page->operations->type == VM_UNINIT) {
    //   ASSERT (child_aux != NULL);
    //   if (!vm_alloc_page_with_initializer (type, upage, writable, init, (void *) child_aux))
    //     return false;
    // } else if (parent_page->operations->type != VM_UNINIT) {
    //     vm_alloc_page (type, upage, writable);
    //     struct page *child_page = spt_find_page (&thread_current ()->spt, upage);
    //     vm_claim_page (child_page->va);
    //     // struct page *child_page = vm_claim_page(dst, upage);
    //     if (child_page == NULL)
    //       return false;
    //   memcpy (child_page->frame->kva, parent_page->frame->kva, PGSIZE);
    // }

  }

  return true;
}
  1. supplemental_page_table_kill --> process_exit를 할때 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. */

  struct hash_iterator i;
  hash_first (&i, &spt->pages);
  while (hash_next (&i)) {
    struct page *page = hash_entry (hash_cur (&i), struct page, elem_hash);

    if (page->operations->type == VM_FILE) // hash를 순회하면서 type이 VM_FILE 이라면
      do_munmap (page->va); // 내부를 전부 munmap 해서 data를 free 해버리고

    // free(page);
  }
  // hash_destroy(&spt->pages, spt_des);
  hash_clear (&spt->pages, spt_des); // clear를 통해서 hash_table을 날려버림
}
void
spt_des (struct hash_elem *e, void *aux) {
  const struct page *p = hash_entry (e, struct page, elem_hash);
  // vm_dealloc_page (p);
  free (p); // input된 e를 확장해서 page를 free 함
}
profile
https://shin8037.tistory.com/

0개의 댓글