통합했다
건드려야 할 곳이다
이상한 곳 건드리지 않게 유의하자
include/vm/vm.h
, vm/vm.c
vm_type
의 정의와 설명을 볼 수 있다
VM_UNINIT
VM_ANON
VM_FILE
VM_PAGE_CACHE
(프로젝트 4용이라 지금은 무시)include/vm/uninit.h
, vm/uninit.c
VM_ANON
) 혹은 파일 기반 페이지(VM_FILE
)로 변환include/vm/anon.h
, vm/anon.c
VM_ANON
) 처리를 제공한다include/vm/file/h
, vm/file.c
VM_FILE
) 처리를 제공한다include/vm/inspect.h
, vm/inspect.c
페이지는 알다시피 4kB, 정확히는 4,096바이트다 palloc 많이 써서 안다
아무튼, 가상 페이지도 4,096바이트 짜리 가상 메모리 구간인데
페이지 정렬로 이루어져야 한다
63 48 47 39 38 30 29 21 20 12 11 0
+-------------+----------------+----------------+----------------+-------------+------------+
| Sign Extend | Page-Map | Page-Directory | Page-directory | Page-Table | Page |
| | Level-4 Offset | Pointer | Offset | Offset | Offset |
+-------------+----------------+----------------+----------------+-------------+------------+
| | | | | |
+------- 9 ------+------- 9 ------+------- 9 ------+----- 9 -----+---- 12 ----+
Virtual Address
하위 12비트는 오프셋, 상위 비트들은 페이지 테이블의 인덱스로 사용된다
오프셋(12비트)은 페이지 내부 바이트 위치(0~4095)를 알 수 있고
각 인덱스(각 9비트)는 해당 레벨 테이블에서 512(=2^9)개 엔트리 중 하나를 고르는 거다
PML4(최상위) → PDPT → PD → PT(최하위) → PT 엔트리(PTE)
Sign Extend는 말그대로 연장한거라 47비트 값으로 꽉 채우면된다 47비트가 1이었으면 1로, 0이었으면 0으로
각 프로세스는 KERN_BASE(0x8004000000)
이하의
사용자(가상) 페이지 집합을 독립적으로 가지나
커널(가상) 페이지 집합은 전역이다
커널은 어떤 스레드든 프로세스든 위치가 같다는 거다
커널은 사용자/커널 페이지 모두 접근 가능하지만
사용자 프로세스는 자신의 사용자 페이지만 접근 가능하다
Pintos에서 가상 주소 다루기 위한 함수들도 존재한다
Virtual address참조
추가로 KERN_BASE
가 뭐고 왜 0x8004000000
인지도 알아봤다
KERN_BASE
는 경계값이다
프레임은 물리 메모리 상의 페이지 크기이지 정렬된 연속 구간이다
64비트 물리 주소는 프레임 번호와 오프셋으로 나뉜다
12 11 0
+-----------------------+-----------+
| Frame Number | Offset |
+-----------------------+-----------+
Physical Address
x86-64는 물리 주소로 직접 메모리에 접근하는 방법을 제공하지 않는다
Pintos는 이를 우회하기 위해
커널 가상 메모리를 물리 메모리에 일대일로 매핑한다
난 여기가 잘 이해가 안 갔다
가상 메모리 거쳐서 물리 메모리 만지는 거나
물리 메모리 바로 만지는 거나 동일한 거 아닌가?
명령어 누가 주느냐 차이 정도기에 하나 안되면 둘 다 안되야 한다고 생각되었고
만약 둘 다 동일하게 접근 하는 거라면 사실상 눈가리고 아웅하는 거나 다를 바 없어보여 말이다
다행히도 좀 더 알아보니 이해가 되었다
기본적으로 물리 메모리 접근 방법이
명령어 가상 주소 받음
TLB에서 VA→PA(물리 주소) 번역을 찾음 (miss시 PTE로)
권한/보호 검사
캐시/메모리 접근
이 순서로 진행되기에
애시당초 직접 접근하는 방식이 없는 거였다
그렇지만 커널OS 직접 접근하듯 다룰 필요가 있기에
커널 가상 메모리와 물리 메모리를 1대1로 매핑 시킨거였다
KERN_BASE
보다 큰 가상 주소는 물리 주소 0을, 가상 주소 KERN_BASE + 0x1234
는 물리 주소 0x1234
를 대응해주는 식으로 말이다
페이지 테이블은 CPU가 가상 주소 → 물리 주소(페이지 → 프레임)로 변환할 때 사용하는 자료구조다
pintos에서는 threads/mmus.c
에 페이지 테이블 관리 코드가 있다
페이지와 프레임 관계
+----------+
.--------------->|Page Table|-----------.
/ +----------+ |
| 12 11 0 V 12 11 0
+---------+----+ +---------+----+
| Page Nr | Ofs| |Frame Nr | Ofs|
+---------+----+ +---------+----+
Virt Addr | Phys Addr ^
\_______________________________________/
스왑 슬롯은 스왑 파티션 내의 페이지 크기 디스크 공간 구간이다
스왑 파티션이 무엇이냐?
디스크에서 스왑 용도로만 쓰라고 따로 떼어 둔 전용 구역이다
보통 페이지 정렬로 둔다
굳이 페이지로 해야한다는 제약은 없지만 보통 스왑 자체를 페이지 단위로 하는데다가
페이지 테이블이나 보조 페이지 테이블로 하여금 가상 페이지, 슬롯 번호만 기억하면 되도록 설계할 수 있어 깔끔하다
디스크 I/O도 페이지 한번만 읽고/쓰기면 끝이라 효율적이기도 하다
자료 구조 설계/구현해야 하는 것들이다
보조 페이지 테이블 (Supplemental Page Table, SPT)
프레임 테이블 (Frame Table)
스왑 테이블 (Swap Table)
원한다면 부분적으로 통합하거나 해도 괜찮다고 한다
각 자료구조마다 원소에 담길 정보, 스코프(프로세스 로컬 vs 시스템 전역), 인스턴스 개수를 결정해야 한다
이 자료구조들을 비페이지 가능 메모리(malloc
, calloc
으로 할당) 에 저장해도 좋다고 한다
배열(array), 리스트(list), 비트맵(bitmap), 해시 테이블(hash table) 등을 사용 ㄱㄴ
비트맵과 해시 테이블이 성능 좋으니 애용하라는 내용이다
페이지 테이블의 한계 보완위해 각 페이지에 추가 데이터 제공한다
이를 보조 페이지 테이블(SPT)이라 부른다
이것도 페이지 테이블로 부르지만 헷갈리니 보조 붙여주자
SPT는 두 가지 목적에 쓰인다
페이지 폴트 발생 시
프로세스 종료 시
마음대로 만들면 되고 최소 두 가지 접근이 있다
세그먼트 기반
연속 페이지 묶음 단위로 관리
페이지 기반
개별 페이지 단위로 관리
추가로, 원한다면 페이지 테이블 자체를 SPT 추적에 활용할 수도 있다고 한다
이를 위해서는 threads/mmu.c
의 Pintos 페이지 테이블 구현 수정해야해서
고급 학습자에게만 권장한단다
할 건 아니지만 무슨 말인지 궁금하니 가볍게 뭔말인지만 알아보면
따로 보조 자료구조를 두는 대신 기존의 페이지 테이블 엔트리(PTE) 안에 SPT의 메타데이터를 넣는 방식이라고 한다
즉, 페이지테이블에 SPT도 같이 쓰는 방식이다
어떻게 하는지까지는…
나중에 다뤄보겠다
페이지 폴트 버그처리는 프로젝트 2까지만이다
프로젝트 3부터는 파일/스왑에서 페이지를 가져와야 함이라는 뜻일 수도 있다
그렇기에 userprog/exception.c
의 page_fault()
가 호출하는 vm_try_handle_fault()
(in vm/vm.c
) 를 구현해야 한다
수행해야하는 목록 ↓
SPT에서 폴트가 난 가상 페이지를 조회
유효한 접근 일 시에 해당 페이지 데이터가 파일 시스템, 스왑 슬롯, 제로 페이지인지 판단
공유(CoW 등)를 구현했다면, 프레임에 있어도 PTE가 안 잡혀 있을 수 있음
SPT가 이상한 곳(커널 가상 메모리 범위, 읽기 전용 페이지에 쓰기)에 오면 무효 처리하고 프로세스 종료 후 자원해제
프레임 확보
데이터를 프레임으로
파일 시스템/스왑에서 읽거나, 제로로 채우는 등
공유 구현해서 프레임에서 가져온거라면 추가 작업 불필요할 수 있다
해당 가상 주소의 PTE를 프레임으로 매핑
threads/mmu.c
의 함수 사용프레임 테이블은 각 물리 프레임에 대해 하나의 엔트리를 가진다
각 엔트리에는 현재 그 프레임을 차지하는 페이지의 포인터(있다면) 및 선택한 부가 데이터가 포함되어 있다
프레임 테이블은 eviction 정책 구현에 사용된다 (사용 가능한 프레임이 없을 때 축출 대상 페이지를 고르는 등)
사용자 페이지에 쓰일 프레임은 반드시 "user pool"
에서 palloc_get_page(PAL_USER)
로 얻어야 한다
이렇게 안얻으면 "kernel pool"
에서 할당되어 테스트 케이스 실패 할 수 있다
가장 중요한 연산은 미사용 프레임 확보다
여유 프레임이 있으면 쉽지만, 없다면 축출(evict) 로 프레임을 비워야 한다
축출 시 스왑 슬롯 할당 없이는 축출할 수 있는 프레임이 하나도 없고, 스왑도 가득 찼다면 커널 패닉 일으키면 된다
축출 과정 ↓
페이지 교체 알고리즘으로 축출할 프레임 선택
해당 프레임을 참조하는 모든 페이지 테이블의 참조 제거
필요 시, 페이지를 파일 시스템이나 스왑에 기록
페이지 교체 알고리즘 구현 위한 각 PTE의 두 비트다
accessed 비트: 페이지에 읽기/쓰기가 발생하면 CPU가 1로 설정
dirty 비트: 페이지에 쓰기가 발생하면 CPU가 1로 설정
이는 OS가 필요에 따라 0으로 재설정한다
무슨 alias 문제를 조심하란다
서로 다른 가상주소가 같은 물리 프레임 가리키는 상황을 의미한다
A/D 비트(Accessed/Dirty)는 접근에 사용된 그 PTE에만 반영되는데 그로 인해
동일 프레임이라도 어느 PTE를 통해 접근했느냐에 따라 A/D 비트가 불일치 할 수 있다
Pintos에서는 모든 사용자 가상 페이지가 해당 커널 가상 페이지와 에일리어스라서 이를 잘 처리해줘야 한다
방법은 여럿 있을거다
예를 들어, 두 주소의 accessed/dirty 비트를 모두 확인/업데이트하거나, 커널이 사용자 데이터를 접근할 때는 사용자 가상 주소만 사용하도록 하거나 하여 말이다
그 외 에일리어스는 공유 구현 또는 버그가 있는 경우에만 발생해야 한다
자세한 함수는…
여기서 참고 해라
스왑 테이블은 스왑 슬롯의 사용/여유를 추적한다
스왑으로 내보낼 떄 사용할 미사용 스왑 슬롯 선택
페이지가 프레임으로 돌아오거나 해당 프로세스 종료될 때 스왑 슬롯 해제
를 지원해야 한다
vm/build
디렉터리에서 스왑 파티션이 포함된 디스크를 이 명령어로 만들 수 있다
pintos-mkdisk swap.dsk --swap-size=n
이후 swap.dsk
는 pintos 실행 시 추가 디스크로 자동 부착
아니면 임시 nMB 스왑 디스크 사용할 수 있는데 --swap-size=n
을 pintos 실행 인자에 주면 된다고 한다
그래서 이게 뭐하는 거야 하면 페이지 스왑 할때 사용할 임시 메모리 저장소 만드는 거라고 보면 된다
스왑 슬롯은 지연 할당(lazy allocation)해야 한다
미리 하지 말고 진짜 축출이 필요할 때만 할당 하라는 뜻이다
이후, 페이지 내용이 프레임으로 다시 읽혀오면 스왑 슬롯 해제하면 된다
파일 시스템은 일반적으로 read
/write
시스템 콜로 접근한다
그치만 보조 인터페이스로는 파일을 mmap
시스템 콜을 통해 가상 페이지에 매핑 가능하다
read
/write
로 접근할 경우 “파일 → 커널 버퍼 → 사용자 버퍼”로 복사 2번을 해야 하지만
mmap
로 접근할 경우 파일을 가상 페이지간주하고 페이지 폴트가 I/O를 트리거
그 이후는 포인터로 바로 접근한다
접근해서 쓰고 난 뒤에서는 munmap
으로 해제 해줘야 한다
구현관점에서 중요한건
메모리 매핑된 파일이 사용하는 메모리를 추적할 수 있어야 한다는 것이다
SPT/프레임 테이블/스왑 테이블을 설계·구현해야 한다
페이지 폴트 핸들러(vm_try_handle_fault
)가 SPT 기반으로 lazy 하게 페이지를 적재하고, 필요 시 eviction + swap을 수행하도록 한다
사용자 페이지 프레임은 반드시 user pool에서 PAL_USER
로 할당한다
accessed/dirty 비트와 에일리어스 문제를 올바르게 처리해야 한다
mmap
/munmap
으로 파일 매핑을 지원하며, 중첩/겹침 방지와 페이지 폴트 처리가 가능해야 한다