[PintOS] Project 3 - Memory Management

novxerim·2022년 1월 25일
3

SW-Jungle

목록 보기
42/59
post-thumbnail

- Memory Management

메모리 관리

가상 메모리 시스템을 지원하려면 가상 페이지와 물리적 프레임을 효과적으로 관리해야 합니다. 즉, 어떤 (가상 또는 물리적) 메모리 영역이 어떤 목적으로 누구에 의해 사용되고 있는지 등을 추적해야 합니다. 먼저 추가 페이지 테이블을 처리한 다음 물리적 프레임을 처리합니다. 이해를 돕기 위해 가상 페이지에는 "페이지"라는 용어를 사용하고 물리 페이지에는 "프레임"이라는 용어를 사용합니다.

- Page Structure and Operations

페이지 구조 및 작업

- struct page

구조체 페이지

include/vm/vm.h에 정의된 page 는 가상 메모리의 페이지를 나타내는 구조입니다. 페이지에 대해 알아야 하는 모든 필요한 데이터를 저장합니다. 현재 템플릿에서 구조는 다음과 같습니다.

struct page {
  const struct page_operations *operations;
  void *va;              /* Address in terms of user space */
  struct frame *frame;   /* Back reference for frame */

  union {
    struct uninit_page uninit;
    struct anon_page anon; // stack, heap 할당 시 사용
    struct file_page file; // 우리가 다룰 페이지의 타입 3가지(uninit, anon, file)
#ifdef EFILESYS
    struct page_cache page_cache;
#endif
  };
};

page operations의 아래에, virtual address, physical frame 및 Union 필드가 있습니다. 

Union : 같은 메모리 영역을 여러 개의 변수들이 공유할 수 있게 하는 기능. 메모리 절약.

유니온은 메모리 영역에 다른 유형의 데이터를 저장할 수 있는 특별한 데이터 유형입니다. 유니온에는 여러 구성원이 있지만 한 번에 하나의 구성원만 값을 포함할 수 있습니다. 즉, 시스템의 페이지는 uninit_page, anon_page, file_page 또는 page_cache일 수 있습니다. 예를 들어 페이지가 익명 페이지인 경우( Anonymous Page 참조 ) 페이지 구조는 필드 struct anon_page anon를 멤버 중 하나로 갖습니다 . anon_page는 익명 페이지를 유지하는 데 필요한 모든 정보가 포함됩니다.

- Page Operations

페이지 작업

위에서 설명하고 include/vm/vm.h에 정의된 바와 같이, 하나의 페이지는 VM_UNINIT, VM_ANON 또는 VM_FILE에 있을 수 있습니다. 페이지에는 스왑 인, 스왑 아웃 및 페이지 파괴와 같은 몇 가지 작업이 수행됩니다. 각 페이지 유형마다 이러한 작업에 대해 필요한 단계와 작업이 다릅니다. 즉, VM_ANON 페이지와 VM_FILE 페이지에 대해 다른 destroy 함수를 호출해야 합니다.

한 가지 방법은 각 경우를 처리하기 위해 각 함수에서 switch-case 구문을 사용하는 것입니다. 이를 처리하기 위해 객체 지향 프로그래밍의 "클래스 상속" 개념을 도입합니다. 실제로 C 프로그래밍 언어에는 "클래스"나 "상속"이 없습니다 . Linux와 같은 실제 운영 체제 코드에서 유사한 방식으로 개념을 실현하기 위해 함수 포인터 를 사용합니다 .

함수 포인터는 여러분이 배운 다른 포인터와 마찬가지로 메모리 내의 함수 또는 실행 가능한 코드를 가리키는 포인터입니다. 함수 포인터는 검사 없이 런타임 값을 기반으로 실행할 특정 함수를 호출하는 간단한 방법을 제공하기 때문에 유용합니다. 우리의 경우 destroy (page)코드 수준에서 단순히 호출하는 것으로 충분하며 컴파일러는 올바른 함수 포인터를 호출하여 페이지 유형에 따라 적절한 destroy루틴을 선택합니다 .

