Pintos는 pml4
라는 메모리를 효율적으로 사용
하기 위해 가상 및 물리적 매핑을 관리하는 페이지 테이블이 있다.
그러나 이것으로 충분하지 않고 page fault
발생과 자원 관리
를 위해서
각각 페이지에 대한 추가 정보
를 들고 있는 SPT
(Supplementary page table)가 필요
하다.
Page fault시
커널이 SPT
에서 오류가 발생한 가상 페이지를 조회해서 어떤 데이터가 있는지를 확인
프로세스가 종료시
커널이 SPT
를 참조하여 어떤 리소스를 free 할 것인지를 결정
한다.Pintos에서 권장하는 테이블을 구성하는 방법 아래와 같이 총 4가지가 있고 장단점은 다음과 같다.
1. 배열(array)
- 가장 구현이 간단
하나 공간복잡도가 커
서 메모리 용량을 낭비한다.
2. 연결리스트(linked list) - 연결리스트도 구현이 비교적 간단
하지만 일일이 확인
하면서 찾아야 하기 때문에 시간 복잡도 O(N)
으로 낭비가 크다.
3. 해시 테이블(Hash table) - (Key, Value)로 데이터를 저장하는 자료구조로 빠르게 데이터를 검색할 수 있는 자료구조이다.
4. 비트맵(Bitmap) - 정보를 비트에 저장하는 구조이다. 구분해야 할 자료가 많지 않을 때는 효율적이지만 구분해야 할 자료가 많으면 많을수록 복잡해진다.
이 중에서 해시 테이블(Hash table)
을 이용하여 구현할 것이다.
해시 테이블은 Key값
에 해시함수를 적용
해서 나온 output인 Hash(정수 값으로 나옴)를
해시 테이블의 전체 배열의 크기인 Capacity
로 나눠서(모듈러연산) 나온 나머지
로 배열의 고유한 index를 생성
한다.
예를 들면 010-2222-2222
이라는 key값
을 해시함수에 넣어서 202
라는 Hash값
이 나온다고 하면
전체 배열의 크기를Capacity
8로 정했다면 모듈러 연산(%8)해서 나온 나머지는 2
가 된다. 이것이 해시 테이블의 고유한 index값이 된다. 이러한 고유한 공간을 bucket 혹은 slot 이라고 한다.
그러나 이 방식에 문제가 하나가 있는데 다른 key값을 해쉬함수에 넣었을 때 동일한 값이 나오면 같은 공간에 어떻게 여러개의 데이터를 저장할 것인지에 대한 문제가 발생한다.
이러한 문제를 해결하기 위해서는 비어있는 다른 주소를 활용하는 개방주소법
과 동일한 bucket에 대해 추가적인 자료구조를 이용(Linked list, Self-Balancing Binary Search Tree 등등)해서 Chaining 방식으로 해결한 분리 연결법
이 있다.
struct hash_elem *
hash_find (struct hash *h, struct hash_elem *e) {
// project3
return find_elem (h, find_bucket (h, e), e); // 찾으면 해당 hash_elem을 반환하고 못찾으면 NULL 반환
}
static struct list *
find_bucket (struct hash *h, struct hash_elem *e) {
// project3
// hash()로 찾은 해시 값(hash_bytes)과 bucket_cnt - 1개를 bit 연산하여 해당 요소가 들어있는 인덱스를 찾고
size_t bucket_idx = h->hash (e, h->aux) & (h->bucket_cnt - 1);
return &h->buckets[bucket_idx]; // SPT의 어떤 buckets인지를 반환
}
struct supplemental_page_table {
// project3
struct hash spt_hash; // SPT 테이블로 hash로 관리하기 추가
};
struct page {
...
/* Your implementation */
// project3
struct hash_elem hash_elem; // hash_elem으로 page를 찾아서 va로 접근
bool writable; // 해당 page가 writable 가능한지를 저장
...
};
};
struct frame {
void *kva;
struct page *page;
// project3
struct list_elem frame_elem; // 전역변수로 들어가는 frame table의 요소인 frame elem 추가
};
...
//project 3
unsigned page_hash(const struct hash_elem *p_, void *aux UNUSED);
bool page_less(const struct hash_elem *a, const struct hash_elem *b, void *aux UNUSED);
bool insert_page(struct hash *pages, struct page *p);
bool delete_page(struct hash *pages, struct page *p);
// project3
struct list frame_table; // frametable을 리스트로 관리하기 위해 전역 변수 선언
struct list_elem* start; // 쫓아낼(evict) page를 찾기 위해 사용하는 전역변수
/* 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) {
/* TODO: Fill this function. */
// project 3
struct page *page = (struct page*)malloc(sizeof(struct page));
// 1. 임시 page를 만든다.
struct hash_elem *e;
/*
2. 인자로 전달받은 va가 가리키는 page 시작 주소를(BITMASK로 offset은 0으로 설정된)
반환하여 임시 page->va 담아준다.
*/
page->va = pg_round_down(va);
// 3. SPT에서 hash_elem과 같은 요소가 있으면 hash_elem을 리턴 아니면 NULL 리턴
e = hash_find(&spt->spt_hash, &page->hash_elem);
free(page);
// 4. 인자로 받은 va로 찾은 hash_elem이 page내 존재하면 page 반환, 아니면 NULL 반환
return e != NULL ? hash_entry(e,struct page,hash_elem) : NULL;
}
SPT내에 인자로 받은 va가 있는지를 찾는 함수. SPT내에 있다면 그 page를 리턴한다.
/* 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. */
// project 3
return insert_page(&spt->spt_hash, page);
}
// SPT내에 인자로 주어진 page 내의 hash_elem이 없는지 확인하고 삽입한다.
bool insert_page(struct hash *pages, struct page *p){
// SPT에 hash_elem을 넣을 수 있다는 것은 찾아서 넣으려는 요소가 내부에 없다(old == NULL)라는 말이다.
if(!hash_insert(pages, &p->hash_elem))
return true;
else
return false;
}
SPT내에 요청한 page의 hash_elem이 없다면 SPT내에 넣는 함수
/* Evict one page and return the corresponding frame.
* Return NULL on error.*/
static struct frame *
vm_evict_frame (void) {
struct frame *victim UNUSED = vm_get_victim ();
/* TODO: swap out the victim and return the evicted frame. */
// project 3
swap_out(victim->page);
return victim;
}
vm_evict_frame 함수는 frame 공간을 디스크로 내리는 swap out을 진행하는 함수이다.
/* 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. */
// project 3
struct thread *curr = thread_current();
struct list_elem *e = start;
// 최근에 방출된 frame_table의 위치부터 탐색 해야되기 때문에 첫번째 for문 사용
for(start = e; start != list_end(&frame_table); start = list_next(start)){
victim = list_entry(start, struct frame, frame_elem);
// page table entry가 최근에 액세스된 경우 (pml4_is_accessed에서 true반환 시)
if (pml4_is_accessed(curr->pml4, victim->page->va))
// 함수에서 accessed bit을 1-> 0으로 설정
pml4_set_accessed(curr->pml4, victim->page->va, 0);
else // accessed bit가 0이면 쫓겨날(victim)대상이 된다.
return victim;
}
// 최근 방출된 위치 기준으로 한바퀴를 돌아도 쫓아낼 대상을 찾지 못하면 처음부터 끝까지 다시 탐색
for(start = list_begin(&frame_table); start != e; start = list_next(start)){
victim = list_entry(start, struct frame, frame_elem);
if(pml4_is_accessed(curr->pml4, victim->page->va))
pml4_set_accessed(curr->pml4, victim->page->va, 0);
else
return victim;
}
return victim;
}
실제로 물리 frame에서 해당 정보를 지우는 건 vm_get_victim()에서 처리하고
victim을 찾는 방식은 clock 알고리즘으로 구현되어있다.
최근에 방출된 frame_table의 위치부터 탐색 해야되기 때문에
그 위치를 저장한 값을 전역변수 start로 저장했었고 그 위치부터 첫번째 for문
으로 쫓아낼 대상을 찾는다.
그럼에도 불구하고 쫓아낼 수 없는 경우가 생길 수도 있기 때문에
전체적으로 한번 더 탐색해서 쫓아낼 대상을 찾는 두번째 for문
을 실행하게 된다.
/* 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) {
/* TODO: Fill this function. */
// project 3
struct frame *frame = (struct frame*)malloc(sizeof(struct frame));
// user pool에서 커널 가상주소 공간으로 1page 할당하고 frame의 kva(kernel virtual address)에 넣는다
frame->kva = palloc_get_page(PAL_USER);
if(frame->kva == NULL) // frame에서 가용할 수 있는 공간이 없다면
{
frame = vm_evict_frame(); // 쫓아낼 frame을 받음
frame->page = NULL;
return frame;
}
list_push_back(&frame_table,&frame->frame_elem); // frametable에 frame_elem을 넣음(맨끝에)
frame->page = NULL;
ASSERT (frame != NULL);
ASSERT (frame->page == NULL);
return frame;
}
vm_get_frame은 palloc_get_page 함수로 사용 가능한 frame을 가져온다.
사용가능한 page가 없다면 frame을 제거해서 memory를 확보하는 함수이다.
/* Claim the page that allocate on VA. */
bool
vm_claim_page (void *va UNUSED) {
struct page *page = NULL;
/* TODO: Fill this function */
// project 3
page = spt_find_page(&thread_current()->spt,va);
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 (); // frame 가져오기
/* Set links */
frame->page = page;
page->frame = frame;
/* TODO: Insert page table entry to map page's VA to frame's PA. */
// page에 선언한 writable 값의 유무에 따라서 swap_in 실행
// install_page 실행시 pml4_get_page()가 NULL이고,
// pml4_set_page()에서 writable의 값을 넣어서 true일 경우 swap_in 가능
if(install_page(page->va, frame->kva, page->writable)){
return swap_in(page, frame->kva);
}
return false;
}
vm_claim_page 함수로 va
를 가진 page가 SPT 내에 있다면
vm_do_claim_page 함수로 virtual memory 공간의 page를 physical memory에 할당한다.
frame을 가져와서 frame과 page의 서로 연결 해준다.
install_page 함수는 유저 가상 주소(UPAGE)에서 커널 가상 주소(KPAGE)로의 mapping을 page table에 추가함
인자로 받는 writable이 true면 user process가 page를 수정할 수 있고 그렇지 않으면 read-only
KPAGE는 user pool에서 가져온 page여야 한다.
UPAGE가 이미 mapping되었거나 메모리 할당이 실패하면 false를 반환한다.
성공하면 true를 반환하고 swap_in함수가 실행된다.
/* 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);
}
SPT를 hash table로 구현하기 때문에 hash_init()을 통해 초기화한다.
hash_init시에 필요한 추가적인 함수들은 아래와 같이 추가한다.
// hash_elem가 있는 page의 va로 hash 값을 return해줌
unsigned page_hash(const struct hash_elem *p_ , void *aux UNUSED){
const struct page *p = hash_entry(p_, struct page, hash_elem);
// hash_elem만 알고 있으니 hash_entry()로 hash_elem이 있는 해당 페이지의 주소를 가져오고
return hash_bytes(&p->va, sizeof p->va); // page의 va에 암호화된 해시 값 리턴
}
// 두 page 요소에 대해 page의 주소값을 비교하는 함수, page a가 page b를 앞서면 return true
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;
}