[PintOS] Page Table 페이지 테이블

페이지 테이블의 주요 역할:
- 가상 주소를 물리 주소로 변환: 프로그램에서 사용하는 가상 주소는 페이지 테이블을 통해 실제 물리 메모리 주소로 변환됩니다.
- 페이지 접근 제어: 페이지 테이블에는 각 페이지의 접근 권한(읽기, 쓰기, 실행 등)이 저장되어 있으며, 이를 통해 메모리 보호 기능을 수행합니다.
- 페이지 교체 관리: 사용하지 않는 페이지는 디스크에 저장된 백업 스토리지(스왑 공간)로 이동하고, 필요한 페이지는 다시 메모리로 로드됩니다. 페이지 테이블은 이러한 페이지 교체 과정을 관리합니다.
페이지 테이블의 구조:
페이지 테이블은 일반적으로 다음과 같은 정보를 포함하는 엔트리들로 구성됩니다.
- 페이지 번호: 가상 주소 공간에서 페이지를 식별하는 번호
- 프레임 번호: 실제 물리 메모리에서 페이지가 할당된 프레임 번호
- 유효 비트: 페이지가 유효한지 여부를 나타내는 비트
- 접근 권한: 페이지에 대한 접근 권한(읽기, 쓰기, 실행 등)
- 변경 비트: 페이지가 변경되었는지 여부를 나타내는 비트
- 보호 키: 페이지에 대한 접근을 제어하는 보안 키
페이지 테이블의 작동 방식:
- 프로그램에서 메모리에 액세스하려고 하면 CPU는 가상 주소를 생성합니다.
- CPU는 페이지 테이블 레지스터(PTR)에 저장된 페이지 테이블 기본 주소를 사용하여 페이지 테이블 엔트리를 찾습니다.
- 페이지 테이블 엔트리에서 페이지 번호가 일치하는 엔트리를 찾습니다.
- 엔트리가 유효하면 프레임 번호가 페이지 테이블 엔트리에 저장됩니다.
- CPU는 프레임 번호와 오프셋을 사용하여 실제 물리 주소를 계산합니다.
- 계산된 물리 주소를 사용하여 메모리에 액세스합니다.
페이지 테이블
threads/mmu.c의 코드는 x86_64 하드웨어 페이지 테이블에 대한 추상 인터페이스입니다. Pintos에서 사용하는 페이지 테이블은 Page-Map-Level-4의 약자인 pml4라고 불리며, 이는 테이블이 4단계로 구성되어 있기 때문입니다. 페이지 테이블 인터페이스는 내부 구조에 접근하기 편리하도록 uint64_t *를 사용하여 페이지 테이블을 표현합니다. 아래 섹션에서는 페이지 테이블 인터페이스와 내부 구조에 대해 설명합니다.
생성, 소멸, 및 활성화
이 함수들은 페이지 테이블을 생성, 소멸 및 활성화합니다. 기본 Pintos 코드에서는 이미 필요한 곳에서 이 함수들을 호출하므로, 직접 호출할 필요는 없습니다.
- uint64_t *pml4_create(void);
- 새로운 페이지 테이블을 생성하고 반환합니다. 새로운 페이지 테이블은 Pintos의 일반 커널 가상 페이지 매핑을 포함하지만 사용자 가상 매핑은 포함하지 않습니다. 메모리를 얻을 수 없는 경우 null 포인터를 반환합니다.
- void pml4_destroy(uint64_t *pml4);
- pml4가 소유한 모든 자원, 페이지 테이블 자체 및 매핑된 프레임을 해제합니다. 모든 레벨의 페이지 테이블 자원을 해제하기 위해 pdpe_destroy, pgdir_destroy 및 pt_destroy를 재귀적으로 호출합니다.
- void pml4_activate(uint64_t *pml4);
- pml4를 활성화합니다. 활성화된 페이지 테이블은 CPU가 메모리 참조를 변환하는 데 사용하는 페이지 테이블입니다.
검사 및 업데이트
이 함수들은 페이지 테이블이 캡슐화한 페이지에서 프레임으로의 매핑을 검사하거나 업데이트합니다. 실행 중인 프로세스와 중지된 프로세스 모두에서 사용되며, 필요에 따라 TLB를 플러시합니다.
- bool pml4_set_page(uint64_t *pml4, void *upage, void *kpage, bool rw);
- 사용자 페이지 upage를 커널 가상 주소 kpage가 식별하는 프레임에 매핑합니다. rw가 true인 경우 페이지는 읽기/쓰기로 매핑되며, 그렇지 않은 경우 읽기 전용으로 매핑됩니다. 사용자 페이지 upage는 pml4에 이미 매핑되어 있지 않아야 합니다. 커널 페이지 kpage는 palloc_get_page(PAL_USER)를 사용하여 사용자 풀에서 얻은 커널 가상 주소여야 합니다. 성공 시 true를 반환하고, 실패 시 false를 반환합니다. 페이지 테이블에 필요한 추가 메모리를 얻을 수 없는 경우 실패합니다.
- void *pml4_get_page(uint64_t *pml4, const void *uaddr);
- pml4에서 uaddr에 매핑된 프레임을 조회합니다. uaddr이 매핑된 경우 해당 프레임의 커널 가상 주소를 반환하고, 그렇지 않은 경우 null 포인터를 반환합니다.
- void pml4_clear_page(uint64_t *pml4, void *upage);
- pml4에서 페이지를 "존재하지 않음"으로 표시합니다. 이후 페이지에 대한 접근은 오류를 발생시킵니다. 페이지 테이블의 다른 비트는 유지되어 접근 및 더티 비트를 확인할 수 있습니다. 페이지가 매핑되지 않은 경우 이 함수는 아무런 효과가 없습니다.
접근 및 더티 비트
x86_64 하드웨어는 각 페이지에 대한 페이지 테이블 엔트리(PTE)의 두 개의 비트를 통해 페이지 교체 알고리즘 구현을 일부 지원합니다. 페이지에 대한 읽기 또는 쓰기 시 CPU는 해당 페이지의 PTE에서 접근 비트를 1로 설정하고, 쓰기 시에는 더티 비트를 1로 설정합니다. CPU는 이 비트들을 0으로 리셋하지 않지만, 운영체제(OS)는 이를 리셋할 수 있습니다. 이러한 비트들을 올바르게 해석하려면 두 개 이상의 페이지가 동일한 프레임을 참조하는 경우인 별칭(alias)에 대한 이해가 필요합니다. 별칭된 프레임이 접근될 때, 접근 및 더티 비트는 오직 하나의 페이지 테이블 엔트리(접근에 사용된 페이지의 엔트리)에서만 업데이트됩니다. 다른 별칭들의 접근 및 더티 비트는 업데이트되지 않습니다.
- bool pml4_is_dirty(uint64_t *pml4, const void *vpage);
- pml4에 더티(혹은 접근)로 표시된 vpage에 대한 페이지 테이블 엔트리가 있으면 true를 반환합니다. 그렇지 않으면 false를 반환합니다.
- void pml4_set_dirty(uint64_t *pml4, const void *vpage, bool dirty);
- void pml4_set_accessed(uint64_t *pml4, const void *vpage, bool accessed);
- pml4에 페이지에 대한 페이지 테이블 엔트리가 있으면 해당 더티(또는 접근) 비트를 주어진 값으로 설정합니다.