페이지 작업을 위한 구조 struct page_operationsinclude/vm/vm.h에 정의되어 있습니다. 이 구조를 3개의 함수 포인터를 포함하는 함수 테이블로 간주합니다.

struct page_operations {
  bool (*swap_in) (struct page *, void *);
  bool (*swap_out) (struct page *);
  void (*destroy) (struct page *);
  enum vm_type type;
};

이제 page_operation 구조를 어디서 찾을 수 있는지 알아보겠습니다. include/vm/vm.h에 있는 struct page 페이지 구조체를 보면, operations라는 필드가 있는 것을 볼 수 있습니다. 이제 vm/file.c로 이동하면 page_operations structure인 file_ops가 함수 프로토타입보다 먼저 선언된 것을 볼 수 있습니다. 이것은 파일 백업 페이지에 대한 함수 포인터 표입니다. .destroy필드에는 file_backed_destroy value가 있는데, 이는 페이지를 파괴하는 함수로 같은 파일에 정의되어 있습니다.

함수 포인터 인터페이스에서 file_backed_destroy가 어떻게 호출되는지 알아보겠습니다. (vm/vm.c)안의 vm_dealloc_page(page) 가 호출되고, 이 페이지가 파일 백업 페이지 (VM_FILE)라고 가정합시다. 해당 함수 내에서 destroy (page)를 호출합니다. destroy (page)는 다음과 같이 include/vm/vm.h의 매크로로 정의됩니다.

#define swap_in(page, v) (page)->operations->swap_in ((page), v)
#define swap_out(page) (page)->operations->swap_out (page)
            
#define destroy(page) if ((page)->operations->destroy) (page)->operations->destroy (page)

이것은 destroy 함수를 호출하면, 실제로 페이지 구조체에서 검색된 destroy 함수인
(page)->operations->destroy (page)를 호출한다는 것을 알려줍니다.


- Implement Supplemental Page Table (첫 번째로 구현할 것)

추가 페이지 테이블 구현

이 시점에서 Pintos에는 메모리의 가상 및 물리적 매핑을 관리하기 위한 페이지 테이블(pml4)이 있습니다. 그러나 이것으로 충분하지 않습니다. 이전 섹션에서 논의한 것처럼 페이지 폴트 및 리소스 관리를 처리하기 위해 각 페이지에 대한 추가 정보를 보유하는 추가 페이지 테이블도 필요합니다. 따라서 프로젝트 3의 첫 번째 작업으로 추가 페이지 테이블에 대한 몇 가지 기본 기능을 구현하는 것이 좋습니다.

- 추가 페이지 테이블을 만든 이유(출처) - 도경

  • 여러 page-table entry가 같은 하나의 physical address를 가리키고 있지만, 다른 page-table entry도 같은 physical address를 가르킬 수 있다. 왜냐햐면 page-table은 page-directory-entry마다 하나씩 가지고 있고, 서로 다른 virtual address를 가지지만 같은 physical memory를 참조하는 경우가 충분히 발생할 수 있기 때문이다. referencing하고 있는 table들은 전부 다르지만, 결국 같은 physical address를 가르키고 있는 것이다. 이러한 경우를 다루기 위해서는 하나의 프로세스가 어떠한 physical memory를 참조하고 있는지에 대한 정보가 필요하고, 이를 위해서 추가적인 page table이 필요한 것이다.
  • 기존의 page-table은 physical memory를 향한 일방적인 pointing에 불과하다. 때문에 physical-memory의 값이 바뀌어 있으면 패닉에 빠질 수 밖에 없다. 기존에 어떠한 데이터가 있는지에 대한 정보를 전혀 가지고 있지 않기 때문에 이러한 문제가 발생하면 해결할 수가 없다. 따라서 supplementary page table에는 원래 physical-memory에 들어가 있어야 할 데이터가 어떤 것인지에 대한 정보를 가지고 있어야 한다.
  • resource-free를 할 때에 어떠한 데이터를 FREE할지를 결정하는데에 사용된다. 두 번째 이유에서 설명한 것을 바탕으로 충분히 직관적인 이해를 할 수 있으리라 본다.

