비디스크 기반 이미지를 구현하는 페이지
주로 스택이나 힙과 같은 용도로 사용
커널 공간과 유저 공간, 물리 메모리를 나타낸 그림
(지연 로딩을 사용한 페이지 초기화)
Lazy loading
메모리 로딩을 필요한 시점까지 지연시키는 디자인
페이지(page)는 할당되어 있지만, 해당 페이지에 대응하는 전용 물리 프레임이 존재하지 않고 페이지의 실제내용도 아직 로드되지 않는 상태
페이지 초기화 과정(initialize 과정)
사용자 프로그램이 실행되는 동안 특정 시점에서 페이지 폴트(page fault)가 발생
→ ❓발생하는 이유 : 프로그램이 해당 페이지에 접근하려고 할 때 페이지가 아직 내용이 없기 때문
anon_initializer
→ (1) uninit_initialize 함수가 호출된다.
→ (2) uninit_initialize 함수는 anon_initializer를 호출한다.
→ (3) anon_initializer는 필요한 작업을 수행하여 익명 페이지의 내용을 로드하고 초기화합니다. 이 작업은 필요에 따라 페이지 스왑 인 등의 추가 절차를 수행한다.
file_backed_initializer
→ (1) uninit_initialize 함수가 호출된다.
→ (2) uninit_initialize 함수는 file_backed_initializer를 호출한다.
→ (3) file_backed_initializer는 필요한 작업을 수행하여 파일을 지원하는 페이지의 내용을 로드하고 초기화한다. 이때 파일에서 데이터를 읽어오거나 메모리 매핑 등의 추가 절차를 수행할 수도 있다.
page의 수명 주기
초기화(initialize) -> (페이지 폴트(page fault) -> 레이지 로딩(lazy-load) -> 스왑 인(swap-in) -> 스왑 아웃(swap-out) -> ...) -> 소멸(destroy)
1. initialize(초기화) : 페이지의 구조를 설정하고 초기화하는 단계
2. page fault(페이지 폴트) : 페이지에 대한 액세스가 발생하면 페이지 폴트가 발생
3. lazy-loading : 페이지 폴트 처리 과정에서 페이지 내용을 필요한 시점에 로드하는 단계
4. swap-in(스왑 인)-물리메모리에 넣는 것 : 필요한 페이지가 스왑 영역에 저장되어 있다면 해당 페이지를 물리 메모리로 로드
5. swap-out(스왑 아웃)-물리 메모리에서 빼는 것 : 필요한 페이지가 물리 메모리에서 스왑 영역으로 옮겨질 때 발생하는 단계
6. destroy(소멸) : 페이지를 해제하고 관련 자원을 정리
프로젝트 2까지는 세그먼트를 물리 프레임에 직접 load하는 방법이였다. 보면 맨 처음에 물리 프레임부터 할당을 받고 파일을 해당 프레임에 load한 다음, 페이지 테이블에서 가상 주소와 물리 주소를 맵핑하는 방식이다. 그래서 프로젝트 2까지 page fault는 커널이나 유저 프로그램에서 나타나는 버그였다.
프로젝트 3부터는 spt에 필요한 정보들을 넣어서 page fault가 발생했을 때(페이지가 요청됐을 떄)가 되어서 메모리에 load하는 lazy load 방식으로 변경한다.
🤔 유효한 페이지 폴트란?
메모리에 올려져 있지 않은 페이지에 접근할 때
→ 해당 페이지의 데이터가 현재 메모리에 로드되어 있지 않은 상태를 의미하며,SPT에는 이러한 페이지의 매핑 정보가 저장되어 있다.
🙄그렇다면 유효한 페이지를 접근했을 때에도 페이지 폴트가 뜨는 경우는?
Bogus Page Falut 라고 부르며 이 경우에는 페이지에 세부 정보들을 load하고 다시 유저 프로그램에게 제어권을 양보한다.
Bogus page fault의 오류 3가지
: lazy loading을 사용하면 eager loading에 비해 오버헤드를 줄일 수 있다.
vm_alloc_page_with_initializer
주어진 타입의 초기화되지 않은 페이지를 생성
(1) 초기화 되지 않은 페이지를 생성
(2) 생성된 페이지의 swap_in 핸들러가 호출된다. 이 핸들러는 페이지를 초기화하고 주어진 aux와 함께 init을 호출하여 페이지를 초기 상태로 설정
→ swap_in 핸들러는 페이지 폴트 핸들러의 일부로 해당 페이지를 메모리로 로드하는 역할을 한다. 이때 swap_in 함수 내에서 uninit_initialize 함수에 도달하는데 이 부분을 수정해야 할수도 있다.
(3) 페이지 구조체를 프로세스의 보조 페이지 테이블에 삽입해서 페이지의 가상주소와 물리 주소간의 매핑이 이루어짐
🤔 여기서 말하는 aux는 어떤 기능일까?
해당 페이지의 초기화에 필요한 추가적인 정보를 전달하는데 사용
uninit_initialize
→ 기본 코드는 먼저 vm_initializer과 aux를 가져와 함수 포인터를 통해 page_initializer를 호출할 수 있음
vm_anon_init
익명 페이지와 관련된 모든 것을 설정할 수 있음
anon_initializer
page-> operations에서 익명 페이지에 대한 핸들러를 설정
userprog/process.c에서 load_segment 및 lazy_load_segment를 구현합니다. 실행 파일에서 세그먼트 로딩을 구현합니다. 이러한 모든 페이지는 커널이 페이지 오류를 인터셉트할 때만 느리게 로드되어야 합니다.
프로그램 로더의 핵심인 userprog/process.c의 load_segment에 있는 루프를 수정해야 합니다. 이 루프는 매번 vm_alloc_page_with_initializer를 호출하여 보류 중인 페이지 객체를 생성합니다. 페이지 오류가 발생하면 파일에서 세그먼트가 실제로 로드되는 시점입니다.
load_segment
파일에서 읽을 바이트 수와 메인 루프 내에서 0으로 채울 바이트 수를 계산
실행 파일로부터 세그먼트를 읽어와서 가상 주소 공간에 로드하는 기능을 수행
현재 코드는 파일에서 읽을 바이트 수와 메인 루프 내에서 0으로 채울 바이트 수를 계산합니다. 그런 다음 vm_alloc_page_with_initializer를 호출하여 보류 중인 객체를 생성합니다. vm_alloc_page_with_initializer에 제공할 보조 값을 보조 인수로 설정해야 합니다. 바이너리 로딩에 필요한 정보를 포함하는 구조를 생성할 수 있습니다.
lazy_load_segment
실행 파일의 페이지를 초기화하는 함수이며 페이지 오류가 발생할 때 호출
이때 페이지 구조체와 aux를 인자로 받아오는데 aux는 load_segment에서 설정한 정보이다.
스택 할당을 새 메모리 관리 시스템에 맞도록 userprog/process.c의 setupstack을 조정해야 합니다. 첫 번째 스택 페이지는 느리게 할당할 필요가 없습니다. 로드 시점에 명령줄 인수를 사용하여 할당하고 초기화할 수 있으므로 결함이 발생할 때까지 기다릴 필요가 없습니다. 스택을 식별하는 방법을 제공해야 할 수도 있습니다. vm/vm.h의 vm 유형에 있는 보조 마커(예: VM_MARKER_0)를 사용하여 페이지를 표시할 수 있습니다.
마지막으로, vm_try_handle_fault 함수를 수정하여 오류 주소에 해당하는 페이지 구조를 spt_find_page를 통해 보조 페이지 테이블을 참조하여 해결합니다.
모든 요구 사항을 구현한 후 포크를 제외한 프로젝트 2의 모든 테스트를 통과해야 합니다.
복사 및 정리 작업을 지원
프로세스를 생성하거나 삭제할 때 필요함, 이때 supplemental page table을 다시 살펴보는 이유는 구현한 초기화 함수 중 일부를 사용하고 싶을 수 있기 때문
supplemental_page_table_copy
자식이 부모의 실행 컨텍스트를 상속해야 할 때 사용되는데 src에서 dst로 복사됨
supplemental_page_table_kill
supplemental_page_table이 보유하고 있는 모든 리소스를 해제
프로세스가 종료될 떄 호출됨
초기화 되지 않은 페이지의 소명 작업을 위한 핸들러
uninit_destroy
페이지 구조체가 보유하던 리소스를 해제
anon_destroy
익명 페이지가 보유하고 있던 리소스를 해제
💯💯여기서 project2를 실행했을 때 올 패스가 나와야 함💯💯
프로젝트 2에서 스택은 USER_STACK에서 시작하는 단일 페이지였으며, 프로그램 실행은 이 크기로 제한되었습니다. 이제 스택이 현재 크기 이상으로 커지면 필요에 따라 추가 페이지를 할당합니다.
vm_try_handle_fault
페이지 오류가 스택 증가에 유효한 경우인지 여부를 확인
vm_stack_growth
추가 주소가 더 이상 결함이 있는 주소가 되지 않도록 하나 이상의 익명 페이지를 할당하여 스택 크기를 늘림
매핑되지 않은 페이지에 접근했으므로 페이지 폴트가 발생하는데 이때 크게 두가지 케이스가 있다.
1. 스택 포인터를 어떻게 가져올 것인지
1-1. 만약 인터럽트 프레임의 RSP 멤버가 유저 스택을 가리키고 있다면 해당 스택 포인터를 그대로 사용
1-2. 인터럽트 프레임의 RSP 멤버가 커널 스택을 가리키고 있다면 커널모드로 전환되었을 때 스레드 구조체에 저장해놓고 있던 RSP를 사용
2. 이 페이지 폴트가 stack growth에 대한 페이지 폴트인지
페이지 폴트가 실제로 SPT에 페이지가 없어서 일어난 유효 페이지 폴트인지 확인
2-1. 실제로 페이지가 존재하지 않는 주소에 접근한 경우
2-2. 페이지가 존재하지 않는 주소에 접근한 것은 맞지만, 주소가 stack에 데이터를 넣으려다 stack 바깥으로 삐져나와 존재하지 않는 페이지에 접근한 경우
2번째 경우가 stack growth에 대한 페이지 폴트 이다.
스택 증가 테이스 케이스가 통과되어야 함