1. Anonymous Page

    1. 개요

      나는 이전에 swap 디스크는 하드 디스크와 완전히 동일한 개념이라고 생각했다. 다른 강의들에서 같다고 생각해도 괜찮다는 말에 그랬던것 같다. 하지만 완전히 같다고 말하면 이해하는게 쉽지가 않다. 왜 anon page는 disk get을 하는지 왜 file-backed 페이지는 container를 이용하는지 이해가 가지 않았다. 그래서 주변 친구들에게 물어보기도 하고 자료를 더 찾아보았다.

      하드디스크는 섹터 단위로 관리한다. 한 바이트씩 관리하지 않고 512 바이트를 1 섹터로 정의하여 섹터 단위로 자료를 저장하고 불러온다. 하드디스크나 낸드가 자료를 읽어오고 수정하는 단위와 관련이 있는것 같은데 자세하게는 모르겠어서 추가로 찾아봐야 한다. 어쨌거나 이와같은 방식이 핀토스의 disk.c에 구현되어있다.

      그리고 하드디스크의 섹터는 용도가 크게 두가지이다. 물론 프로젝트 4에서 용도가 더 있는것 같지만 현재 내가 이해하기로는 2가지이다. 파일을 저장하는 용도와 메인 메모리를 보조하는 swap 용도이다. anon 페이지의 경우 메모리에 자리가 없으면 swap 용도의 디스크에 현재 데이터를 저장한다. file-backed 페이지는 파일의 내용을 매핑하는 것이므로 복사해온 파일에 다시 저장하면 된다. 이러한 차이점을 알지 못할 때는 왜 disk_get 함수를 사용하는지, 왜 container 구조체를 사용하는지 헷갈렸다. 보통 swap 용도의 섹터를 swap space라 한다. 나는 물리적으로 나누어진것이 아니라 개념적으로 나누어진 것이라 영역이라 하면 헷갈릴까봐 용도라는 용어를 사용했다. 스왑 영역은 메인 메모리를 보조하는 역할을 하며 앞에서 학습하였던 내용과 동일하였다. 운영체제에서 본인이 스왑 영역의 용량을 조절 할 수도 있다.

    2. vm_anon_init

      위에서 언급 했듯이 anon 페이지는 swap 영역을 다룬다. swap 영역은 swap table을 통해 관리한다. swap table은 비트맵 자료구조를 이용하여 관리한다. 각 비트는 페이지 1개를 의미하며 처음 비트맵이 만들어지면 모든 비트는 false이다. 추후에 swap out을 하면 true로 바꾸어 준다. 즉 swap 영역이 비어있으면 false, 그렇지 않다면 true의 값을 갖는다. uninit 상태에서 anon Init을 하면 비트맵 구조의 swap table을 생성한다. disk_size 함수가 반환하는 값은 바이트가 아닌 섹터 수임을 유의하자.

      void vm_anon_init (void) {
      		/* 디스크를 swap 용도로 사용 시 disk_get에 1,1을 인자로 넘겨줌 */
      		swap_disk = disk_get(1, 1);
          /* 디스크 사이즈에 몇개의 페이지가 할당되는지 계산 */
      		size_t swap_size = disk_size(swap_disk) / SECTORS_PER_PAGE;
      		/* 페이지 수 만큼의 비트를 가진 비트맵 생성 */
          swap_table = bitmap_create(swap_size);
      }
      
    3. anon_swap_out

      페이지를 swap 영역으로 swap out하는 함수이다. 우선 비트맵을 스캔하여 페이지를 내려보낼 swap slot 1개를 검색한다. 한 페이지에 8개의 섹터가 할당되므로 비트맵을 순회하며 8개의 연속된 섹터의 비트가 false인 경우가 있는지 탐색한다. 그리고 각 섹터에 페이지의 데이터를 입력한다. 데이터를 모두 내려 보냈다면 pml4와 비트맵을 수정해준다. pml4의 present bit을 0으로 갱신하고 비트맵의 false를 true로 바꿔준다. 그리고 나중에 swap in 할때 해당 swap 영역을 찾도록 anon_page 구조체 안에 기록해 둔다.

      static bool anon_swap_out (struct page *page) {
      	struct anon_page *anon_page = &page->anon;
      
      	/* 비트맵을 처음부터 순회해 false 값을 가진 비트를 하나 찾는다.
      	   즉, 페이지를 할당받을 수 있는 swap slot을 하나 찾는다. */
      	int page_no = bitmap_scan(swap_table, 0, 1, false);
      
          if (page_no == BITMAP_ERROR) {
              return false;
          }
      
      	/* 한 페이지를 디스크에 써 주기 위해 SECTORS_PER_PAGE개의 섹터에 저장해야 한다.
      	   이 때 디스크에 각 섹터의 크기 DISK_SECTOR_SIZE만큼 써 준다. */
          for (int i = 0; i < SECTORS_PER_PAGE; ++i) {
              disk_write(swap_disk, page_no * SECTORS_PER_PAGE + i, page->va + DISK_SECTOR_SIZE * i);
          }
      
      	/* swap table의 해당 페이지에 대한 swap slot의 비트를 TRUE로 바꿔주고,
      	   해당 페이지의 PTE에서 Present Bit을 0으로 바꿔준다.
      	   이제 프로세스가 이 페이지에 접근하면 Page Fault가 뜬다.  */
          bitmap_set(swap_table, page_no, true);
          pml4_clear_page(thread_current()->pml4, page->va);
      
      	/* 페이지의 swap_index 값을 이 페이지가 저장된 swap slot의 번호로 써 준다. */
          anon_page->swap_index = page_no;
      
          return true;
      }
      
    4. anon_swap_in

      swap in은 swap out과 반대로 swap 영역에 저장되어있던 데이터를 커널 가상주소에 입력한다. 이때 swap out 때 저장하였던 스왑 영역의 데이터를 커널 영역의 페이지에 입력해준다. 그리고 해당 슬롯을 false로 만들어 다른 페이지가 사용할 수 있도록 한다.

      static bool
      anon_swap_in (struct page *page, void *kva) {
      	struct anon_page *anon_page = &page->anon;
      	int page_no = anon_page->swap_index;
      	
      	/* swap out 상태이므로 해당 페이지의 비트는 true이어야 한다. */
      	if (bitmap_test(swap_table, page_no) == false) {
              return false;
          }
      		/* 페이지 내 섹터들에 대해 커널 가상 주소에 데이터 입력 */
          for (int i = 0; i < SECTORS_PER_PAGE; ++i) {
              disk_read(swap_disk, page_no * SECTORS_PER_PAGE + i, kva + DISK_SECTOR_SIZE * i);
          }
      		/* swap in 완료 하였으므로 해당 슬롯의 비트를 False로 갱신 */
          bitmap_set(swap_table, page_no, false);
          
          return true;
      
      }
      
  2. File-backed Page

    file backed 페이지는 anon 페이지와 달리 이미 저장되어 있는 파일과 연관되어있다. 이 경우 우리는 이전에 container 구조체에 관련 파일에 대한 정보를 입력해 두었다. 이를 이용하면 더욱 간단하게 swap in과 out을 구현할 수 있다.

    1. file_backed_swap_out

      swap out 시키려는 페이지가 비어있으면 안되므로 검사해준다. 그리고 aux에 컨테이너 구조체를 입력하고 dirty bit를 통해 수정이 되었는지 확인한다. 만약 수정되었다면 파일에 수정된 내용을 기입하고 dirty bit를 0으로 갱신시켜준다. 그 후 pml4의 present bit를 수정하여 page fault가 발생하도록 한다.

      static bool
      file_backed_swap_out (struct page *page) {
      	struct file_page *file_page UNUSED = &page->file;
      
      	if (page == NULL)
              return false;
      
          struct container * aux = (struct container *) page->uninit.aux;
          
          /* 페이지의 수정여부를 확인하고 수정 되었다면 파일에 수정내용 기입 */
          if(pml4_is_dirty(thread_current()->pml4, page->va)){
              file_write_at(aux->file, page->va, aux->page_read_bytes, aux->offset);
              pml4_set_dirty (thread_current()->pml4, page->va, 0);
          }
      		/* pml4 갱신 */
          pml4_clear_page(thread_current()->pml4, page->va);
      
      }
      
    2. file_bakced_swap_in

      swap in은 파일로 부터 데이터를 읽어 메모리로 올리는 것이다. 이는 이전에 로드와 유사한 개념이라 코드도 거의 비슷하다고 보면된다.

      static bool
      file_backed_swap_in (struct page *page, void *kva) {
      	struct file_page *file_page UNUSED = &page->file;
      	if(page == NULL)
      		return false;
      
      	struct container *aux = (struct container *)page ->uninit.aux;
      
      	struct file *file = aux->file;
      	off_t offset = aux->offset;
        size_t page_read_bytes = aux->page_read_bytes;
        size_t page_zero_bytes = PGSIZE - page_read_bytes;
      
      	file_seek (file, offset);
      
        if (file_read (file, kva, page_read_bytes) != (int) page_read_bytes) {
            // palloc_free_page (kva);
            return false;
        }
        if (page_zero_bytes>0)
            memset (kva + page_read_bytes, 0, page_zero_bytes);
      
        return true;
      
      }
      
  3. 더 알아보아야 할 것