Implement supplemental page table management functions in vm/vm.c.

vm/vm.c에서 추가 페이지 테이블 관리 기능을 구현합니다 .

먼저 Pintos에서 추가 페이지 테이블을 디자인하는 방법을 결정해야 합니다. 자신만의 추가 페이지 테이블을 디자인한 후(구현0.) 디자인과 관련하여 아래 세 가지 기능을 구현합니다.


- 구현0.

        // [include>vm>vm.h]
        struct supplemental_page_table {
        	struct hash spt_hash;
        };

자신만의 supplemental_page_table을 먼저 디자인한다. 나는 해쉬테이블로 구현하였다.


- 구현1.

void supplemental_page_table_init (struct supplemental_page_table *spt);

추가 페이지 테이블을 초기화합니다. 추가 페이지 테이블에 사용할 데이터 구조를 선택할 수 있습니다. 함수는 새로운 프로세스가 시작될 때 (userprog/process.c의 initd)와, 프로세스가 fork될 때 (userprog/process.c의 __do_fork) 불립니다.

  • 코드 구현 (hash 이해하기)
  1. supplemental_page_table_init 함수를 채우기 위해서는 init함수인 만큼 초기화를 해야 하는데, 나는 여러 자료 구조 (배열, 비트맵, 해시테이블) 중 hash table을 이용하기로 했으니 hash.c에서 hash 함수들을 찾아가보았다.
  2. hash를 초기화 시키는 함수인 hash_init 함수가 있다. 이 함수를 이용해 spt_init 함수를 채우면 될 듯 하다. 그렇다면 hash_init 함수를 쓸 때 필요한 인자는 무엇이 있을까?
    - hash_init
bool
hash_init (struct hash *h, 
			hash_hash_func *hash, hash_less_func *less, void *aux) {
	h->elem_cnt = 0;
	h->bucket_cnt = 4;
	h->buckets = malloc (sizeof *h->buckets * h->bucket_cnt);
	h->hash = hash;
	h->less = less;
	h->aux = aux;

	if (h->buckets != NULL) {
		hash_clear (h, NULL);
		return true;
	} else
		return false;
}
  1. 필요한 인자는 hash_init (struct hash *h, hash_hash_func *hash, hash_less_func *less, void *aux)
  • hash_hash_func : 주어진 aux 데이터에서 해시 요소에 대한 해시 값을 계산 후 반환
  • hash_less_func : 해시 요소들을 비교함
    4. 최종적으로 밑 코드처럼 구현하기 위해서는 해당 사용할 함수들을 또 구현해야 함.
void supplemental_page_table_init (struct supplemental_page_table *spt UNUSED) {
	hash_init(&spt->spt_hash, page_hash, page_less, NULL);
}
  1. vm.cpage_hash, page_less 구현 → 최종 supplemental_page_table_init 구현.
  • 코드 (이해하기)
// 해시 테이블 초기화할 때 해시 값을 구해주는 함수의 포인터
unsigned page_hash(const struct hash_elem *p_, void *aux UNUSED) {
    const struct page *p = hash_entry(p_, struct page, hash_elem);
    return hash_bytes(&p->va, sizeof p->va);
}

// 해시 테이블 초기화할 때 해시 요소들 비교하는 함수의 포인터
// a가 b보다 작으면 true, 반대면 false
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, hash_elem);
    const struct page *b = hash_entry(b_, struct page, hash_elem);

    return a->va < b->va;
}
/* Initialize new supplemental page table */
void supplemental_page_table_init (struct supplemental_page_table *spt UNUSED) {
	hash_init(&spt->spt_hash, page_hash, page_less, NULL);
}

- 구현2.

struct page *spt_find_page (struct supplemental_page_table *spt, void *va);

주어진 supplemental page table에서 va에 해당하는 것을 찾으십시오 . 
실패하면 NULL을 반환합니다.

  • 코드
    - hash_find 함수 (구현) 다시보기 **
