ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [PintOS] WIL (4) 난 이해를 한 걸까..?
    SW 정글/운영체제 2024. 10. 22. 00:09

     

    이 글은 swap disk와 copy on write에 대해 무지성으로 작성되었습니다.


    Copy On Write

     


    현재까지는 fork할 때 메모리가 할당된 page에 대해서 부모에서 자식으로 복사할 때 같은 내용의 물리메모리를 자식에게도 할당 해주었다. 같은 메모리가 두 번 복사되기에 메모리 낭비로 볼 수도 있다.

    cow는 fork시에는 부모와 자식이 동일한 물리 메모리를 가리키게 하고

    해당 페이지에 write 요청이 발생해야 새로운 물리메모리를 할당해 주도록 수정한다.

     

    fork 시 물리메모리를 모두 복사하지 않고 부모와 같은 물리메모리를 공유하다가 write작업 시 해당 페이지의 물리메모리를 새로 맵핑한다.

    부모와 자식 프로세스는 쓰기 작업 이후에 서로 다른 메모리 프레임을 가지게 되어, 쓰기 작업이 서로에게 영향을 주지 않

     

    ** 페이지를 만들 때부터 쓰려고 하면 page fault가 나도록 해야 함

     


    여기부터는 수정이 있었던 파일 일부에 대해 간략히 메모하고 지나가겠다.

     

    1. thread.h 수정

    #ifdef VM
        /* Table for whole virtual memory owned by thread. */
        struct supplemental_page_table spt;
        void *rsp;
        void* stack_bottom;
    #endif
    • 각 스레드마다 보조 페이지 테이블(supplemental_page_table)을 추가하여 가상 메모리를 관리
    • rsp는 현재 스레드의 스택 포인터를 저장하며, 커널 모드와 유저 모드 전환 시 사용
    • stack_bottom은 현재 스레드의 스택의 바닥 주소를 나타내며, 스택 확장 시 사용

     

    2. anon.h

    #ifndef VM_ANON_H
    #define VM_ANON_H
    #include "vm/vm.h"
    
    #define SECTOR_SIZE 512 // 추가된 부분
    
    struct page;
    enum vm_type;
    
    struct anon_page {
        int bit_idx; // 추가된 부분: 스왑 공간에서의 비트맵 인덱스를 저장
    };
    
    void vm_anon_init (void);

     

    • SECTOR_SIZE를 정의하여 디스크 섹터의 크기를 지정
    • anon_page 구조체에 bit_idx를 추가하여 페이지가 스왑 디스크의 어느 위치에 저장되었는지 추적
    • 이는 스왑 인/아웃 시 해당 페이지의 위치를 알아내기 위해 필요

     

    3. file.h

    enum vm_type;
    
    struct file_page {
        struct file *file;
        off_t ofs;
        void *file_start_page;
        void *file_end_page;
        size_t page_read_bytes;
        size_t page_zero_bytes;
    };
    
    void vm_file_init (void);

     

    file_page 구조체를 정의하여 파일 기반 페이지에 필요한 정보를 저장

    • file: 해당 페이지가 속한 파일을 가리킨다.
    • ofs: 파일 내에서의 오프셋
    • file_start_page, file_end_page: 메모리 매핑된 파일의 시작과 끝 페이지 주소
    • page_read_bytes: 페이지에서 읽어야 할 바이트 수
    • page_zero_bytes: 페이지에서 0으로 채워야 할 바이트 수

     

    4. vm.h (1)

    struct frame {
        void *kva;
        struct page *page;
        struct list_elem frame_elem; // 추가된 부분: 프레임 테이블 관리를 위한 리스트 요소
    };
    
    struct page {
        struct frame *frame;   /* Back reference for frame */
    
        /* Your implementation */
        struct hash_elem page_elem; /* 해시 테이블 요소 */
        bool is_writable;
        int bit_idx; // 스왑 비트맵 인덱스
    
        union {
            struct uninit_page uninit;
            struct anon_page anon;
            struct file_page file;
        #ifdef EFILESYS
            struct page_cache page_cache;
        #endif
        };
    };

     

     

    • frame 구조체에 frame_elem을 추가하여 프레임 테이블을 리스트로 관리
    • page 구조체에 page_elem을 추가하여 보조 페이지 테이블에서 페이지를 해시 테이블로 관리
    • is_writable: 페이지의 쓰기 가능 여부를 나타냄
    • bit_idx: 스왑 공간에서의 인덱스를 저장하여 스왑 인/아웃 시 사용

     

    5. vm.h(2)

    // for aux
    struct load_info{
        struct file *file;
        size_t page_read_bytes;
        size_t page_zero_bytes;
        off_t ofs;
        bool writable;
        size_t file_start_page;
        size_t file_end_page;
    };

     

     

    • load_info 구조체를 정의하여 지연 로딩(lazy loading) 시 필요한 정보를 저장
    • aux로 전달되어 페이지를 초기화할 때 사용

     

     

    6. process.c (1)

    int process_wait(tid_t child_tid UNUSED)
    {
        struct thread *child = get_child_process(child_tid);
        if (child == NULL)
            return -1;
    
        sema_down(&child->wait_sema);
        list_remove(&child->child_elem);
        int result = child->exit_status;
        sema_up(&child->exit_sema);
    
        return result; // 변경된 부분: result를 반환
    }

     

    • process_wait 함수에서 child->exit_status 대신 result 변수를 반환하도록 수정
    • 이는 child->exit_status가 sema_up 이후에 변경될 수 있으므로, 미리 값을 저장해 두기 위함

     

     

    7. process.c (2)

    void process_exit(void)
    {
        struct thread *cur = thread_current();
    
        // FDT의 모든 파일을 닫는다.
        for (int i = 2; i < FDT_COUNT_LIMIT; i++) {
            process_close_file(i);
        }
        palloc_free_multiple(cur->fdt, FDT_PAGES);
    
        // 현재 실행 중인 파일도 닫는다.
        file_close(cur->running); 
        process_cleanup();
    
        // 부모에게 종료를 알린다.
        sema_up(&cur->wait_sema);
    
        // 부모의 signal을 기다린다.
        sema_down(&cur->exit_sema);
    
        // 추가된 부분: 보조 페이지 테이블 해시 제거
        hash_destroy(&cur->spt.pages, page_dealloc);
    }

     

    • 프로세스가 종료될 때 보조 페이지 테이블을 제거하여 메모리 누수를 방지
    • hash_destroy와 page_dealloc을 사용하여 페이지를 해제

    8. lazy_load_segment에서 file seek 추가 및 aux 사용

    struct load_info *info = (struct load_info *)aux;
    uint8_t *kpage = page->frame->kva;
    
    file_seek(info->file, info->ofs);
    if (file_read(info->file, kpage, info->page_read_bytes) != (int)info->page_read_bytes){
        palloc_free_page(kpage);
        free(aux);
        return false;
    }
    memset(kpage + info->page_read_bytes, 0, info->page_zero_bytes);
    
    free(aux);
    
    return true;

     

     

    • lazy_load_segment 함수에서 file_seek을 사용하여 파일의 정확한 위치에서 읽음
    • aux로 전달된 load_info 구조체를 사용하여 필요한 정보를 가져옴
    • 페이지를 메모리에 로드한 후 aux를 해제

     

    9. load_segment 함수 수정

    bool load_segment(struct file *file, off_t ofs, uint8_t *upage,
                     uint32_t read_bytes, uint32_t zero_bytes, bool writable)
    {
        ASSERT((read_bytes + zero_bytes) % PGSIZE == 0);
        ASSERT(pg_ofs(upage) == 0);
        ASSERT(ofs % PGSIZE == 0);
    
        file_seek (file, ofs);
        while (read_bytes > 0 || zero_bytes > 0)
        {
            size_t page_read_bytes = read_bytes < PGSIZE ? read_bytes : PGSIZE;
            size_t page_zero_bytes = PGSIZE - page_read_bytes;
    
            struct load_info *aux = (struct load_info  *)malloc(sizeof(struct load_info));
            if(aux == NULL)	
                return false;
            memset(aux, 0, sizeof(struct load_info));
            aux->file = file;
            aux->page_read_bytes = page_read_bytes;
            aux->page_zero_bytes = page_zero_bytes;
            aux->writable = writable;
            aux->ofs = ofs;
    
            if (!vm_alloc_page_with_initializer(VM_ANON, upage, writable, lazy_load_segment, aux))
                return false;
    
            read_bytes -= page_read_bytes;
            zero_bytes -= page_zero_bytes;
            upage += PGSIZE;
            ofs += page_read_bytes;
        }
        return true;
    }

     

    • aux로 load_info 구조체를 만들어 지연 로딩에 필요한 정보를 전달
    • file_seek을 통해 파일의 오프셋을 설정
    • vm_alloc_page_with_initializer를 사용하여 페이지를 할당하고 초기화

    10. setup_stack 함수 수정

    static bool setup_stack(struct intr_frame *if_)
    {
        bool success = false;
        void *stack_bottom = (void *)(((uint8_t *)USER_STACK) - PGSIZE);
    
        if(vm_alloc_page(VM_ANON | VM_MARKER_0, stack_bottom, 1)){
            success = vm_claim_page(stack_bottom);
            if(success){
                if_->rsp = USER_STACK;
                thread_current()->stack_bottom = stack_bottom;
            }
        }
        return success;
    }

     

     

    • 스택을 초기화할 때 페이지를 할당하고 즉시 클레임(claim)
    • stack_bottom을 thread_current()에 저장하여 이후 스택 확장 시 사용
    • 유저 스택의 최상단 주소를 if_->rsp에 설정

     

    11. anon.c에서 swap in/out 구현

    static bool anon_swap_in (struct page *page, void *kva) {
        struct anon_page *anon_page = &page->anon;
        
        int idx = anon_page->bit_idx;
        if(idx < 0) {
            return false;
        }
        
        for (int i = 0; i < 8; i++) {
            disk_sector_t sec_no = (disk_sector_t) idx*8 + i;
            disk_read(swap_disk, sec_no, kva + SECTOR_SIZE * i);
        }
    
        anon_page->bit_idx = -1;
        bitmap_flip(swap_table.swap_used_map, idx);
    
        return true;
    }
    
    static bool anon_swap_out (struct page *page) {
        struct anon_page *anon_page = &page->anon;
        struct frame *victim_frame = page->frame;
        int idx = bitmap_scan(swap_table.swap_used_map, 0, 1, false);
        if (idx == BITMAP_ERROR) {
            return false;
        }
    
        for (int i = 0; i < 8; i++) {
            disk_sector_t sec_no = (disk_sector_t) idx*8 + i;
            disk_write(swap_disk, sec_no, (victim_frame->kva) + SECTOR_SIZE * i);
        }
    
        anon_page->bit_idx = idx;
        pml4_clear_page(thread_current()->pml4, page->va);
        bitmap_flip(swap_table.swap_used_map, idx);
    
        return true;
    }
    
    static void anon_destroy (struct page *page) {
        struct anon_page *anon_page = &page->anon;
        struct thread *cur = thread_current();
        hash_delete(&cur->spt.pages, &page->page_elem);
        if (page->frame){
            pml4_clear_page(cur->pml4, page->va);
            palloc_free_page(page->frame->kva); 
            list_remove(&page->frame->frame_elem);
            free(page->frame);
            page->frame = NULL;
        }
    }

     

     

    • anon_swap_in: 스왑 디스크에서 페이지를 메모리로 읽어오기
      • 스왑 공간에서 bit_idx를 사용하여 위치를 찾기
      • 8개의 섹터를 읽어와 페이지 크기(4KB)를 채우기
      • 스왑 비트맵을 업데이트
    • anon_swap_out: 메모리의 페이지를 스왑 디스크로 내보내기
      • 스왑 비트맵에서 빈 공간을 찾기
      • 8개의 섹터에 페이지를 기록
      • 페이지 테이블 엔트리를 제거
    • anon_destroy: 페이지를 해제하고 관련 자원을 정리

     

     

    12. file.c에서 파일 기반 페이지의 swap in/out 구현

    bool file_backed_initializer (struct page *page, enum vm_type type, void *kva) {
        page->operations = &file_ops;
        struct file_page *file_page = &page->file;
        return true;
    }
    
    static bool file_backed_swap_in (struct page *page, void *kva) {
        struct file_page *file_page = &page->file;
        struct file *file = file_page->file;
        file_seek(file, file_page->ofs);
        file_read_at(file, kva, file_page->page_read_bytes, file_page->ofs);
        return true;
    }
    
    static bool file_backed_swap_out (struct page *page) {
        uint64_t *pml4 = thread_current()->pml4;
        struct file_page *file_page = &page->file;
    
        if(pml4_is_dirty(pml4, page->va)){
            struct file *file = file_page->file;
            file_seek(file, file_page->ofs);
            file_write_at(file, page->frame->kva, file_page->page_read_bytes, file_page->ofs);
            pml4_set_dirty(pml4, page->va, false);
        }
    
        pml4_clear_page(pml4, page->va);
    
        return true;
    }
    
    static void file_backed_destroy (struct page *page) {
        struct file_page *file_page = &page->file;
        uint64_t *pml4 = thread_current()->pml4;
        struct supplemental_page_table *spt = &thread_current()->spt;
    
        hash_delete(&spt->pages, &page->page_elem);
    
        if(page->frame != NULL){
            if(pml4_is_dirty(pml4, page->va))
                file_write_at(file_page->file, page->va, file_page->page_read_bytes, file_page->ofs);
            
            pml4_clear_page(pml4, page->va);
            palloc_free_page(page->frame->kva);
    
            list_remove(&page->frame->frame_elem);
            free(page->frame);
            page->frame = NULL;
        }
    }

     

    • file_backed_swap_in: 파일에서 페이지를 읽어와 메모리에 로드
    • file_backed_swap_out: 페이지가 수정되었는지 확인하고, 수정되었으면 파일에 내용을 기록
    • file_backed_destroy: 페이지를 해제하고, 수정된 내용이 있으면 파일에 반영

     

     

     


     

    vm.c 코드 (전체코드)

    더보기
    더보기
    /* vm.c: Generic interface for virtual memory objects. */
    
    #include "threads/malloc.h"
    #include "vm/vm.h"
    #include "vm/inspect.h"
    #include "threads/mmu.h"
    #include "userprog/process.h"
    #include "userprog/syscall.h"
    #include <string.h>
    
    static unsigned
    page_hash (const struct hash_elem *p_, void *aux UNUSED);
    static bool
    page_less (const struct hash_elem *a_,
               const struct hash_elem *b_, void *aux UNUSED);
    
    static struct page *page_lookup (struct supplemental_page_table *spt, const void *address);
    
    struct list frame_table;
    
    struct lock frame_table_lock;
    
    /* Initializes the virtual memory subsystem by invoking each subsystem's
     * intialize codes. */
    void
    vm_init (void) {
    	vm_anon_init ();
    	vm_file_init ();
    	list_init(&frame_table);
    
    	lock_init(&frame_table_lock);
    #ifdef EFILESYS  /* For project 4 */
    	pagecache_init ();
    #endif
    	register_inspect_intr ();
    	/* DO NOT MODIFY UPPER LINES. */
    	/* TODO: Your code goes here. */
    }
    
    /* Get the type of the page. This function is useful if you want to know the
     * type of the page after it will be initialized.
     * This function is fully implemented now. */
    enum vm_type
    page_get_type (struct page *page) {
    	int ty = VM_TYPE (page->operations->type);
    	switch (ty) {
    		case VM_UNINIT:
    			return VM_TYPE (page->uninit.type);
    		default:
    			return ty;
    	}
    }
    
    /* Helpers */
    static struct frame *vm_get_victim (void);
    static bool vm_do_claim_page (struct page *page);
    static struct frame *vm_evict_frame (void);
    
    /* Create the pending page object with initializer. If you want to create a
     * page, do not create it directly and make it through this function or
     * `vm_alloc_page`. */
    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;
    
    	struct page* page = spt_find_page (spt, upage);
    
    	/* Check wheter the upage is already occupied or not. */
    	if (page == 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. */
    		page = malloc(sizeof(struct page));
    		if (page == NULL)
    			goto err;
    		bool (*initializer)(struct page *, enum vm_type, void *);
    		switch (VM_TYPE(type)){
    		case VM_ANON:
    			initializer = anon_initializer;
    			break;
    		case VM_FILE:
    			initializer = file_backed_initializer;
    			break;
    		default:
    			goto err;
    		}
    
    		uninit_new(page, upage, init, type, aux, initializer);
    		page->is_writable = writable;
    		
    		/* TODO: Insert the page into the spt. */
    		return spt_insert_page(spt, page);
    	}
    err:
    	return false;
    }
    
    /* Find VA from spt and return page. On error, return NULL. */
    struct page *
    spt_find_page (struct supplemental_page_table *spt UNUSED, void *va UNUSED) {
    	struct page *page = NULL;
    	/* TODO: Fill this function. */
    	return page_lookup(spt, va);
    }
    
    /* Insert PAGE into spt with validation. */
    bool
    spt_insert_page (struct supplemental_page_table *spt UNUSED,
    		struct page *page UNUSED) {
    	int succ = false;
    	/* TODO: Fill this function. */
    	struct hash_elem * result = hash_insert(&spt->pages, &page->page_elem);
    	if(result == NULL)
    		succ = true;
    	return succ;
    }
    
    void
    spt_remove_page (struct supplemental_page_table *spt, struct page *page) {
    	vm_dealloc_page (page);
    	return true;
    }
    
    /* Get the struct frame, that will be evicted. */
    static struct frame *
    vm_get_victim (void) {
    	 /* TODO: The policy for eviction is up to you. */
    	struct list_elem *e = list_pop_front(&frame_table);
    	struct frame *victim = list_entry(e, struct frame, frame_elem);
    
    	return victim;
    }
    
    /* Evict one page and return the corresponding frame.
     * Return NULL on error.*/
    static struct frame *
    vm_evict_frame (void) {
    	struct frame *victim = vm_get_victim ();
    	/* TODO: swap out the victim and return the evicted frame. */
    	struct page *victim_page = victim->page;
    	
    	if(swap_out(victim_page)) {
    
    		victim_page->frame = NULL;
    		victim->page = NULL;
    		memset(victim->kva, 0, PGSIZE);
    
    		return victim;
    	}
    
    	return NULL;
    }
    
    /* palloc() and get frame. If there is no available page, evict the page
     * and return it. This always return valid address. That is, if the user pool
     * memory is full, this function evicts the frame to get the available memory
     * space.*/
    static struct frame *
    vm_get_frame (void) {
    	struct frame *frame = (struct frame*)malloc(sizeof(struct frame));
    	ASSERT (frame != NULL);
    	/* TODO: Fill this function. */
    	frame->kva = palloc_get_page(PAL_USER);
    	frame->page = NULL;
    	if(frame->kva == NULL) {
    		free(frame);
    		frame = vm_evict_frame();
    	}
    	
    	ASSERT (frame->page == NULL);
    	
    	lock_acquire(&frame_table_lock);
    	list_push_back(&frame_table, &frame->frame_elem);
    	lock_release(&frame_table_lock);
    
    	return frame;
    }
    
    /* Growing the stack. */
    static void
    vm_stack_growth (void *addr UNUSED) {
        if(vm_alloc_page(VM_ANON | VM_MARKER_0, pg_round_down(addr), 1))
    		thread_current()->stack_bottom -= PGSIZE;
    	
    }
    
    /* Handle the fault on write_protected page */
    static bool
    vm_handle_wp (struct page *page UNUSED) {
    }
    
    /* Return true on success */
    bool
    vm_try_handle_fault (struct intr_frame *f, void *addr,
    		bool user, bool write, bool not_present) {
    	struct supplemental_page_table *spt = &thread_current ()->spt;
    	/* TODO: Validate the fault */
    	if(addr == NULL || is_kernel_vaddr(addr)){
    		return false;
    	}
    	
    	if (!not_present){
    		return false;
    	}
    
    	void *rsp = f->rsp; // user access인 경우 rsp는 유저 stack을 가리킨다.
        if (!user) // kernel access인 경우 thread에서 rsp를 가져와야 한다.
    		rsp = thread_current()->rsp;
    
    	// 스택 확장으로 처리할 수 있는 폴트인 경우, vm_stack_growth를 호출한다.
        if (rsp-8 <= addr  && USER_STACK - 0x100000 <= addr && addr <= USER_STACK)
    		vm_stack_growth(pg_round_down(addr));
    	
    	struct page *page = spt_find_page(spt, addr);
    	if (page == NULL){
    		return false;
    	}
    	if (write && !page->is_writable){
    		return false;
    	}
    	
    	/* TODO: Your code goes here */
    	bool success = vm_do_claim_page (page);
    	return success;
    }
    
    /* Free the page.
     * DO NOT MODIFY THIS FUNCTION. */
    void
    vm_dealloc_page (struct page *page) {
    	destroy (page);
    	free (page);
    }
    
    /* Claim the page that allocate on VA. */
    bool
    vm_claim_page (void *va UNUSED) {
    	struct thread *cur = thread_current();
    	struct page *page = spt_find_page(&cur->spt, va);
    	/* TODO: Fill this function */
    	if (page == NULL){
    		return false;
    	}
    	return vm_do_claim_page (page);
    }
    
    /* Claim the PAGE and set up the mmu. */
    static bool
    vm_do_claim_page (struct page *page) {
    	struct frame *frame = vm_get_frame ();
    
    	/* Set links */
    	frame->page = page;
    	page->frame = frame;
    
    	/* TODO: Insert page table entry to map page's VA to frame's PA. */
    
    	struct thread *cur = thread_current();
    	pml4_set_page(cur->pml4, page->va, frame->kva, page->is_writable);
    	return swap_in(page, frame->kva);
    }
    
    /* Initialize new supplemental page table */
    void
    supplemental_page_table_init (struct supplemental_page_table *spt UNUSED) {
    	hash_init (&spt->pages, page_hash, page_less, NULL);
    }
    
    /* Returns a hash value for page p. */
    unsigned
    page_hash (const struct hash_elem *p_, void *aux UNUSED) {
      const struct page *p = hash_entry (p_, struct page, page_elem);
      return hash_bytes (&p->va, sizeof p->va);
    }
    
    /* Returns true if page a precedes page b. */
    bool
    page_less (const struct hash_elem *a_,
               const struct hash_elem *b_, void *aux UNUSED) {
      const struct page *a = hash_entry (a_, struct page, page_elem);
      const struct page *b = hash_entry (b_, struct page, page_elem);
    
      return a->va < b->va;
    }
    
    /* Returns the page containing the given virtual address, or a null pointer if no such page exists. */
    struct page *
    page_lookup (struct supplemental_page_table *spt, const void *va) {
    	struct page p;
    	struct hash_elem *e;
    	
    	p.va = pg_round_down(va);
    	e = hash_find (&spt->pages, &p.page_elem);
    	return e != NULL ? hash_entry (e, struct page, page_elem) : NULL;
    }
    
    /* Copy supplemental page table from src to dst */
    bool
    supplemental_page_table_copy (struct supplemental_page_table *dst,
    		struct supplemental_page_table *src) {
    	struct hash_iterator i;
    	struct hash_elem *elem;
    	hash_first(&i, &src->pages);
    	while ((elem = hash_next(&i))){
    		struct page *p = hash_entry(elem, struct page, page_elem);
    		enum vm_type type = page_get_type(p);
    
    		if (VM_TYPE(p->operations->type) == VM_UNINIT){
    			//if(!vm_alloc_page_with_initializer(VM_ANON, p->va, p->is_writable, p->uninit.init, p->uninit.aux)) // 왜 ANON?
    			struct load_info *copy_aux = (struct load_info *)malloc(sizeof(struct load_info));
    			memcpy(copy_aux, p->uninit.aux, sizeof(struct load_info));
    			if(!vm_alloc_page_with_initializer(type, p->va, p->is_writable, p->uninit.init, copy_aux))
    				return false;
    		}else{
    			if(vm_alloc_page(type, p->va, p->is_writable)
    			&& vm_claim_page(p->va)){
    				struct page* copy = spt_find_page(dst, p->va);
    				memcpy(copy->frame->kva, p->frame->kva, PGSIZE);
    				copy->frame->page = copy;
    			}else
    				return false;
    		}
    		
    	}
    	return true;
    	
    }
    
    /* Free the resource hold by the supplemental page table */
    void
    supplemental_page_table_kill (struct supplemental_page_table *spt UNUSED) {
    	/* TODO: Destroy all the supplemental_page_table hold by thread and
    	* TODO: writeback all the modified contents to the storage. */
    	// hash_destroy(&spt->pages, page_dealloc);
    	hash_clear(&spt->pages, page_dealloc);
    }
    
    void page_dealloc(struct hash_elem *e, void *aux UNUSED) {
    	struct page *target = hash_entry (e, struct page, page_elem);
    	destroy(target);
        free(target);
    }
     

    핀토스의 vm.c 파일은 가상 메모리 객체에 대한 일반적인 인터페이스를 구현한다.

    이 코드는 주로 가상 메모리의 초기화, 페이지 할당, 페이지 타입 확인, 페이지 테이블 관리, 페이지 폴트 처리, 스왑 인/아웃, 스택 확장 등의 기능을 포함한다.

     

    1.초기화 함수 (vm_init):

    • 가상 메모리 서브시스템 초기화를 담당한다.
    • vm_anon_init(), vm_file_init() 등 서브시스템 초기화를 호출하고, 프레임 테이블을 초기화하며, 락을 설정한다.
    void
    vm_init (void) {
    	vm_anon_init ();
    	vm_file_init ();
    	list_init(&frame_table);
    
    	lock_init(&frame_table_lock);
    #ifdef EFILESYS  /* For project 4 */
    	pagecache_init ();
    #endif
    	register_inspect_intr ();
    	/* DO NOT MODIFY UPPER LINES. */
    	/* TODO: Your code goes here. */
    }

     

     

    2. 페이지 타입 가져오기 (page_get_type):

    • 페이지의 타입을 확인하는 함수로, 페이지가 어떤 타입(익명 페이지, 파일 백업 페이지 등)인지 반환한다.
    enum vm_type
    page_get_type (struct page *page) {
    	int ty = VM_TYPE (page->operations->type);
    	switch (ty) {
    		case VM_UNINIT:
    			return VM_TYPE (page->uninit.type);
    		default:
    			return ty;
    	}
    }

     

     

    3. 페이지 할당 (vm_alloc_page_with_initializer):

    • 새 페이지 객체를 생성하고 초기화 함수를 설정한다.
    • 이미 존재하는 주소에 대한 페이지 요청이 들어오면, 해당 주소가 이미 사용 중인지 확인 후 새 페이지를 할당하거나 에러 처리한다.
    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;
    
    	struct page* page = spt_find_page (spt, upage);
    
    	/* Check wheter the upage is already occupied or not. */
    	if (page == 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. */
    		page = malloc(sizeof(struct page));
    		if (page == NULL)
    			goto err;
    		bool (*initializer)(struct page *, enum vm_type, void *);
    		switch (VM_TYPE(type)){
    		case VM_ANON:
    			initializer = anon_initializer;
    			break;
    		case VM_FILE:
    			initializer = file_backed_initializer;
    			break;
    		default:
    			goto err;
    		}
    
    		uninit_new(page, upage, init, type, aux, initializer);
    		page->is_writable = writable;
    		
    		/* TODO: Insert the page into the spt. */
    		return spt_insert_page(spt, page);
    	}
    err:
    	return false;
    }

     

     

    4. 페이지 찾기 및 삽입 (spt_find_page, spt_insert_page):

    • 주소를 사용하여 보조 페이지 테이블에서 해당 페이지를 찾거나, 새 페이지를 삽입한다.
    struct page *
    spt_find_page (struct supplemental_page_table *spt UNUSED, void *va UNUSED) {
    	struct page *page = NULL;
    	/* TODO: Fill this function. */
    	return page_lookup(spt, va);
    }

     

    bool
    spt_insert_page (struct supplemental_page_table *spt UNUSED,
    		struct page *page UNUSED) {
    	int succ = false;
    	/* TODO: Fill this function. */
    	struct hash_elem * result = hash_insert(&spt->pages, &page->page_elem);
    	if(result == NULL)
    		succ = true;
    	return succ;
    }

     

     

    5. 페이지 제거 (spt_remove_page):

    • 주어진 페이지를 보조 페이지 테이블에서 제거하고 메모리에서 할당 해제한다.
    void
    spt_remove_page (struct supplemental_page_table *spt, struct page *page) {
    	vm_dealloc_page (page);
    	return true;
    }

     

     

    6. 페이지 스왑 아웃 및 프레임 희생자 선택 (vm_evict_frame, vm_get_victim):

    • 메모리에서 프레임을 제거(스왑 아웃)하고, 스왑 아웃할 프레임의 희생자를 결정한다.
    static struct frame *
    vm_get_victim (void) {
    	 /* TODO: The policy for eviction is up to you. */
    	struct list_elem *e = list_pop_front(&frame_table);
    	struct frame *victim = list_entry(e, struct frame, frame_elem);
    
    	return victim;
    }
    
    /* Evict one page and return the corresponding frame.
     * Return NULL on error.*/
    static struct frame *
    vm_evict_frame (void) {
    	struct frame *victim = vm_get_victim ();
    	/* TODO: swap out the victim and return the evicted frame. */
    	struct page *victim_page = victim->page;
    	
    	if(swap_out(victim_page)) {
    
    		victim_page->frame = NULL;
    		victim->page = NULL;
    		memset(victim->kva, 0, PGSIZE);
    
    		return victim;
    	}
    
    	return NULL;
    }

     

     

    7. 스택 확장 (vm_stack_growth):

    • 주소가 주어지면 스택을 확장하는 기능을 제공한다. 주소가 스택 확장 가능 범위 내에 있는지 확인 후 확장을 수행한다.
    static void
    vm_stack_growth (void *addr UNUSED) {
        if(vm_alloc_page(VM_ANON | VM_MARKER_0, pg_round_down(addr), 1))
    		thread_current()->stack_bottom -= PGSIZE;
    	
    }

     

     

    8. 페이지 폴트 처리 (vm_try_handle_fault):

    • 페이지 폴트가 발생했을 때, 해당 주소에 대한 페이지를 찾고, 필요에 따라 페이지를 로드하거나 스택을 확장하는 로직을 포함한다.
    bool
    vm_try_handle_fault (struct intr_frame *f, void *addr,
    		bool user, bool write, bool not_present) {
    	struct supplemental_page_table *spt = &thread_current ()->spt;
    	/* TODO: Validate the fault */
    	if(addr == NULL || is_kernel_vaddr(addr)){
    		return false;
    	}
    	
    	if (!not_present){
    		return false;
    	}
    
    	void *rsp = f->rsp; // user access인 경우 rsp는 유저 stack을 가리킨다.
        if (!user) // kernel access인 경우 thread에서 rsp를 가져와야 한다.
    		rsp = thread_current()->rsp;
    
    	// 스택 확장으로 처리할 수 있는 폴트인 경우, vm_stack_growth를 호출한다.
        if (rsp-8 <= addr  && USER_STACK - 0x100000 <= addr && addr <= USER_STACK)
    		vm_stack_growth(pg_round_down(addr));
    	
    	struct page *page = spt_find_page(spt, addr);
    	if (page == NULL){
    		return false;
    	}
    	if (write && !page->is_writable){
    		return false;
    	}
    	
    	/* TODO: Your code goes here */
    	bool success = vm_do_claim_page (page);
    	return success;
    }

     

     

    9. 페이지 및 프레임 클레임 (vm_claim_page, vm_do_claim_page):

    • 주소에 해당하는 페이지를 클레임하고, 물리 메모리에 매핑한다.
    bool
    vm_claim_page (void *va UNUSED) {
    	struct thread *cur = thread_current();
    	struct page *page = spt_find_page(&cur->spt, va);
    	/* TODO: Fill this function */
    	if (page == NULL){
    		return false;
    	}
    	return vm_do_claim_page (page);
    }
    
    /* Claim the PAGE and set up the mmu. */
    static bool
    vm_do_claim_page (struct page *page) {
    	struct frame *frame = vm_get_frame ();
    
    	/* Set links */
    	frame->page = page;
    	page->frame = frame;
    
    	/* TODO: Insert page table entry to map page's VA to frame's PA. */
    
    	struct thread *cur = thread_current();
    	pml4_set_page(cur->pml4, page->va, frame->kva, page->is_writable);
    	return swap_in(page, frame->kva);
    }

     

     

    10. 보조 페이지 테이블 관련 함수 (supplemental_page_table_init, supplemental_page_table_copy, supplemental_page_table_kill):

    • 보조 페이지 테이블을 초기화하거나, 복사하거나, 종료 처리를 담당한다.
    void
    supplemental_page_table_init (struct supplemental_page_table *spt UNUSED) {
    	hash_init (&spt->pages, page_hash, page_less, NULL);
    }

     

    bool
    supplemental_page_table_copy (struct supplemental_page_table *dst,
    		struct supplemental_page_table *src) {
    	struct hash_iterator i;
    	struct hash_elem *elem;
    	hash_first(&i, &src->pages);
    	while ((elem = hash_next(&i))){
    		struct page *p = hash_entry(elem, struct page, page_elem);
    		enum vm_type type = page_get_type(p);
    
    		if (VM_TYPE(p->operations->type) == VM_UNINIT){
    			//if(!vm_alloc_page_with_initializer(VM_ANON, p->va, p->is_writable, p->uninit.init, p->uninit.aux)) // 왜 ANON?
    			struct load_info *copy_aux = (struct load_info *)malloc(sizeof(struct load_info));
    			memcpy(copy_aux, p->uninit.aux, sizeof(struct load_info));
    			if(!vm_alloc_page_with_initializer(type, p->va, p->is_writable, p->uninit.init, copy_aux))
    				return false;
    		}else{
    			if(vm_alloc_page(type, p->va, p->is_writable)
    			&& vm_claim_page(p->va)){
    				struct page* copy = spt_find_page(dst, p->va);
    				memcpy(copy->frame->kva, p->frame->kva, PGSIZE);
    				copy->frame->page = copy;
    			}else
    				return false;
    		}
    		
    	}
    	return true;
    	
    }

     

    void
    supplemental_page_table_kill (struct supplemental_page_table *spt UNUSED) {
    	/* TODO: Destroy all the supplemental_page_table hold by thread and
    	* TODO: writeback all the modified contents to the storage. */
    	// hash_destroy(&spt->pages, page_dealloc);
    	hash_clear(&spt->pages, page_dealloc);
    }

     

     

Designed by Tistory.