jun-wiki

View My GitHub Profile

Posts (Latest 10 updated) :
Read all
Contents:
  1. 익명 페이지 (Anonymous Page)
    1. 지연 로딩을 통한 페이지 초기화 (Page Initialization with Lazy Loading)
    2. 실행 파일을 위한 지연 로딩 (Lazy Loading for Executable)
    3. vm_alloc_page_with_initializer() 구현
    4. load_segment와 lazy_load_segment 구현 (userprog/process.c)
    5. 스택 설정 조정 (userprog/process.c의 setup_stack)
    6. vm_try_handle_fault 수정
    7. 보조 페이지 테이블 — 재방문 (Supplemental Page Table - Revisit)
    8. 페이지 정리 (Page Cleanup)

익명 페이지 (Anonymous Page)

익명 페이지 구현에 대해 다룬다

익명 페이지는 파일로부터 매핑된게 아닌 커널로부터 프로세스에게 할당된 메모리 페이지를 말한다

익명 매핑은 백업 파일이나 장치가 없다

익평 페이지는 스택과 힙처럼 실행 파일에서 사용된다

간단히 말하자면 커널에서 관리하는 유저 영역 메모리 페이지다

include/vm/anon.h를 보면 익명 페이지를 설명하는 구조체 anon_page가 존재한다

지금은 비어있는데 필요한 정보다 상태 저장할 멤버들 알아서 추가하라고 한다


지연 로딩을 통한 페이지 초기화 (Page Initialization with Lazy Loading)

지연 로딩은 메모리 로딩 실제 필요할때까지 지연하는 설계말한다
페이지만 할당하고 전용 물리 프레임도 실제 내용도 없다
내용은 실제로 필요해지는 시점, 페이지 폴트가 신호보내면 그때 로드된다

페이지 타입 세 가지이므로 초기화 루틴은 각 페이지마다 다르다

페이지 초기화 흐름의 상위 수준 관점에서 보자면

  • 커널이 새로운 페이지 요청을 받으면 vm_alloc_page_with_initializer가 호출

    • 초기화 함수는 페이지 구조체 할당

    • 초기화 함수는 페이지 타입에 따라 적절한 초기화를 설정해 새 페이지 초기화

    • 사용자 프로그램에 제어 반환

  • 사용자 프로그램이 실행되는 동안 내용 없는 페이지에 접근 시 페이지 폴트 방새아

    • 페이지 폴트 처리 절차동안 uninit_initialize 호출

    • 이전에 설정해둔 초기화 함수 호출

      • 익명 페이지: anon_initializer

      • 파일 기반 페이지: file_backed_initializer


페이지의 생명주기를 이를 통해 알 수 있다

페이지 생명주기:
initialize
-> (page_fault -> lazy-load -> swap-in -> swap-out -> ...)
-> destroy


실행 파일을 위한 지연 로딩 (Lazy Loading for Executable)

지연 로딩, Lazy Loading에서는 프로세스에서 즉시 필요한 메모리 부분만 주 메모리에 로드된다

당연히 모든 바이너리 이미지 한 번에 메모리에 로드하는 것보다 오버헤드 줄일 수 있다

지연 로딩을 위해, include/vm/vm.hVM_UNINIT이라는 페이지 타입을 도입한다

모든 페이지는 처음에 VM_UNINIT이라는 페이지로 생성되고 초기화되지 않은 페이지를 위한 페이지 구조체 struct uninit_pageinclude/vm/uninit.h에 제공한다

초기화되지 않은 페이지를 생성, 초기화, 파괴하는 함수들은 include/vm/uninit.h에 있다

아직 미완성이라 우리가 만들어야 한다

페이지 폴트 발생시, 페이지 폴트 핸들러(userprog/exception.cpage_fault)가 vm/vm.c의 vm_try_handle_fault로 제어를 넘긴다

vm_try_handle_fault에서 유효한 페이지 폴트인지부터 판단한다

유효하지 않다면 일부 내용 로드 후, 사용자 프로그램에 제어를 반환한다

잘못된 페이지 폴트의 경우

  1. 지연 로딩된 페이지

  2. 스왑 아웃된 페이지

  3. 쓰기 보호된 페이지