/* 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. */
    struct page dummy_page; dummy_page.va = pg_round_down(va); // dummy for hashing
    struct hash_elem *e;

    e = hash_find(&spt->spt_hash, &dummy_page.hash_elem);

	if(e == NULL)
		return NULL;

    return page = hash_entry(e, struct page, hash_elem);
}
  • pg_round_down : vaddr의 페이지를 받는다
  • hash_find : hash_elem 구조체를 e로 받아온다 (다시 타고들어가기)

- 구현3.

bool spt_insert_page (struct supplemental_page_table *spt, struct page *page);

주어진 supplemental page table에 struct page를 삽입하십시오 . 
이 함수는 가상 주소가 주어진 supplemental page table에 존재하지 않는지 확인해야 합니다.

  • 코드
/* Insert PAGE into spt with validation. */
bool spt_insert_page (struct supplemental_page_table *spt UNUSED, struct page *page UNUSED) {
	int succ = false;
	/* P3 추가 */
	struct hash_elem *e = hash_find(&spt->spt_hash, &page->hash_elem);
	if(e != NULL) // page already in SPT
		return succ; // false, fail

	// page not in SPT
	hash_insert (&spt->spt_hash, &page->hash_elem);
	return succ = true;
}
  • 추가 함수
bool delete_page (struct hash *pages, struct page *p) {
	if (hash_delete(pages, &p->hash_elem))
		return false;
	else
		return true;
}

- Frame Management (구현할 것 2)

프레임 관리

지금부터 모든 페이지는 메모리가 구성될 때 메모리에 대한 메타 데이터를 보유하고 있지 않습니다. 따라서 물리적 메모리를 관리하려면 다른 방식이 필요합니다. include/vm/vm.h에는 물리적 메모리를 나타내는 struct frame이 존재합니다 . 현재 구조는 다음과 같습니다.

/* The representation of "frame" */
struct frame {
  void *kva; /* kernel virtual address */
  struct page *page; // 페이지 구조체를 가리킴
	struct list_elem elem;  // 추가
};

커널 가상 주소인 kva와 페이지 구조체인 page 이렇게 두 필드만 있습니다. 프레임 관리 인터페이스를 구현할 때 더 많은 구성원을 추가할 수 있습니다.


vm/vm.c에서 vm_get_framevm_claim_page과 vm_do_claim_page를 구현하세요. (구현)

- 구현4.

static struct frame *vm_get_frame (void);

palloc_get_page를 호출하여 user pool에서 new physical page를 가져옵니다.
user pool에서 페이지를 가져오면 프레임을 할당하고 구성원을 초기화(initialize its members)한 후 반환합니다. 
vm_get_frame을 구현한 후에는 이 함수를 통해 모든 사용자 공간 페이지(PALLOC_USER)를 할당해야 합니다.
페이지 할당에 실패할 경우 스왑 아웃을 처리할 필요가 없습니다. 일단 PANIC("todo")으로 해당 사례를 표시하십시오.

  • 코드 구현
    - 물리 주소와 매핑할 때 필요한 frame 수정하기!
    1. 먼저 palloc_get_page 를 호출하라고 했으니 해당 함수를 찾아보자.
    - palloc_get_page
/* The representation of "frame" */
struct frame {
  void *kva; /* kernel virtual address */
  struct page *page; // 페이지 구조체를 가리킴
	struct list_elem elem;  // 추가
};
  • return → pages or memset or PANIC
    - 주석
    📎 / 사용 가능한 단일 페이지를 가져오고 커널 가상 주소를 반환합니다.
    - PAL_USER가 설정된 경우, 페이지는 사용자 풀에서 가져오고, 그렇지 않은 경우 커널 풀에서 가져옵니다.
    - PAL_ZERO가 FLAGS로 설정되어 있으면, 페이지가 0으로 채워집니다.
    - 사용 가능한 페이지가 없는 경우 null 포인터를 반환하고, PAL_ASSERT가 FLAGS에 설정되어 있지 않으면 커널 패닉이 발생합니다.
    /
  • 1-2. 타고가보자.
    - palloc_flags
