[Project 3] Virtual Memory (2)


Uninitialized page.


pintos仮想メモリのpageタイプは3種類あります.
  • Uninit type
  • Anonymous type
  • File-backed type
  • anonymousとfile backdの名前からいくつかの特性と用途を推定することができますが、unnitタイプは容易に考えられません.
    どんな役を演じるかを理解するにはPintosLazy loadingの大図を理解する必要があります.

    Project 2への移行中、User Processを実行するために必要なすべてのデータは、実行前にkernel threadから設定フェーズの物理メモリにアップロードされます.また、pml 4の役割は、アップロードデータを指すアドレスと、ユーザへのページアドレスとをマッピングすることである.(install_page function)
    ただし、実行に必要なすべてのデータを最初に物理メモリに配置するのではなく、必要に応じてpage fault handlerを使用してディスクから物理メモリにデータを昇格させることができます.ユーザに渡されるページタイプによって、後続の初期化関数と物理メモリにアップロードされるswap in関数は異なります.これらのページの準備過程は統一ページ段階であると考えられる.

    すべてのタイプでよく見るよりも、最初にUninitTypeでユーザーに投げつけられたページがどのような形で現れ、どのような内容が含まれているかの関数を見てみましょう.

    最初の実行時にユーザーに投げ出された仮想ページについて、

    bool vm_alloc_page_with_initializer (enum vm_type type, void *upage, bool writable, vm_initializer *init, void *aux) {
    
    	ASSERT (VM_TYPE(type) != VM_UNINIT)
    
    	struct supplemental_page_table *spt = &thread_current ()->spt;
    
    	/* Check wheter the upage is already occupied or not. */
    	if (spt_find_page (spt, upage) == NULL) {
    		/* TODO: Create the page, fetch the initialier according to the VM type,
    		 * TODO: and then create "uninit" page struct by calling uninit_new. You
    		 * TODO: should modify the field after calling the uninit_new. */
    		
    		struct page* page = (struct page*)malloc(sizeof(struct page));
    
            typedef bool (*initializerFunc)(struct page *, enum vm_type, void *);
            initializerFunc initializer = NULL;
    
            switch(VM_TYPE(type)) {
                case VM_ANON:
                    initializer = anon_initializer;
                    break;
                case VM_FILE:
                    initializer = file_backed_initializer;
                    break;
    		}
    
            uninit_new(page, upage, init, type, aux, initializer);
    
            // page member 초기화
            page->writable = writable;
            // hex_dump(page->va, page->va, PGSIZE, true);
    
    		/* TODO: Insert the page into the spt. */
    		return spt_insert_page(spt, page);
    	}
    err:
    	return false;
    }
    上の関数はpageを作成し、sptにないことを確認します.現在はuninittypeですが、以降のタイプをパラメータとして受信し、ページのstruct memberを入れます.
    つまり、この関数の終了時に生成されるページはUninit typeになりますが、ユーザーがアクセス権を持っている場合はpageエラーが発生し、その後に追加されたpage typeに基づいてinitialize関数が実行されます.
    このように生成されたページは、vm−claim−page関数を介してマッピングを要求する.イニシエータ関数にSPTを加えた.マッピング中、vm-do-claim-pageは物理フレームと仮想ページを決定的にマッピングし、直接接続し、swap in関数によってディスク(swap space)から物理メモリに昇格させる.
    ファイルバックアップページでは、lazy-load-segment関数を使用してのみ、実行可能なファイルに関連するデータをディスクから物理メモリにアップロードできます.
    整理すると、vm-claim-pageは、ユーザが転送する仮想ページに物理フレームとマッピングがない場合に使用される関数であり、lazy-load-segmentは、物理フレームにディスクからファイル関連データ(swap-in)をロードする関数である.
    これらは、ユーザー・プログラムのページ・ポーリングに由来します.

    Page fault handling.


    PagePaultではいったい何をして、それらの仕事をすることができますか?
    vm-try-handle-faultです
    bool vm_try_handle_fault (struct intr_frame *f UNUSED, void *addr UNUSED,
    		bool user UNUSED, bool write UNUSED, bool not_present UNUSED) {
    	struct supplemental_page_table *spt UNUSED = &thread_current ()->spt;
    	// struct page *page = NULL;
    	/* TODO: Validate the fault */
    	/* TODO: Your code goes here */
    	if (is_kernel_vaddr(addr)) {
            return false;
    	}
    
        void *rsp_stack = is_kernel_vaddr(f->rsp) ? thread_current()->rsp_stack : f->rsp;
        if (not_present){
            if (!vm_claim_page(addr)) {
                if (rsp_stack - 8 <= addr && USER_STACK - 0x100000 <= addr && addr <= USER_STACK) {
                    vm_stack_growth(thread_current()->stack_bottom - PGSIZE);
                    return true;
                }
                return false;
            }
            else
                return true;
        }
        return false;
    }
    関数で確認できるinitializer関数はありません.ユーザーはUninit typeのページ(初期実行時)を受信し、アクセス時にpage faultが発生したためです.したがって、vm-claim-page(=>仮想ページと物理フレームのマッピング)から実行されるコードが表示されます.
    ここまでは上図の4番の手順に相当します.

    その後の第5のプロセスは、lazy−load−seagementまたはanon−swap−inなどの各ページタイプに対する(File back type pageを知りたいなら)関数によって行われる.
    bool lazy_load_segment (struct page *page, void *aux) {
    	/* TODO: Load the segment from the file */
    	/* TODO: This called when the first page fault occurs on address VA. */
    	/* TODO: VA is available when calling this function. */
    	struct file *file = ((struct container *)aux)->file;
    	off_t offsetof = ((struct container *)aux)->offset;
    	size_t page_read_bytes = ((struct container *)aux)->page_read_bytes;
        size_t page_zero_bytes = PGSIZE - page_read_bytes;
    
    	file_seek(file, offsetof);
    
        if (file_read(file, page->frame->kva, page_read_bytes) != (int)page_read_bytes) {
    		palloc_free_page(page->frame->kva);
            return false;
        }
        memset(page->frame->kva + page_read_bytes, 0, page_zero_bytes);
    
        return true;
    }
    これにより、実行に必要なデータを物理フレームにアップロードし、フレームと仮想ページをマッピングした後、page faultが最後に発生したコマンドからユーザを再実行させることができる.