오늘 하루에 집중하자
  • [CS] PintOS Project 3 - Virtual Memory(2) - Memory Management
    2023년 01월 04일 21시 56분 26초에 업로드 된 글입니다.
    작성자: nickhealthy

     

    가상 메모리 시스템을 지원하기 위해서는 가상 페이지와 물리적 프레임을 효과적으로 관리해야 합니다. 이것은 어떤 (가상 또는 물리적) 메모리 영역이 어떤 목적으로, 누가 사용하고 있는지 등을 추적해야 한다는 것을 의미합니다. 첫째로 추가 페이지 테이블을 처리하고, 그 다음 물리적 프레임을 처리합니다. 참고로, 이해를 위해 "페이지"라는 용어는 가상 페이지를, "프레임"이라는 용어는 물리적 페이지를 의미합니다.

     

    Page Structure and Operations (페이지 구조와 처리 작업)


    Struct Page

    include/vm/vm.h 에 정의된 page는 가상 메모리에서의 페이지를 나타내는 구조체입니다. 페이지에 관해 알아야 할 모든 필요한 데이터를 저장합니다. 현재 템플릿에서 이 구조체는 다음과 같습니다:

    struct page {
      const struct page_operations *operations;
      void *va;              /* Address in terms of user space */
      struct frame *frame;   /* Back reference for frame */
    
      union {
        struct uninit_page uninit;
        struct anon_page anon;
        struct file_page file;
    #ifdef EFILESYS
        struct page_cache page_cache;
    #endif
      };
    };

     

    Page Operations, 가상 주소, 물리적 프레임을 가지고 있습니다. 또한, 유니온 필드를 가지고 있습니다. Union은 메모리 영역에 여러 종류의 데이터를 저장할 수 있는 특별한 데이터 타입입니다. Union에는 여러 멤버가 있지만, 한 번에 한 멤버에만 값을 가질 수 있습니다. 이것은 우리 시스템에서 페이지가 uninit_page, anon_page, file_page, 또는 page_cache 중 하나일 수 있다는 것을 의미합니다. 예를 들어, 페이지가 Anonymous Page일 경우, 그때 Page 구조체는 그 중 하나인 struct anon_page anon 필드를 가집니다. anon_page 는 익명 페이지를 위해 유지해야 할 모든 필요한 정보를 가지고 있습니다.

     

    Page Operations

    위에서 설명한 것과 같이, include/vm/vm.h 에서 정의된 페이지는 VM_UNINIT, VM_ANON, 또는 VM_FILE 중 하나가 될 수 있습니다. 페이지에서는 스왑 인, 스왑 아웃, 그리고 페이지 제거와 같은 여러 가지 작업이 수행될 수 있습니다. 각 페이지 유형마다 이러한 작업을 수행하기 위해 필요한 절차와 작업은 다릅니다. 즉, VM_ANON 페이지와 VM_FILE 페이지에 대해 다른 destroy 함수가 호출되어야 합니다. 한 가지 방법은 각 함수에서 switch-case 구문을 사용해서 각 케이스를 처리하는 것입니다. 우리는 이러한 문제를 해결하기 위해 객체 지향 프로그래밍의 "클래스 상속" 개념을 소개합니다. 실제로 C 프로그래밍 언어에는 "클래스"나 "상속"이 없지만, 우리는 함수 포인터를 사용해서 이러한 개념을 구현할 수 있습니다. 이것은 실제 운영 체제 코드처럼 리눅스와 같이 구현하는 것과 유사합니다.

     

    함수 포인터는 지금까지 배운 다른 포인터와 같이, 메모리 안에서 함수나 실행 가능한 코드를 가리키는 포인터입니다. 함수 포인터는 실행 시간 값에 기반해 정해진 함수를 실행하기 위한 간단한 방법을 제공하기 때문에 유용합니다. 우리의 경우, 코드 수준에서는 그냥 destroy (page)를 호출하면 되고, 컴파일러는 페이지 유형에 따라 적절한 destroy 루틴을 선택해서 올바른 함수 포인터를 호출합니다.

     

    Page Operations을 위한 구조체인 struct page_operationsinclude/vm/vm.h에 정의되어 있습니다. 이 구조체를 함수의 테이블로 생각해보고, 3개의 함수 포인터가 포함되어 있습니다.

    struct page_operations {
      bool (*swap_in) (struct page *, void *);
      bool (*swap_out) (struct page *);
      void (*destroy) (struct page *);
      enum vm_type type;
    };

     

    이제, page_operation 구조체가 어디에 있는지 살펴보겠습니다. include/vm/vm.h 에 있는 페이지 구조체인 struct page를 살펴보면, operations 필드를 찾을 수 있습니다. 이제 vm/file.c 로 가보면, 함수 원형 전에 file_ops라는 page_operations 구조체가 선언되어 있을 것입니다. 이것은 파일 기반 페이지를 위한 함수 포인터의 테이블입니다. .destroy 필드에는 file_backed_destroy가 있는데, 이것은 페이지를 제거하는 함수이고, 같은 파일에서 정의되어 있습니다.

     

    함수 포인터 인터페이스를 사용해서 file_backed_destroy가 어떻게 호출되는지 살펴보겠습니다. vm/vm.c에 있는 vm_dealloc_page (page)가 호출된다고 가정해보고, 이 페이지가 파일 기반 페이지(VM_FILE)일 경우를 생각해봅시다. 이 함수 안에서 destroy (page)를 호출합니다. destroy (page)는 include/vm/vm.h에서 정의된 매크로로 아래와 같이 정의되어 있습니다:

    #define destroy(page) if ((page)->operations->destroy) (page)->operations->destroy (page)

     

    이것은 destroy 함수를 호출하는 것은 실제로 (page)->operations->destroy (page)를 호출한다는 것을 알려줍니다. 이것은 페이지 구조체에서 가져온 destroy 함수입니다. 이 페이지가 VM_FILE 페이지인 경우, .destroy 필드는 file_backed_destory를 가리킵니다. 결과적으로, 파일 기반 페이지의 destroy 루틴이 실행됩니다.

     

    Implement Supplemental Page Table (추가 페이지 테이블 구현하기)


    이 시점에서, PintOS에는 메모리의 가상 및 물리 매핑을 관리하기 위한 페이지 테이블(pml4)이 있습니다. 그러나 이것만으로는 충분하지 않습니다. 이전 섹션에서 언급한 것처럼, 페이지 잠금과 자원 관리를 처리하기 위해 각 페이지에 대한 추가 정보를 저장하기 위해 추가 페이지 테이블이 필요합니다. 따라서, 프로젝트 3의 첫 번째 작업으로 추가 페이지 테이블의 기본 기능을 구현할 것을 권장합니다.

     

    vm/vm.c 에 추가 페이지 테이블 관리 기능을 구현합니다.

     

    먼저 Pintos에서 추가 페이지 테이블을 어떻게 설계할 것인지 결정해야 합니다. 추가 페이지 테이블을 설계한 후, 아래 세 가지 기능을 자신의 설계에 맞게 구현합니다.

    void supplemental_page_table_init (struct supplemental_page_table *spt);
    추가 페이지 테이블을 초기화합니다. 추가 페이지 테이블에 사용할 데이터 구조를 선택할 수 있습니다. 이 함수는 새로운 프로세스가 시작할 때(userprog/process.c의 initd에서)와 프로세스가 포크될 때(userprog/process.c의 __do_fork에서) 호출됩니다.

     

    struct page *spt_find_page (struct supplemental_page_table *spt, void *va);
    주어진 추가 페이지 테이블에서 va에 해당하는 struct page를 찾습니다. 실패하면 NULL을 반환합니다.

     

    bool spt_insert_page (struct supplemental_page_table *spt, struct page *page);
    주어진 추가 페이지 테이블에 struct page를 삽입합니다. 이 함수는 주어진 추가 페이지 테이블에 가상 주소가 존재하지 않음을 검사해야 합니다.

     

    Frame Management (프레임 관리)


    지금부터 모든 Page는 메모리가 생성될 때의 메모리에 대한 메타데이터만을 저장하는 페이지가 아닙니다. 따라서 물리 메모리를 관리하기 위해 다른 전략이 필요합니다. include/vm/vm.h 에는 물리 메모리를 나타내는 struct frame이 존재합니다. 현재 구조는 다음과 같습니다:

    /* The representation of "frame" */
    struct frame {
      void *kva;
      struct page *page;
    };

     

    구조체에는 두 개의 필드가 있습니다 - 커널 가상 주소인 kva와 page 구조체인 page입니다. 프레임 관리 인터페이스를 구현할 때 멤버를 추가할 수 있습니다.

     

    vm/vm.c 에서 vm_get_frame, vm_claim_page, vm_do_claim_page를 구현합니다.

     

    static struct frame *vm_get_frame (void);
    palloc_get_page을 호출하여 새로운 물리 페이지를 사용자 풀에서 가져옵니다. 사용자 풀에서 페이지를 성공적으로 가져왔을 때, 프레임을 할당하고, 그 멤버를 초기화한 후 그것을 반환합니다. vm_get_frame을 구현한 후, 이 함수를 통해 모든 사용자 공간 페이지(PALLOC_USER)를 할당해야 합니다. 지금은 페이지 할당 실패의 경우 스왑 아웃을 처리할 필요가 없습니다. 일단 이러한 경우를 PANIC ("todo")로 표시하기만 하세요.

     

    bool vm_do_claim_page (struct page *page);
    페이지를 할당하는 것을 의미하는 Claim을 수행합니다. 먼저 vm_get_frame을 호출하여 프레임을 가져옵니다(이미 템플릿에서 완료됨). 그런 다음, MMU를 설정해야 합니다. 즉, 가상 주소와 물리 주소 간의 매핑을 페이지 테이블에 추가합니다. 반환 값은 작업이 성공했는지 아닌지를 지정해야 합니다.

     

    bool vm_claim_page (void *va);
    va를 할당할 페이지를 Claim합니다. 먼저 페이지를 가져온 다음, 페이지와 함께 vm_do_claim_page를 호출합니다.

     

    댓글