// [include>threads>palloc.h]
/* How to allocate pages. */
enum palloc_flags {
	PAL_ASSERT = 001,           /* Panic on failure. */
	PAL_ZERO = 002,             /* Zero page contents. */
	PAL_USER = 004              /* User page. */
};
  • palloc_get_multiple
    - 주석
    📎 / 사용 가능한 그룹 페이지를 가져오고 커널 가상 주소를 반환합니다.
    - PAL_USER가 설정된 경우, 페이지들은 사용자 풀에서 가져오고, 그렇지 않은 경우 커널 풀에서 가져옵니다.
    - PAL_ZERO가 FLAGS로 설정되어 있으면, 페이지가 0으로 채워집니다.
    - 사용 가능한 페이지가 너무 적으면 null 포인터를 반환하고,PAL_ASSERT가 FLAGS에 설정되어 있지 않으면 커널 패닉이 발생합니다.
    /
// [threads>palloc.c]
/* Obtains and returns a group of PAGE_CNT contiguous free pages.
 - If `PAL_USER` is set, the pages are obtained from the user pool, otherwise from the kernel pool. (single과 동일)
 - If `PAL_ZERO` is set in FLAGS, then the pages are filled with zeros. (single과 동일)
 - If too few pages are available, returns a null pointer, 
	unless `PAL_ASSERT` is set in FLAGS, in which case the kernel panics. */

void * palloc_get_multiple (enum palloc_flags flags, size_t page_cnt) {
	struct pool *pool = flags & PAL_USER ? &user_pool : &kernel_pool;

	lock_acquire (&pool->lock);
	size_t page_idx = bitmap_scan_and_flip (pool->used_map, 0, page_cnt, false);
	lock_release (&pool->lock);
	void *pages;

	if (page_idx != BITMAP_ERROR)
		pages = pool->base + PGSIZE * page_idx;
	else
		pages = NULL;

	if (pages) {
		if (flags & PAL_ZERO)
			memset (pages, 0, PGSIZE * page_cnt);
	} else {
		if (flags & PAL_ASSERT)
			PANIC ("palloc_get: out of pages");
	}

	return pages;
}

🤔  음... 내가 사용할 데이터구조는 해시테이블인데 여기선 주구장창 비트맵함수네?
뭐,. 너무 빠지지 말고 일단 틀만 이해하고 넘어가자.

→ 어쨌든, if문 2개 이후 pages를 리턴한다. (혹은 memset, PANIC)

  1. 그러면 어떻게 vm_get_frame에 이 palloc_get_page 함수를 호출해서 구현해야할까?

palloc_get_page를 호출하여 user pool에서 new physical page를 가져옵니다.
user pool에서 페이지를 가져오면 프레임을 할당하고 구성원을 초기화(initialize its members)한 후 반환합니다.

  • vm_get_frame 함수를 보자.
    - 스켈레톤 코드
// [vm.c]

static struct frame *vm_get_frame (void) {
	struct frame *frame = NULL;
	/* TODO: Fill this function. */

	ASSERT (frame != NULL);
	ASSERT (frame->page == NULL);
	return frame;
}
  • 코드
    - 주석
    📎 palloc()하고 frame을 get하세요.
    사용 가능한 페이지가 없는 경우, 해당 페이지를 삭제한 후 return합니다.
    그러면 항상 유효한 주소가 return됩니다.
    즉, 만약 user pool 메모리가 가득 찬 경우, 이 함수는 사용 가능한 메모리 공간을 확보하기 위해서 frame을 제거합니다.

가상 페이지 - ‘페이지’ / 물리 페이지 - ‘프레임’ 이라고 했었는데
여기서 말하는 페이지 = 물리 페이지 인듯. 그래서 frame 함수들을 사용함.

  • 최종
