알다시피 64 비트 가상주소는 이렇게 구성되어있다
63 48 47 39 38 30 29 21 20 12 11 0
+-------------+----------------+----------------+----------------+-------------+------------+
| Sign Extend | Page-Map | Page-Directory | Page-directory | Page-Table | Physical |
| | Level-4 Offset | Pointer | Offset | Offset | Offset |
+-------------+----------------+----------------+----------------+-------------+------------+
| | | | | |
+------- 9 ------+------- 9 ------+------- 9 ------+----- 9 -----+---- 12 ----+
Virtual Address
그리고 이러한 가상 주소를 다루기 위한 함수/매크로가
include/threads/vaddr.h
와 include/threads/mmu.h
헤더에 정의되어 있다
#define PGSHIFT { /* 세부 내용 생략 */ }
#define PGBITS { /* 세부 내용 생략 */ }
가상 주소의 오프셋 부분의 시작 비트 인덱스(0)와 비트 수(12)를 각각 나타낸다
#define PGMASK { /* 세부 내용 생략 */ }
페이지 오프셋 위치의 비트가 1이고 나머지는 0인 비트 마스크(0xfff
)
#define PGSIZE { /* 세부 내용 생략 */ }
페이지 크기(바이트 단위, 4,096)
#define pg_ofs(va) { /* 세부 내용 생략 */ }
가상 주소 va
에서 페이지 오프셋을 추출해 반환
#define pg_no(va) { /* 세부 내용 생략 */ }
가상 주소 va
에서 페이지 번호를 추출해 반환
#define pg_round_down(va) { /* 세부 내용 생략 */ }
va
가 속한 가상 페이지의 시작 주소(오프셋을 0으로 만든 값)를 반환
#define pg_round_up(va) { /* 세부 내용 생략 */ }
va
를 가장 가까운 상위 페이지 경계로 반올림한 주소를 반환
Pintos에서는 가상 메모리를 사용자 가상 메모리와 커널 가상 메모리로 나눈다
두 영역의 경계는 KERN_BASE
다
#define KERN_BASE { /* 세부 내용 생략 */ }
커널 가상 메모리의 기준(base) 주소
기본값은 0x8004000000
로 사용자 가상 메모리는 가상 주소 0부터 KERN_BASE
미만까지이며, 커널 가상 메모리는 나머지 가상 주소 공간을 차지한다
#define is_user_vaddr(vaddr) { /* 세부 내용 생략 */ }
#define is_kernel_vaddr(vaddr){ /* 세부 내용 생략 */ }
각각 va
가 사용자 가상 주소인지, 커널 가상 주소인지 여부를 true/false
로 반환한다
x86-64에서 직접 물리 주소로 메모리 접근하는 방법이 없다
그치만 OS커널에서는 이러할 필요가 있기에 Pintos에서는 커널 가상 메모리와 물리 메모리를 1대1로 매핑시켜 우회했다
KERN_BASE
보다 큰 가상 주소는 물리 주소 0을, 가상 주소 KERN_BASE + 0x1234
는 물리 주소 0x1234
를 대응해주는 식으로 말이다
이를 통해 물리 주소에 KERN_BASE
를 더하면 그 주소에 접근하는 커널 가상 주소가 되고, 반대로 커널 가상 주소에서 KERN_BASE
를 빼면 해당하는 물리 주소가 된다
이를 위한 한 쌍의 함수를 include/threads/vaddr.h
에서 제공한다
#define ptov(paddr) { /* 세부 내용 생략 */ }
물리 주소 pa
(0 이상이고, 물리 메모리 총 바이트 수 이하)에 대응하는 커널 가상 주소를 반환
#define vtop(vaddr) { /* 세부 내용 생략 */ }
커널 가상 주소인 va
에 대응하는 물리 주소를 반환
페이지 테이블에 대한 연산은 include/threads/mmu.h
에서 제공한다
#define is_user_pte(pte) { /* 세부 내용 생략 */ }
#define is_kern_pte(pte) { /* 세부 내용 생략 */ }
해당 PTE(Page Table Entry)가 각각 사용자 혹은 커널 소유인지 확인
#define is_writable(pte) { /* 세부 내용 생략 */ }
해당 PTE가 가리키는 가상 주소가 쓰기 가능한지를 확인
typedef bool pte_for_each_func (uint64_t *pte, void *va, void *aux);
bool pml4_for_each (uint64_t *pml4, pte_for_each_func *func, void *aux);
typedef bool pte_for_each_func(...)
는 콜백 함수다
역할은 PTE를 주면 계속 순회할지/중단할지 알려준다
bool pml4_for_each(...)
는 페이지 테이블 순회기로
주어진 PML4 아래의 모든 매핑된 최하위 PTE를 돌며 매 PTE마다 받은 콜백 호출한다
예시 콜백 함수
static bool
stat_page (uint64_t *pte, void *va, void *aux) {
if (is_user_vaddr (va))
printf ("user page: %llx\n", va);
if (is_writable (va))
printf ("writable page: %llx\n", va);
return true;
}
아무튼, 이를 활용해서 리소스 회수하거나 CoW준비 등을 할 수 있다