가상 메모리 시스템을 지원하기 위해서는 가상 페이지와 물리 프레임을 효과적으로 관리해야 합니다. 이는 어떤 (가상 또는 물리) 메모리 영역이 사용 중인지, 어떤 목적으로, 누구에 의해 사용되는지 등을 추적해야 한다는 것을 의미합니다. 먼저 보조 페이지 테이블을 다룬 다음, 물리 프레임을 다룰 것입니다. 이해를 돕기 위해, 가상 페이지에는 "페이지"라는 용어를, 물리 페이지에는 "프레임"이라는 용어를 사용합니다.
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
};
};
이 구조체는 페이지 연산(아래 참조), 가상 주소, 물리 프레임을 가지고 있습니다. 추가로, 공용체(union) 필드를 가지고 있습니다. 공용체는 하나의 메모리 영역에 서로 다른 타입의 데이터를 저장할 수 있게 해주는 특별한 데이터 타입입니다. 공용체에는 여러 멤버가 있지만, 한 번에 하나의 멤버만 값을 가질 수 있습니다. 이는 우리 시스템의 페이지가 uninit_page, anon_page, file_page, 또는 page_cache일 수 있다는 것을 의미합니다. 예를 들어, 페이지가 익명 페이지(Anonymous Page 참조)인 경우, page 구조체는 struct anon_page anon
을 멤버 중 하나로 가지게 됩니다. anon_page
는 익명 페이지를 위해 유지해야 하는 모든 필요한 정보를 포함하게 됩니다.
위에서 설명한 것처럼 include/vm/vm.h
에 정의된 바와 같이, 페이지는 VM_UNINIT
, VM_ANON
, 또는 VM_FILE
일 수 있습니다. 페이지에 대해서는 스왑 인, 스왑 아웃, 페이지 파괴와 같은 여러 가지 작업이 수행됩니다. 페이지의 타입에 따라 이러한 작업에 필요한 단계와 작업이 다릅니다. 즉, VM_ANON
페이지와 VM_FILE
페이지에 대해 서로 다른 destroy
함수를 호출해야 합니다. 한 가지 방법은 각 함수에서 switch-case 문법을 사용하여 각 경우를 처리하는 것입니다. 우리는 객체 지향 프로그래밍의 "클래스 상속" 개념을 도입하여 이를 처리합니다. 실제로 C 프로그래밍 언어에는 "클래스"나 "상속"이 없지만, 우리는 리눅스와 같은 실제 운영 체제 코드에서와 유사한 방식으로 함수 포인터를 활용하여 이러한 개념을 구현합니다.
함수 포인터는 지금까지 배운 다른 포인터와 마찬가지로, 함수 또는 메모리 내의 실행 가능한 코드를 가리키는 포인터입니다. 함수 포인터는 실행 시간 값에 따라 어떤 검사 없이도 특정 함수를 호출하여 실행할 수 있는 간단한 방법을 제공하기 때문에 유용합니다. 우리의 경우, 코드 레벨에서 destroy (page)
를 호출하는 것만으로 충분하며, 컴파일러는 페이지 타입에 따라 적절한 destroy
루틴을 올바른 함수 포인터를 호출하여 선택할 것입니다.
페이지 연산을 위한 구조체 struct page_operations
는 include/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
로 가면, 함수 프로토타입 앞에 선언된 page_operations 구조체 file_ops
를 볼 수 있습니다. 이것은 파일 지원 페이지를 위한 함수 포인터 테이블입니다. .destroy
필드는 페이지를 파괴하는 함수인 file_backed_destroy
의 값을 가지며, 이는 동일한 파일에 정의되어 있습니다.
함수 포인터 인터페이스를 통해 file_backed_destroy
가 어떻게 호출되는지 이해해 봅시다. vm_dealloc_page (page)
(vm/vm.c
에 있음)가 호출되고, 이 페이지가 파일 지원 페이지(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 루틴이 수행됩니다.
이 시점에서 Pintos는 메모리의 가상 및 물리 매핑을 관리하기 위한 페이지 테이블(pml4
)을 가지고 있습니다. 그러나 이것만으로는 충분하지 않습니다. 이전 섹션에서 논의한 바와 같이, 페이지 폴트와 리소스 관리를 처리하기 위해서는 각 페이지에 대한 추가 정보를 저장할 보충 페이지 테이블이 필요합니다. 따라서 프로젝트 3의 첫 번째 작업으로 보충 페이지 테이블에 대한 몇 가지 기본 기능을 구현할 것을 제안합니다.
vm/vm.c
에서 보충 페이지 테이블 관리 함수를 구현하세요.
먼저 Pintos에서 보충 페이지 테이블을 어떻게 설계할지 결정해야 합니다. 자신만의 보충 페이지 테이블을 설계한 후, 아래의 세 가지 함수를 설계에 맞게 구현하세요.