// [vm.c]
/* 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 = NULL;
	void *kva = palloc_get_page(PAL_USER);
	/* TODO: Fill this function. */

	/* P3 추가 */
	if (kva == NULL){ // NULL이면(사용 가능한 페이지가 없으면) 
		frame = vm_evict_frame(); // 페이지 삭제 후 frame 리턴
	}
	else{ // 사용 가능한 페이지가 있으면
		frame = malloc(sizeof(struct frame)); // 페이지 사이즈만큼 메모리 할당
		frame->kva = kva;
	}

	ASSERT (frame != NULL);
	// ASSERT (frame->page == NULL);
	return frame;
}
  • struct frame 다시 보기
/* The representation of "frame" */
struct frame {
  void *kva; /* kernel virtual address */
  struct page *page; // 페이지 구조체를 가리킴
};
  • palloc_get_page(PAL_USER)

PAL_USER를 사용하는 이유 : 커널 풀 대신 사용자 풀에서 메모리를 할당하게 하기 위해서다. 사용자 풀의 페이지가 부족하면 사용자 프로그램의 페이지가 부족해지지만, 커널 풀의 페이지가 부족해지면 많은 커널 함수들이 메모리를 확보하는 데 문제가 생길 수 있고, 많은 오류가 발생한다.

  • vm_evict_frame (구현해야 함)
// [vm.c]
/* 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. */

	if(victim->page != NULL){
		swap_out(victim->page);
	}
	// Manipulate swap table according to its design
	return victim;
}
#define swap_out(page) (page)->operations->swap_out (page)
  • evict(제거)를 그냥 스왑아웃으로 해버리는 듯.
    → 그럼 다시 스왑인으로 복구(?) 해낼 수도 있는건가?
  • vm_get_victim (구현해야 함) (이해하기)
    : 위의 함수에서 return된 frame을 받아와서 실제로 제거하는 함수
/* Get the struct frame, that will be evicted. */
static struct frame *vm_get_victim (void) {
	struct frame *victim = NULL;
	 /* TODO: The policy for eviction is up to you. */
	/* P3 추가 */
	victim = list_entry(list_pop_front(&frame_table), struct frame, elem); // FIFO algorithm
	return victim;
}
  • struct frame 안에 list_elem 필드 추가
struct frame{
	..
	struct list_elem elem;
}
  • vm.c 파일 상단에 frame_table 변수 선언
struct list frame_table;
  1. 이 부분은 어디서 구현하는거지?

vm_get_frame을 구현한 후에는 이 함수를 통해 모든 사용자 공간 페이지(PALLOC_USER)를 할당해야 합니다.
페이지 할당에 실패할 경우 스왑 아웃을 처리할 필요가 없습니다. 일단 PANIC("todo")으로 해당 사례를 표시하십시오.

일단은 이제 해당 함수를 구현해놨으니 다른 곳에서 (else 두번째줄~ 추가해서) 사용하면 될 것 같음. if vm_get_frame() else Fail -> !swap_out, return PANIC("todo")


- 구현5.

bool vm_do_claim_page (struct page *page);

claim은 physical frame인 페이지를 할당하는 것을 의미합니다. 먼저 (템플릿에서 이미 수행 된) vm_get_frame 을 호출하여 프레임을 얻습니다. 그런 다음 MMU를 설정해야 합니다. 즉, virtual address에서 페이지 테이블의 physical address로의 매핑을 추가합니다. 반환 값은 작업의 성공 여부를 나타내야 합니다.

  • 코드
