익명 페이지 구현에 대해 다룬다
익명 페이지는 파일로부터 매핑된게 아닌 커널로부터 프로세스에게 할당된 메모리 페이지를 말한다
익명 매핑은 백업 파일이나 장치가 없다
익평 페이지는 스택과 힙처럼 실행 파일에서 사용된다
간단히 말하자면 커널에서 관리하는 유저 영역 메모리 페이지다
include/vm/anon.h
를 보면 익명 페이지를 설명하는 구조체 anon_page
가 존재한다
지금은 비어있는데 필요한 정보다 상태 저장할 멤버들 알아서 추가하라고 한다
지연 로딩은 메모리 로딩 실제 필요할때까지 지연하는 설계말한다
페이지만 할당하고 전용 물리 프레임도 실제 내용도 없다
내용은 실제로 필요해지는 시점, 페이지 폴트가 신호보내면 그때 로드된다
페이지 타입 세 가지이므로 초기화 루틴은 각 페이지마다 다르다
페이지 초기화 흐름의 상위 수준 관점에서 보자면
커널이 새로운 페이지 요청을 받으면 vm_alloc_page_with_initializer
가 호출
초기화 함수는 페이지 구조체 할당
초기화 함수는 페이지 타입에 따라 적절한 초기화를 설정해 새 페이지 초기화
사용자 프로그램에 제어 반환
사용자 프로그램이 실행되는 동안 내용 없는 페이지에 접근 시 페이지 폴트 방새아
페이지 폴트 처리 절차동안 uninit_initialize
호출
이전에 설정해둔 초기화 함수 호출
익명 페이지:
anon_initializer
파일 기반 페이지:
file_backed_initializer
페이지의 생명주기를 이를 통해 알 수 있다
페이지 생명주기:
initialize
-> (page_fault -> lazy-load -> swap-in -> swap-out -> ...)
-> destroy
지연 로딩, Lazy Loading에서는 프로세스에서 즉시 필요한 메모리 부분만 주 메모리에 로드된다
당연히 모든 바이너리 이미지 한 번에 메모리에 로드하는 것보다 오버헤드 줄일 수 있다
지연 로딩을 위해, include/vm/vm.h
에 VM_UNINIT
이라는 페이지 타입을 도입한다
모든 페이지는 처음에 VM_UNINIT
이라는 페이지로 생성되고 초기화되지 않은 페이지를 위한 페이지 구조체 struct uninit_page
를 include/vm/uninit.h
에 제공한다
초기화되지 않은 페이지를 생성, 초기화, 파괴하는 함수들은 include/vm/uninit.h
에 있다
아직 미완성이라 우리가 만들어야 한다
페이지 폴트 발생시, 페이지 폴트 핸들러(userprog/exception.c
의 page_fault
)가 vm/vm.
c의 vm_try_handle_fault
로 제어를 넘긴다
vm_try_handle_fault
에서 유효한 페이지 폴트인지부터 판단한다
유효하지 않다면 일부 내용 로드 후, 사용자 프로그램에 제어를 반환한다
잘못된 페이지 폴트의 경우
지연 로딩된 페이지
스왑 아웃된 페이지
쓰기 보호된 페이지
지금 우리가 고려해야 할것은 지연 로딩된 페이지다
만약 지연 로딩을 위한 페이지 폴트라면, 커널은 vm_alloc_page_with_initializer
에서 이전에 설정해 둔 초기화 함수 중 하나를 호출하여 세그먼트를 지연 로딩한다
이를 위해 userprog/process.c
에서 lazy_load_segment
를 구현해야 한다
vm_alloc_page_with_initializer()
구현bool vm_alloc_page_with_initializer (enum vm_type type, void *va,
bool writable, vm_initializer *init, void *aux);
주어진 타입으로, 초기화되지 않은 페이지를 생성한다
uninit
페이지의 swap_in
핸들러는 타입에 따라 페이지를 자동으로 초기화하고, 주어진 AUX로 INIT를 호출한다
페이지 구조체를 얻고, 이를 프로세스의 보조 페이지 테이블에 삽입해야한다
vm.h
의 VM_TYPE
매크로가 유용하다
type
은 기본 타입 + 플래그라 VM_TYPE(type)
를 통해 기본 타입만 분리 가능
페이지 폴트 핸들러는 호출 체인을 따라가 swap_in
을 호출할 때 uninit_initialize
에 도달한다
이에 대한 구현은 되어있지만 경우에 따라 uninit_initialize
를 수정해야 할 수도 있다
static bool uninit_initialize (struct page *page, void *kva);
첫 폴트 시 페이지 초기화
주어진 코드에서는 먼저 vm_initializer
와 aux를 가져오고, 함수 포인터를 통해 해당 page_initializer
를 호출
vm/anonc.c
의 vm_anon_init
과 anon_initializer
도 필요에 따라 수정할 수 있다
void vm_anon_init (void);
익명 페이지 서브시스템을 초기화한다
bool anon_initializer (struct page *page,enum vm_type type, void *kva);
여기서 익명 페이지와 관련된 거 설정할 수 있다
load_segment
와 lazy_load_segment
구현 (userprog/process.c
)실행 파일로부터 세그먼트 로딩 구현해야 한다
모두 지연 로딩이어야 하며 커널이 페이지 폴트 가로챌 때만 로드된다
프로그램 로더의 핵심인 userprog/process.c
의 load_segment
루프를 수정해야 한다
루프 한 바퀴마다 vm_alloc_page_with_initializer
를 호출하여 대기 중(pending) 페이지 객체를 만드며, 페이지 폴트 발생 시 세그먼트가 파일에서 실제로 로드된다
static bool load_segment (struct file *file, off_t ofs, uint8_t *upage,
uint32_t read_bytes, uint32_t zero_bytes, bool writable);
제공 코드는 루프 내에서 파일에서 읽을 바이트 수와 0으로 채울 바이트 수 계산하고
vm_alloc_page_with_initializer
호출해 대기 객체 만든다
이때 vm_alloc_page_with_initializer
에 제공할 aux 인자로 사용할 보조 값을 설정해줘야 한다
요약하면 작업 예약하는 함수다
나중에 폴트 나면 읽을 정보 예약하는 함수 (각 페이지마다 불러올 정보 등록해둔다)
static bool lazy_load_segment (struct page *page, void *aux);
lazy_load_segment
는 load_segment
에서 vm_alloc_page_with_initializer
의 네 번째 인자로 제공된다
이 함수는 실행 파일의 페이지를 위한 초기화 함수로 페이지 폴트 시점에 호출된다
page
구조체와 aux를 인자로 받는다
aux는 load_segment
에서 설정한 정보다
이를 통해 세그먼트를 읽어올 파일을 찾고 세그먼트를 메모리에 읽어들여야 한다
얘가 실행하는 함수다
폴트 났을 때aux
에 든 정보로 파일 데이터 채우고 매핑 (남는 부분은 0으로 채워둠)
메모리 관리 시스템에 맞게 스택 할당 조정해야 한다
첫번째 스택 페이지는 지연 로딩 필요 없다
로드 시점에 즉시 할당 및 초기화 가능해 폴트 기다릴 필요가 없으니 말이다
스택 식별 방법이 필요할 수도 있는데 vm/vm.h
의 vm_type
에 있는
보조 마커 (VM_MARKER_0
등)를 사용할 수 있다
vm_try_handle_fault
수정보조 페이지 테이블을 통해 spt_find_page
로 폴트가 발생한 주소에 해당하는 페이지 구조체를 해결 하도록 vm_try_handle_fault
를 수정해야 한다
여기까지 구현 완료시 project 2의 모든 테스트(fork 제외)가 통과되어야 한다
생성 또는 프로세스 파괴 시 필요한 복사와 정리 연산을 지원하기 위해 보조 페이지 테이블 인터페이스를 다시 살펴야 한다
참고로 인터페이스는 API의미한다 보조 페이지 테이블 함수 묶음이란 말이다
vm/cm.c
에 supplemental_page_table_copy
와 supplemental_page_table_kill
을 구현해야 한다
bool supplemental_page_table_copy (struct supplemental_page_table *dst,
struct supplemental_page_table *src);
위 함수의 역할은 src
의 보조 페이지 테이블을 dst
로 복사한다
자식이 부모의 실행 컨텍스트 상속해야 할 때(fork()
) 사용된다
src
의 보조 페이지 테이블의 각 페이지를 순화하여 dst
의 보조 페이지 테이블에 항목을 정확히 복사하면 된다
uninit
페이지 할당 후 즉시 claim해야 한다
claim은 당연히 실체화 하는 거 말한다 (물리 프레임 배정, 매핑, 내용 채워넣기)
void supplemental_page_table_kill (struct supplemental_page_table *spt);
위 함수는 보조 페이지 테이블이 보유한 모든 리소스 해제한다
프로세스 종료될때(process_exit()
) 호출된다
페이지 엔트리를 순회하며 destroy(page)
를 호출해야 한다
실제 페이지 테이블(pml4)과 물리 메모리(palloc으로 할당된 메모리)는 호출자가 보조 페이지 테이블 정리 후 알아서 정리한다
vm/uninit.c
의 uninit_destroy
와 vm/anon.c
의 anon_destroy
를 구현해야 한다
초기화 안된 페이지의 destroy 연산 핸들러다
초기화 안된 페이지가 다른 페이지 객체로 변환돼도 프로세스가 종료될 때 uninit
페이지가 남아 있을 수 있다
다른 페이지 객체로 변환되는 예시:
첫 접근 시 (page fault)
익명 메모미 VM_ANON으로 전환
파일 기반 VM_FILE로 전환
fork 시 SPT 복사에서 즉시 claim 시
mmap 생성 직후의 지연 로딩 경로
mmap
된 범위 UNINIT로 등록 되고 1) 흐름으로 FILE로 전환static void uninit_destroy (struct page *page);
페이지 구조체가 보유한 리소스 해제하며 페이지의 vm 타입 확인하고 그에 따라 처리한다
나중에 기능 추가하지만 지금은 익명 페이지까지만 처리 가능
static void anon_destroy (struct page *page);
익명 페이지가 보유한 리소스 해제한다
페이지 구조체 자체를 명시적으로 해제할 필요는 없다
호출자가 이를 수행한다 (free()
)
여기까지 하면 프로젝트 2의 모든 테스트가 통과되어야 한다