지금 우리가 고려해야 할것은 지연 로딩된 페이지

만약 지연 로딩을 위한 페이지 폴트라면, 커널은 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.hVM_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.cvm_anon_initanon_initializer도 필요에 따라 수정할 수 있다

void vm_anon_init (void);

익명 페이지 서브시스템을 초기화한다


bool anon_initializer (struct page *page,enum vm_type type, void *kva);

여기서 익명 페이지와 관련된 거 설정할 수 있다


load_segmentlazy_load_segment 구현 (userprog/process.c)

실행 파일로부터 세그먼트 로딩 구현해야 한다

모두 지연 로딩이어야 하며 커널이 페이지 폴트 가로챌 때만 로드된다

프로그램 로더의 핵심인 userprog/process.cload_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_segmentload_segment에서 vm_alloc_page_with_initializer의 네 번째 인자로 제공된다

이 함수는 실행 파일의 페이지를 위한 초기화 함수로 페이지 폴트 시점에 호출된다

page구조체와 aux를 인자로 받는다

aux는 load_segment에서 설정한 정보다

이를 통해 세그먼트를 읽어올 파일을 찾고 세그먼트를 메모리에 읽어들여야 한다

얘가 실행하는 함수다
폴트 났을 때 aux에 든 정보로 파일 데이터 채우고 매핑 (남는 부분은 0으로 채워둠)


스택 설정 조정 (userprog/process.c의 setup_stack)

메모리 관리 시스템에 맞게 스택 할당 조정해야 한다

첫번째 스택 페이지는 지연 로딩 필요 없다

로드 시점에 즉시 할당 및 초기화 가능해 폴트 기다릴 필요가 없으니 말이다

스택 식별 방법이 필요할 수도 있는데 vm/vm.hvm_type에 있는 보조 마커 (VM_MARKER_0 등)를 사용할 수 있다


vm_try_handle_fault 수정

보조 페이지 테이블을 통해 spt_find_page로 폴트가 발생한 주소에 해당하는 페이지 구조체를 해결 하도록 vm_try_handle_fault를 수정해야 한다

여기까지 구현 완료시 project 2의 모든 테스트(fork 제외)가 통과되어야 한다


보조 페이지 테이블 — 재방문 (Supplemental Page Table - Revisit)

생성 또는 프로세스 파괴 시 필요한 복사와 정리 연산을 지원하기 위해 보조 페이지 테이블 인터페이스를 다시 살펴야 한다

참고로 인터페이스는 API의미한다 보조 페이지 테이블 함수 묶음이란 말이다

vm/cm.csupplemental_page_table_copysupplemental_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으로 할당된 메모리)는 호출자가 보조 페이지 테이블 정리 후 알아서 정리한다


페이지 정리 (Page Cleanup)

vm/uninit.cuninit_destroyvm/anon.canon_destroy를 구현해야 한다

초기화 안된 페이지의 destroy 연산 핸들러다

초기화 안된 페이지가 다른 페이지 객체로 변환돼도 프로세스가 종료될 때 uninit페이지가 남아 있을 수 있다

  • 다른 페이지 객체로 변환되는 예시:

    1. 첫 접근 시 (page fault)

      • 익명 메모미 VM_ANON으로 전환

      • 파일 기반 VM_FILE로 전환

    2. fork 시 SPT 복사에서 즉시 claim 시

      • 사본 만들 떄 자식 SPT에 UNINIT로 넣고 ANON/FILE로 전환
    3. mmap 생성 직후의 지연 로딩 경로

      • mmap된 범위 UNINIT로 등록 되고 1) 흐름으로 FILE로 전환


static void uninit_destroy (struct page *page);

페이지 구조체가 보유한 리소스 해제하며 페이지의 vm 타입 확인하고 그에 따라 처리한다

나중에 기능 추가하지만 지금은 익명 페이지까지만 처리 가능


static void anon_destroy (struct page *page);

익명 페이지가 보유한 리소스 해제한다


페이지 구조체 자체를 명시적으로 해제할 필요는 없다

호출자가 이를 수행한다 (free())


여기까지 하면 프로젝트 2의 모든 테스트가 통과되어야 한다