// [vm.c]
/* Claim the PAGE and set up the mmu. */
/* va에서 PT(안의 pa)에 매핑을 추가함. */
static bool
vm_do_claim_page (struct page *page) {
	struct frame *frame = vm_get_frame ();
	/* P3 추가 */

	/* 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();
	bool writable = page->writable; // [vm.h] struct page에 bool writable; 추가
	pml4_set_page(cur->pml4, page->va, frame->kva, writable);
	// add the mapping from the virtual address to the physical address in the page table.

	bool res = swap_in (page, frame->kva);

	return res;
}
  • pml4_set_page 을 써야겠군
    - 주석

📎 - UPAGE로부터 KPAGE(kva)로 식별된 물리적 프레임으로의 매핑을 PML4에 추가합니다.
- UPAGE가 이미 매핑되어 있으면 안 됩니다.
- KPAGE는 palloc_get_page()가 있는 사용자 풀에서 가져온 페이지여야 합니다.
- WRITABLE(읽기가능)이 true이면, 새 페이지 (기본 속성)는 read/write(rw)입니다. 그렇지 않으면 read-only입니다.
- 성공하면 true를 반환하고 메모리 할당에 실패하면 false를 반환합니다.

  • 원문
/* Adds a mapping in page map level 4 PML4 from user virtual page UPAGE to the physical frame identified by kernel virtual address KPAGE.
* UPAGE must not already be mapped. 
* KPAGE should probably be a page obtained from the user pool with palloc_get_page().
* If WRITABLE is true, the new page is read/write;
                           otherwise it is read-only.
* Returns true if successful, false if memory allocation failed. */
// [threads>mmu.c] (구현 되어 있음)

bool
pml4_set_page (uint64_t *pml4, void *upage, void *kpage, bool rw) {
	ASSERT (pg_ofs (upage) == 0);
	ASSERT (pg_ofs (kpage) == 0);
	ASSERT (is_user_vaddr (upage));
	ASSERT (pml4 != base_pml4);

	uint64_t *pte = pml4e_walk (pml4, (uint64_t) upage, 1);

	if (pte)
		*pte = vtop (kpage) | PTE_P | (rw ? PTE_W : 0) | PTE_U;
	return pte != NULL;
}
  • PTE flags
    - PTE_P : 1 = present, 0 = not
    - PTE_W : 1 = read/write, 0 = read-only
    - PTE_U : 1 = user/kernel, 0 = kernel only

- 구현6.

bool vm_claim_page (void *va);

페이지를 va에 할당합니다(=클레임). 먼저 페이지를 가져온 다음 해당 페이지와 함께 vm_do_claim_page를 호출해야 합니다.

  • 코드
/* Claim the page that allocate on VA. */
bool
vm_claim_page (void *va UNUSED) {
	/* P3 추가 */
	struct supplemental_page_table *spt = &thread_current()->spt; // 이러면 주소를 가리키는건가?
	struct page *page = spt_find_page(spt, va);
	if (page == NULL) {
		return false;
	}
	return vm_do_claim_page (page);
}
  • vm_try_handler_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 */

	// return vm_do_claim_page (page);

	// Step 1. Locate the page that faulted in the supplemental page table
	void * fpage_uvaddr = pg_round_down(addr); // round down to nearest PGSIZE
	// void * fpage_uvaddr = (uint64_t)addr - ((uint64_t)addr%PGSIZE); // round down to nearest PGSIZE

	struct page *fpage = spt_find_page(spt, fpage_uvaddr);
	
	// Invalid access - Not in SPT (stack growth or abort) / kernel vaddr / write request to read-only page
	if(is_kernel_vaddr(addr)){

		return false;
	}
	else if (fpage == NULL){
		void *rsp = user ? f->rsp : thread_current()->rsp; // a page fault occurs in the kernel
		const int GROWTH_LIMIT = 32; // heuristic
		const int STACK_LIMIT = USER_STACK - (1<<20); // 1MB size limit on stack

		// Check stack size max limit and stack growth request heuristically
		if((uint64_t)addr > STACK_LIMIT && USER_STACK > (uint64_t)addr && (uint64_t)addr > (uint64_t)rsp - GROWTH_LIMIT){
			vm_stack_growth (fpage_uvaddr);
			fpage = spt_find_page(spt, fpage_uvaddr);
		}
		else{
			exit(-1); // mmap-unmap
			//return false;
		}
	}
	else if(write && !fpage->writable){

		exit(-1); // mmap-ro
		// return false;
	}

	ASSERT(fpage != NULL);

	// Step 2~4.
	bool gotFrame = vm_do_claim_page (fpage);

	// if (gotFrame)
		// list_push_back(&frame_table, &fpage->frame->elem);

	return gotFrame;
}

profile
블로그 이전했습니다. https://yerimi11.tistory.com/

0개의 댓글