고찰같다(1)이 너무 길어지는 바람에
따로 소스 파일을 정리할 공간이 필요했다.
핀토스 1~3주차 때, 개념 공부를 확실히 하고 들어가야 한다는 것을 느껴서
이번에는 과하다 싶을 정도로 정리를 했다.
like 국자세발자전콥터 코드
(국자, 세발자전거, 프로펠러는 나무위키, 엔진은 현대위아 출처)
위 그림은 내가 짜는 코드와 비슷하다.
좋아보이면 갖다붙이기 때문이다.
그리고 그게 이번엔 글이 됐다.
지난 주는 다른 공부를 해야했기 때문에, 이번주차 핀토스는 늦게 시작하게 되었다.
그래서 이제야 코드를 볼 기회가 생기게 되었다.
쿵짝짝 쿵짝짝
사쿠라여?
출처: 타짜
마음을 다 잡고... 코드를 봐야겠다...
사실 일전의 포스팅에서 아마 함수별 역할이란 역할은 죄다 써놨을 것이다.
다만 이게 디스크 조각 마냥 여기저기 흩어져있어서
디스크 조각 모음처럼 하나로 모으는 과정이 필요할 것 같다.
필요한 코드, 사용한 코드에 대해서만 쓰려고 한다.
서술된 함수 순서가 소스 파일 내의 함수 순서랑 다르다는 생각이 들 수도 있다.
구현한 차례대로 정리되어있기 때문이다.
또 process.c다.
경악을 금치 못한다.
노답 3형제 등장이다.
출처: https://www.teamblind.com/kr/post/주식-개노답-삼형제-1rTZdtd8
노답 함수는 아래 세 개가 전부가 아니긴 하다.
여담으로 사실 주식은 재밌다.
static 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. */
}
접근하면 그제서야 파일로부터 segment를 불러온다.
/* Loads a segment starting at offset OFS in FILE at address
* UPAGE. In total, READ_BYTES + ZERO_BYTES bytes of virtual
* memory are initialized, as follows:
*
* - READ_BYTES bytes at UPAGE must be read from FILE
* starting at offset OFS.
*
* - ZERO_BYTES bytes at UPAGE + READ_BYTES must be zeroed.
*
* The pages initialized by this function must be writable by the
* user process if WRITABLE is true, read-only otherwise.
*
* Return true if successful, false if a memory allocation error
* or disk read error occurs. */
static 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);
while (read_bytes > 0 || zero_bytes > 0) {
/* Do calculate how to fill this page.
* We will read PAGE_READ_BYTES bytes from FILE
* and zero the final PAGE_ZERO_BYTES bytes. */
size_t page_read_bytes = read_bytes < PGSIZE ? read_bytes : PGSIZE;
size_t page_zero_bytes = PGSIZE - page_read_bytes;
/* TODO: Set up aux to pass information to the lazy_load_segment. */
void *aux = NULL;
if (!vm_alloc_page_with_initializer (VM_ANON, upage,
writable, lazy_load_segment, aux))
return false;
/* Advance. */
read_bytes -= page_read_bytes;
zero_bytes -= page_zero_bytes;
upage += PGSIZE;
}
return true;
}
이상한 점이 보일 것이다.
page_read_bytes랑 page_zero_bytes는 계산만 하고 다른 데에 쓰지를 않는다.
아마도 aux에 이를 적당히 사용해서 넣어야할 것 같다.
아무튼 파일 크기만큼 페이지를 생성한다.
/* Create a PAGE of stack at the USER_STACK. Return true on success. */
static bool
setup_stack (struct intr_frame *if_) {
bool success = false;
void *stack_bottom = (void *) (((uint8_t *) USER_STACK) - PGSIZE);
/* TODO: Map the stack on stack_bottom and claim the page immediately.
* TODO: If success, set the rsp accordingly.
* TODO: You should mark the page is stack. */
/* TODO: Your code goes here */
return success;
}
유저 스택을 생성한다.
struct hash_elem {
struct list_elem list_elem;
};
처음에 봤을 때 굉장히 신기하게 생겼다고 느꼈다.
구조체 안에 구조체가 통으로 있네?
그럼 사실상 hash_elem과 list_elem이 같은 게 아닐까?
다형성같기도 하다.
그리고 나는 처음에 요 hash_elem이 'hash 값(value)이 담긴 녀석들을 연결하는구나'라고 생각했다.
완전 잘못된 생각이었다.
얘는 hash table을 이어주는 녀석이다.
무슨 말인지는 아래에서 보충 설명하겠다.
#define hash_entry(HASH_ELEM, STRUCT, MEMBER) \
((STRUCT *) ((uint8_t *) &(HASH_ELEM)->list_elem \
- offsetof (STRUCT, MEMBER.list_elem)))
그럼 얘는 사실상 메소드 오버라이딩이 되겠다.
근데 왤까.. 다 맞다고 해주는 것 같은 느낌도 든다.
/* Hash table. */
struct hash {
size_t elem_cnt; /* Number of elements in table. */
size_t bucket_cnt; /* Number of buckets, a power of 2. */
struct list *buckets; /* Array of `bucket_cnt' lists. */
hash_hash_func *hash; /* Hash function. */
hash_less_func *less; /* Comparison function. */
void *aux; /* Auxiliary data for `hash' and `less'. */
};
elem_cnt는 아마도 hash_elem이 몇 개 있는지에 관한 것
bucket_cnt는 bucket?이 몇 개 있는지
struct list는 bucket? 리스트
hash_hash_func는 hash 값으로 변환시키는 함수
hash_less_func는 뭔가 값을 비교하는 함수
aux는 모르겠다.
그런 생각이 들 수 있다.
키와 값 쌍이 없고 이상한 놈들만 있다는 생각.
정확하게 해시 테이블은 배열 기반의 자료구조이다.
배열은 인덱스를 알면 O(1)의 시간복잡도로 접근이 가능한데
이 인덱스를 '키'처럼 생각해서 관리하는 것이다.
그러니까 아무튼 배열이 없다는 말이다.
그럼 진짜 해시인가? 라는 의문이 들기도 한다. 그래서 hash.c를 쭉 훑어봤는데
결론은 해시 맞다. O(1)로 접근한다.
그리고 현재 해시 구조체에서 buckets라는 수상한 녀석을 확인할 수 있다.
여기서 유일하게 리스트인데, 아마도 얘랑 hash_elem이랑 관련이 있지 않을까 싶다.
또한 bucket's'이므로 얘는 결국 bucket을 관리하는 거고, bucket이 hash_elem을 가지고 있을거라 생각한다.
하지만 bucket이라는 구조체는 없었는데, hash_elem 구조체를 가지고 있는 구조체는
bucket이 키값을 담는 리스트인 것은 맞으나 hash elem이랑은 깊은 관련이 없다.
삭선이 많아지고 있다...
hash_elem은 범용적으로 쓰이는 것 같다.
해시테이블끼리도 연결하고 bucket의 list에서도 리스트 값끼리 연결할 때도 쓰이는 것 같다.
/* A hash table iterator. */
struct hash_iterator {
struct hash *hash; /* The hash table. */
struct list *bucket; /* Current bucket. */
struct hash_elem *elem; /* Current hash element in current bucket. */
};
이다.
처음에 bucket을 관리하는 녀석인 줄 알았으나
아니다. 얘는 hash table들을 관리하는 녀석이다
쉽게 말해 hash table끼리 연결리스트로 연결되어있다는 의미이다.
인줄 알았는데 해시 테이블을 순회할 때 사용하는 녀석이라고 한다.
따라서 해시 테이블의 정보와 현재 버켓, 현재 값을 가리키고 있다.
이쯤되면 hash가 도대체 어떤 놈인지, 어떻게 작동하는 놈인지 궁금해진다.
내 가설은 이렇다.
우선 해시는 배열로 관리되어야 한다. 그러니 일단 말록으로 주소들을 할당받는다.
근데 여기서 하나, hash.c에 is_power_of_2라는 함수가 있다. (2의 힘!)
즉, hash 배열은 2의 제곱수로 관리되는게 아닐까?
| 0x0000 | 0x0001 | 0x0002 | 0x0003 |
|---|---|---|---|
대략 이런 느낌이다.
각 주소가 결국 bucket이라고 생각하면 될 것 같다.
그리고 아래 hash.c의 hash_insert에서 확인할 수 있듯이 bucket은 value를 담는 통인 것 같다.
그럼 bucket은 무엇일까?
이건 find_bucket의 함수를 보고 유추해볼 수 있다. 이 역시 hash.c에 있다.
find_bucket의 반환값은 list*이다. 즉, list의 주소를 반환한다.
근데 보면
return &h->buckets[bucket_idx];
이런식으로 되어있다.
즉 buckets을 인덱스로 접근을 했는데, 그 값을 반환하는게 아니고 그 주소를 반환하는 것이다.
위의 표를 예시로 들면
| 0x0000 | 0x0001 | 0x0002 | 0x0003 |
|---|---|---|---|
| A | B | C | D |
여기서 0x0000을 반환한 것이다.
반환값은 list* 이고 이게 0x0000과 같다는 것이다.
쉽게 생각해보자.
list* a = 0x0000이면
*a는 위의 배열에서 A를 의미한다.
즉 배열은 다음과 같은 구조일 것이다.
| 0x0000 | 0x0001 | 0x0002 | 0x0003 |
|---|---|---|---|
| list | list | list | list |
물론 얘는 연결리스트이기 때문에 값들이 여기에 담겨있지는 않다.
리스트의 구조를 다시 떠올려보면
/* List. */
struct list {
struct list_elem head; /* List head. */
struct list_elem tail; /* List tail. */
};
이렇게 돼 있다.
따라서 head가 해당 해시값을 가진 value를 가리키고 있을 것이다.
이쯤되면 이 해시가 어떤 구조인지 이해가 될 것이다.
https://velog.io/@mogiyoon/Jungle-Second#해시테이블hash-table
얘는 해시 체이닝으로 관리하고 있다.
해시를 초기화하는 함수인데, 추후 함수를 수정할 일이 있으면 그때 코드도 올리겠다.
struct hash_elem *
hash_insert (struct hash *h, struct hash_elem *new) {
struct list *bucket = find_bucket (h, new);
struct hash_elem *old = find_elem (h, bucket, new);
if (old == NULL)
insert_elem (h, bucket, new);
rehash (h);
return old;
}
find_bucket으로 bucket을 찾고, 해당 bucket에서 find_elem 함수로 같은 원소가 있는지 찾는다.
해당 원소가 없다면(NULL) 해당 해시 테이블에 새로운 원소를 삽입한다.
static struct list *
find_bucket (struct hash *h, struct hash_elem *e) {
size_t bucket_idx = h->hash (e, h->aux) & (h->bucket_cnt - 1);
return &h->buckets[bucket_idx];
}
해시테이블의 해시 함수를 사용해서 나온 값과 bucket cnt를 비트 연산하여 bucket_idx를 계산한다.
자세한 계산 방법은 아직 잘 모르겠다...
그리고 흥미로운 걸 발견했는데,
/* Element per bucket ratios. */
#define MIN_ELEMS_PER_BUCKET 1 /* Elems/bucket < 1: reduce # of buckets. */
#define BEST_ELEMS_PER_BUCKET 2 /* Ideal elems/bucket. */
#define MAX_ELEMS_PER_BUCKET 4 /* Elems/bucket > 4: increase # of buckets. */
버켓별로 element의 최대값을 두는 것 같다.
아마 불균형한 AVL tree가 재조정하듯이 버킷별로 element가 최댓값이 됐을 때 재조정하는 듯 하다.
vm.c를 먼저 작성해버렸는데, 헤더부터 보는게 역시 맞았다.
enum vm_type {
/* page not initialized */
VM_UNINIT = 0,
/* page not related to the file, aka anonymous page */
VM_ANON = 1,
/* page that realated to the file */
VM_FILE = 2,
/* page that hold the page cache, for project 4 */
VM_PAGE_CACHE = 3,
/* Bit flags to store state */
/* Auxillary bit flag marker for store information. You can add more
* markers, until the value is fit in the int. */
VM_MARKER_0 = (1 << 3),
VM_MARKER_1 = (1 << 4),
/* DO NOT EXCEED THIS VALUE. */
VM_MARKER_END = (1 << 31),
};
vm의 타입에 관한 것인가보다.
우선 uninit, anon, file은 page의 타입과 관련이 있따.
나머지는 아직 모르겠다.
사실 헤더를 다시 작성하게 된 것은 얘 때문이다.
얘 구조가 쉬운 줄 알았더니 오묘~하다.
struct page {
const struct page_operations *operations;
void *va; /* Address in terms of user space */
struct frame *frame; /* Back reference for frame */
/* Your implementation */
/* Per-type data are binded into the union.
* Each function automatically detects the current union */
union {
struct uninit_page uninit;
struct anon_page anon;
struct file_page file;
#ifdef EFILESYS
struct page_cache page_cache;
#endif
};
};
va는 유저풀에서 할당된 가상 주소이다.
frame은 물리메모리에 해당하는 커널 가상 주소를 가지고 있다.
implementation에는 hash elem이나 writable이 들어갈 예정이다.
union은 안에 있는 멤버 변수 중 하나만 쓰는 건데, 여기서는 uninit, anon, file 중에 하나를 쓴다.
하나 설명하지 않은게 있다.
사실 헤더를 따로 만든 것은 page_operations 때문이다.
struct page_operations {
bool (*swap_in) (struct page *, void *);
bool (*swap_out) (struct page *);
void (*destroy) (struct page *);
enum vm_type type;
};
우선 하나 알아둘 것이
C언어에서
bool (*swap_in) (struct page *, void *);
이렇게 선언하는 것은 오른쪽 괄호에 있는 것을 매개변수로 받는 함수 포인터를 선언하는 것이다.
그리고 그 밑에 매크로가 있는데,
#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)
이건 위의 page_operations를 쉽게 사용하기 위한 매크로일 것이다.
그럼 얘네가 어떻게 사용이 되는가?
uninit, anon이나 file에서 확인할 수 있다.
이 모든 건 uninit 때문에 작성됐다.
때문에 page_operation이 어떻게 사용되는지 궁금하다면,
혹은 lazy loading에 대해서 자세히 알고싶다면
uninit.c를 참고하도록 하자.
이름 굉장히 길다.
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. */
/* TODO: Insert the page into the spt. */
}
err:
return false;
}
얘는 spt안에 가상 주소에 해당하는 페이지가 없을 경우 uninit 페이지를 만드는 함수이다.
새로 만든 페이지는 타입에 따라 다르게 초기화 해야한다.
struct page *
spt_find_page (struct supplemental_page_table *spt UNUSED, void *va UNUSED) {
struct page *page = NULL;
/* TODO: Fill this function. */
return page;
}
spt에서 원하는 페이지를 찾는 함수이다.
spt를 어떤 자료구조로 만들었는지에 따라 내부 코드도 달라질 것이다.
bool
spt_insert_page (struct supplemental_page_table *spt UNUSED,
struct page *page UNUSED) {
int succ = false;
/* TODO: Fill this function. */
return succ;
}
spt에 새로운 페이지를 넣는 함수이다.
새로운 페이지를 넣는데 실패하거나, 이미 있는 페이지인 경우 false를 리턴한다.
static struct frame *
vm_get_frame (void) {
struct frame *frame = NULL;
/* TODO: Fill this function. */
ASSERT (frame != NULL);
ASSERT (frame->page == NULL);
return frame;
}
페이지에 필요한 실제 물리 공간을 할당받는 함수이다.
/* Claim the page that allocate on VA. */
bool
vm_claim_page (void *va UNUSED) {
struct page *page = NULL;
/* TODO: Fill this function */
return vm_do_claim_page (page);
}
설명이 너무 적어서 당황스럽긴했는데
spt에 페이지 정보들이 다 저장돼 있다는 것을 기본으로 생각하자.
그럼 우리는 그걸 꺼내서 쓰면 될 것이다.
/* 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. */
return swap_in (page, frame->kva);
}
PTE에 페이지의 물리 주소를 등록할 차례이다.
PTE하면 또 userprog에서 많이 봤을 것이다.
pml4와 관련된 함수를 사용하면 될 것 같다.
/* Return true on success */
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);
}
page fault가 났을 때 호출하는 fault함수이다.
/* Copy supplemental page table from src to dst */
bool
supplemental_page_table_copy (struct supplemental_page_table *dst UNUSED,
struct supplemental_page_table *src UNUSED) {
}
src spt를 dst spt로 복사한다.
/* Uninitlialized page. The type for implementing the
* "Lazy loading". */
struct uninit_page {
/* Initiate the contets of the page */
vm_initializer *init;
enum vm_type type;
void *aux;
/* Initiate the struct page and maps the pa to the va */
bool (*page_initializer) (struct page *, enum vm_type, void *kva);
};
lazy loading을 위한 구조이다.
눈에 잘 익혀두자.
static const struct page_operations uninit_ops = {
.swap_in = uninit_initialize,
.swap_out = NULL,
.destroy = uninit_destroy,
.type = VM_UNINIT,
};
swap_in 하면 uninit_initialize 함수를,
swap_out 하면 NULL을,
destroy 하면 uninit_destory 함수를 실행한다.
/* DO NOT MODIFY this function */
void
uninit_new (struct page *page, void *va, vm_initializer *init,
enum vm_type type, void *aux,
bool (*initializer)(struct page *, enum vm_type, void *)) {
ASSERT (page != NULL);
*page = (struct page) {
.operations = &uninit_ops,
.va = va,
.frame = NULL, /* no frame for now */
.uninit = (struct uninit_page) {
.init = init,
.type = type,
.aux = aux,
.page_initializer = initializer,
}
};
}
새로운 페이지를 만들어서 spt에 저장할 때 실행하는 함수이다.
operation을 uninit_ops를
va에 해당 페이지의 가상 주소를
frame에 NULL을
(아직 페이지폴트가 일어나지 않았기 때문에 할당받은 물리 메모리 혹은 그에 관련된 커널 가상주소가 없다.)
uninit에 uninit page 구조체에 넣을 정보를 담는다.
/* Initalize the page on first fault */
static bool
uninit_initialize (struct page *page, void *kva) {
struct uninit_page *uninit = &page->uninit;
/* Fetch first, page_initialize may overwrite the values */
vm_initializer *init = uninit->init;
void *aux = uninit->aux;
/* TODO: You may need to fix this function. */
return uninit->page_initializer (page, uninit->type, kva) &&
(init ? init (page, aux) : true);
}
누구냐 넌
그럼 여태까지 살펴봤던 함수들로 uninit page와 lazy load가 어떤 일을 벌이는지
간단하게 살펴보겠다.
사실 아직은 load_segment에서
vm_alloc_page_with_initializer (VM_ANON, upage, writable, lazy_load_segment, aux)
왜 이런식(VM_ANON)으로 호출하는지 모르겠긴하다.
하지만 lazy_load에 대해 이해하기에 좋은 것 같아 들고왔다.
위 함수를 실행시키면 당연한 얘기지만, vm_alloc_page_with_initializer 안의 코드가 동작할 것이다.
혹시나 아직 vm_alloc_page_with_initializer을 구현하지 않았다면,
그래서 혼자 힘으로 구현하고 싶다면 넘기자.
현재 구현 진행 중인 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;
/* 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* new_page = malloc(sizeof(struct page));
if (new_page == NULL) {
goto err;
}
bool (*init_func)(struct page *, enum vm_type, void *);
switch (VM_TYPE(type))
{
case VM_ANON:
init_func = anon_initializer;
break;
case VM_FILE:
init_func = file_backed_initializer;
break;
default:
goto err;
}
uninit_new(new_page, upage, init, type, aux, init_func);
new_page->writable = writable;
/* TODO: Insert the page into the spt. */
if (!spt_insert_page(spt, new_page)) {
//TODO: Release page
goto err;
}
return true;
}
err:
return false;
}
여기서 중요한 부분은 uninit_new 함수 부분이다.
uninit_new(new_page, upage, init, type, aux, init_func);
이 부분
새로 할당한 new_page, 유저 가상 주소가 담긴 upage, init, type, aux
그리고 페이지 타입에 따른 타입 초기화함수가 전달된다.
앞서 보여준
vm_alloc_page_with_initializer (VM_ANON, upage, writable, lazy_load_segment, aux)
얘로 예시를 들면
uninit_new(new_page, upage, lazy_load_segment, VM_ANON, aux, anon_initializer);
와 같이 넘어간다고 할 수 있다.
그럼 uninit_new 함수에서는
void
uninit_new (new_page, upage, lazy_load_segment, VM_ANON, aux, anon_initializer) {
ASSERT (page != NULL);
*new_page = (struct page) {
.operations = &uninit_ops,
.va = upage,
.frame = NULL, /* no frame for now */
.uninit = (struct uninit_page) {
.init = lazy_load_segment,
.type = VM_ANON,
.aux = aux,
.page_initializer = anon_initializer,
}
};
}
와 같이 된다. 그럼 new_page의 구조체를 살펴보면
struct page new_page{
uninit_ops //const struct page_operations *operations;
upage //void *va; /* Address in terms of user space */
NULL //struct frame *frame; /* Back reference for frame */
uninit //struct uninit_page uninit;
};
이런 상태다.
uninit 구조체도 살펴보면
struct uninit_page uninit{
lazy_load_segment //vm_initializer *init;
VM_ANON //enum vm_type type;
aux //void *aux;
anon_initializer //bool (*page_initializer);
};
과 같은 상태이다.
이렇게 저장돼있고
국방부의 시계가 돌아가듯 page_fault_handler의 시계도 돌아가기 시작한다.
page_fault_handler는 결국 vm_do_claim_page를 건드리게 되고
이 함수는 swap_in (page, frame->kva)을 리턴한다.
swap_in 기억나는가?
이는 page 구조체의 operations 구조체와 관련된 매크로이다.
현재 new_pages의 operation 구조체는 uninit_ops 구조체이며
내부의 swap_in에는 uninit_initialize 함수가 있다.
즉, vm_do_claim_page의 결과로
uninit_initialize(page, frame->kva)가 실행된다.
이를 new_page 구조체와 함께 다시 해석해보자.
/* Initalize the page on first fault */
static bool
uninit_initialize (new_page, frame->kva) {
struct uninit_page *uninit = &page->uninit; //new_page->uninit
vm_initializer *init = uninit->init; //lazy_load_segment
void *aux = uninit->aux; // aux
return uninit->page_initializer (page, uninit->type, kva) &&
(init ? init (page, aux) : true);
}
여기서 중요한 건 역시 return문이라고 생각한다.
return uninit->page_initializer (page, uninit->type, kva) && (init ? init (page, aux) : true);
and 연산자를 기준으로
page_initializer (page, uninit->type, kva)
와
(init ? init (page, aux) : true)
로 나눠볼 수 있는데,
삼항연산자 코드는 init이 NULL이냐 아니냐에 따라 init을 실행시키거나 true를 반환하는 것을 알 수 있다.
위에서 init이 NULL이 아니므로 return 문은 매개변수를 생략하고 다음처럼 해석된다.
return anon_initializer && lazy_load_segment
결국 page_fault가 발생하면 타입에 따른 초기화 함수 및 vm_initializer 함수가 실행된다.
static struct frame *
vm_get_victim (void) {
struct frame *victim = NULL;
/* TODO: The policy for eviction is up to you. */
return victim;
}
swap out 당할 페이지를 고른다.
정책에 따라 알고리즘을 구현하고 frame을 반환해서 최종적으로 victim frame을 반환하면 된다.
static struct frame *
vm_evict_frame (void) {
struct frame *victim UNUSED = vm_get_victim ();
/* TODO: swap out the victim and return the evicted frame. */
return NULL;
}
vm_get_victim으로 얻은 frame을 swap out 시키고
이런저런 처리를 한 뒤 frame을 반환한다.
anon.h는 별볼일 없다...
anon.c에는 다음과 같은 구조체가 정의되어 있다.
/* DO NOT MODIFY this struct */
static const struct page_operations anon_ops = {
.swap_in = anon_swap_in,
.swap_out = anon_swap_out,
.destroy = anon_destroy,
.type = VM_ANON,
};
그 누구랑 이름이 같다.
swap을 다루기 위해서는 disk를 건드려야한다.
이쯤되니 드는 생각이, 핀토스 프로젝트는 짜임새가 참 좋다는 생각이 든다.
프로젝트 4가 파일 시스템인데, 그 프로젝트를 이해하기 위한 작업이라는 생각이 든다.
유저 프로그램 역시, 가상 메모리를 위한 커다란 준비였다고 볼 수 있는 것 같다.
/* Returns the disk numbered DEV_NO--either 0 or 1 for master or
slave, respectively--within the channel numbered CHAN_NO.
Pintos uses disks this way:
0:0 - boot loader, command line args, and operating system kernel
0:1 - file system
1:0 - scratch
1:1 - swap
*/
struct disk *
disk_get (int chan_no, int dev_no) {
ASSERT (dev_no == 0 || dev_no == 1);
if (chan_no < (int) CHANNEL_CNT) {
struct disk *d = &channels[chan_no].devices[dev_no];
if (d->is_ata)
return d;
}
return NULL;
}
어째보면 swap test의 핵심이 아닐까 생각한다.
그리고 우리가 유심히 봐야하는 부분은
0:0 - boot loader, command line args, and operating system kernel
0:1 - file system
1:0 - scratch
1:1 - swap
이 부분인데, 우리는 swap disk를 쓸 예정이므로
swap_dist = disk_get(1, 1)처럼 할당하면 되지 않을까 생각한다.
/* Returns the size of disk D, measured in DISK_SECTOR_SIZE-byte
sectors. */
disk_sector_t
disk_size (struct disk *d) {
ASSERT (d != NULL);
return d->capacity;
}
disk_sector_t를 반환하는 함수이다.
1섹터는 512바이트와 같다고 한다.
따라서 페이지씩 기록하려면 8섹터 단위로 기록하면 될 것 같다.