8 min read

pintos - project3(Anonymous Page) 수도 코드

저번 포스팅에 이어 두번째 과제인 Anonymous Page에 대한 포스팅 진행하겠습니다.

이번 과제에서는 Lazy-Loading을 구현해야합니다.본격적으로 구현을 하기 앞서 필수적으로 알아야할 지식들을 먼저 소개하고 가겠습니다.

  1. VM과제에서 페이지의 종류는 3가지입니다.UNINIT,File-Backed,ANON입니다.Lazy-Loading을 구현하게되면 우선적으로 UNINIT 타입의 페이지를 생성합니다.
  2. 이후 페이지 폴트가 발생하면 UNINIT 페이지에 실제 물리 프레임을 연결하고 File 또는 ANON 타입의 페이지로 타입을 변경합니다.
  3. exception.c의 page_fault()함수를 따라가며 페이지 폴트가 어떤 흐름을 가지는지 이해하시면 구현에 도움이 됩니다.
  4. 결론적으로 UNINIT 타입의 페이지에 존재하는 va에 쓰레드가 접근하면 페이지 폴트가 무조건 발생하며 뒤이어 해당 페이지에 실제 물리 프레임이 할당됩니다.

깃북에 위에서 말한 대부분의 내용이 있으니 구현전 상세히 읽어보시길 바랍니다.

Lazy Loading for Executable

해당 챕터에서 가장 먼저 구현해야할 함수는 아래와 같습니다.

//vm.c
bool vm_alloc_page_with_initializer (enum vm_type type, void *va,
        bool writable, vm_initializer *init, void *aux);
  • 우선 해당 함수의 스켈레톤 코드를 보면 spt_find_page()함수의 결과가 NULL이 아니면 에러를 일으키며 false를 리턴합니다.
  • 즉,현재 쓰레드의 SPT에 인자로 들어온 upage라는 주소를 가지는 페이지는 존재하지 않습니다.
  • upage의 경우,새롭게 만들 페이지의 va입니다.
  • 해당 함수를 구현하기전에 해당 함수를 호출하는 함수를 먼저 찾아보시길 추천합니다.
  • 우선 아무 정보도 담겨있지않은 페이지를 하나 새롭게 할당한 뒤,아래 문법을 통해 해당 페이지가 추후에 사용할 page_initializer를 만들어줍니다.
typedef bool (*page_initializer) (struct page *, enum vm_type, void *kva);
page_initializer new_initializer = NULL;
  • 이후 new_initializer를 페이지 타입에 맞게 initializer를 설정해줍니다.
  • 앞서 저희가 할당한 page를 uninit 페이지로 만들어줍니다.
  • 만약 남은 인자가 존재한다면 이 또한 처리해줍니다.
  • 이후 저희가 만든 페이지를 SPT에 넣어주며 함수값을 리턴해줍니다.
static bool uninit_initialize (struct page *page, void *kva);
  • 저희 팀의 코드에서는 해당 함수를 따로 수정하지 않고 넘어갔습니다.
void vm_anon_init (void);
  • swap in-out 부분과 직결되는 내용이라 현재단계에서는 수정하지 않고 넘어갑니다.
bool anon_initializer (struct page *page,enum vm_type type, void *kva);
  • 해당 함수 또한 스켈레톤 코드로 남겨두고 추후 스왑 부분에서 수정하겠습니다.
//process.c
static bool load_segment (struct file *file, off_t ofs, uint8_t *upage,
        uint32_t read_bytes, uint32_t zero_bytes, bool writable);
  • 지금부터가 lazy-loading을 구현하는것에 있어 가장 중요한 함수들이라고 볼 수 있습니다.
  • 우선 기존의 project2에서 사용하던 load_segment() 함수가 아니라 파일의 아래부분에 #else 밑 부분에 정의되어 있는 함수입니다.
  • 우선 주석으로 주어진 todo를 읽어보면 lazy_load_segment()함수로 전달한 데이터들을 설정하라고 합니다.
  • 여기서 aux라는 개념을 이해하셔야 합니다.이는 저희가 어떠한 구조체를 만들어 해당 aux를 통해 해당 구조체의 주소값을 전달하여 추후 해당 aux를 통해 저희가 만든 구조체에 담겨있는 데이터에 접근할수 있게합니다.
  • 즉, load_segment()함수에서 lazy_load_segment()함수로 전달할 데이터들을 여러분이 결정해야합니다.
  • 결정하실때 기존의 load_segment()함수를 유심히 보시면 도움임 될것 같습니다.
  • 저희 팀의 경우 process.h 파일에 새로운 구조체를 하나 선언하여 해당 구조체의 주소를 aux로 넘겨주었습니다.
//process.c
static bool lazy_load_segment (struct page *page, void *aux);
  • 해당 함수는 실제로 페이지 폴트가 발생했을 때 호출되며,실제로 주어진 물리메모리에 주어진 파일을 읽습니다.
  • 이 함수 또한 기존의 load_segment()가 실제로 페이지를 load하는 부분을 참고하여 작성하시면 됩니다.
  • 두번째 인자인 aux에 접근하면 저희가 load_segment()에서 전달한 데이터를 사용할수 있습니다.
//process.c
static bool setup_stack (struct intr_frame *if_);
  • prcoess.c에서 마지막으로 구현해야할 함수입니다.
  • 해당 함수에서는 한개의 페이지 크기만큼을 USER_STACK 주소에서 내려 새로운 스택 바텀으로 설정해줍니다.
  • vm_alloc_page()를 호출하여 바로 하나의 UNINIT 페이지를 생성합니다.
  • vm_alloc_page() 함수는 앞서 저희가 구현한 vm_alloc_page_with_initializer() 함수에서 1,2,3번째 인자만 받는 함수입니다.4,5번째 인자는 NULL로 받습니다.vm.h에 define을 통해 정의되어 있습니다.
  • 이후,해당 페이지에 곧바로 물리 프레임을 할당시켜 ANON 타입의 페이지로 설정합니다.만약 해당 함수 호출이 성공할 경우 if의 rsp값을 USER_STACK으로 변경합니다.
  • 즉, 프로젝트2의 argument passing에서 저희가 스택에 인자를 쌓았을때 그 인자들이 쌓인 페이지는 Lazy-Loading 되지 않고 곧바로 사용되어야하기에 setup_stack() 함수에서 이를 설정해주는것입니다.
  • 설정된 rsp인 USER_STACK부터 argument passing의 인자가 쌓입니다.
  • 쉽게 말하면 어차피 바로 써야할 페이지에 대해서 미리 물리 프레임을 연결해주는 함수입니다.

Supplemental Page Table - Revisit

//vm.c
bool supplemental_page_table_copy (struct supplemental_page_table *dst,
    struct supplemental_page_table *src);
  • 해당 함수는 부모 프로세스가 가지고 있는 본인의 SPT 정보를 빠짐없이 자식 프로세스에게 복사해주는 기능을 수행합니다.(fork 시스템 콜)
  • 우선 해시테이블을 통해 구현한 SPT를 iteration해줘야 합니다.이를 구현하기 위한 방법을 hash.c 파일에서 제공합니다.저희 팀의 경우 간단히 while문을 통해 iter해주는 방식을 택하였습니다.
  • 이후 iter를 돌 때마다 해당 hash_elem과 연결된 page를 찾아서 해당 페이지 구조체의 정보들을 저장합니다.
  • 페이지 구조체의 어떠한 정보를 저장해야할지 감이 안오신다면 vm_alloc_page_with_initializer()함수의 인자를 참고하시길 바랍니다.
  • 부모 페이지들의 정보를 저장한 뒤,자식이 가질 새로운 페이지를 생성해야합니다.생성을 위해 부모 페이지의 타입을 먼저 검사합니다.즉,부모 페이지가UNINIT 페이지인 경우와 그렇지 않은 경우를 나누어 페이지를 생성해줘야합니다.
  • 만약 uninit이 아닌 경우 setup_stack()함수에서 했던 것처럼 페이지를 생성한뒤 바로 해당 페이지의 타입에 맞는 initializer를 호출해 페이지 타입을 변경시켜줍니다.그리고 나서 부모페이지의 물리 메모리 정보를 자식에게도 복사해주어야 합니다.
  • 모든 함수 호출이 정상적으로 이루어졌다면 return true를 하며 함수를 종료합니다.
//vm.c
void supplemental_page_table_kill (struct supplemental_page_table *spt);
  • 깃북에 적힌것처럼 해당 함수는 존재하는 SPT를 모두 free()하며 할당 해제해주는 함수입니다.
  • 저희는 간단한게 hash.c 파일에 존재하는 hash_destroy() 함수만 사용했습니다.
  • 해당 함수를 사용하려면 두번째 인자로 넘어가는 aux에 대응하는 함수를 하나 만들어야합니다.
  • 만들어야하는 함수의 경우 hash_elem을 인자로 받기에 elem을 통해 page를 찾고 해당 페이지를 할당 해제해주는 로직을 구현했습니다.
  • 추후 memory-mapped 부분을 진행할때,해당 함수를 추가적으로 수정할 예정입니다.

Page Cleanup

static void uninit_destroy (struct page *page);
static void anon_destroy (struct page *page);
  • 위 두개의 함수는 저희가 해당 함수들의 주석을 읽고나서 변경을 하지 않았습니다.

깃북에 적힌 바와 같이 위와 같이 진행하게 되며 project2의 테스트 케이스들이 전부 통과합니다.혹시 여기까지 고민하며 구현하다가 시간이 촉박하실 경우 제 깃허브를 참고하셔서 코드 구현하시길 바랍니다.