Pintos 구조 of 프로젝트 3 고찰같다 (2)

모기·2025년 6월 3일

Pintos.Gochal

목록 보기
6/7

고찰같다(1)이 너무 길어지는 바람에
따로 소스 파일을 정리할 공간이 필요했다.

핀토스 1~3주차 때, 개념 공부를 확실히 하고 들어가야 한다는 것을 느껴서
이번에는 과하다 싶을 정도로 정리를 했다.

like 국자세발자전콥터 코드

(국자, 세발자전거, 프로펠러는 나무위키, 엔진은 현대위아 출처)

위 그림은 내가 짜는 코드와 비슷하다.
좋아보이면 갖다붙이기 때문이다.
그리고 그게 이번엔 글이 됐다.

지난 주는 다른 공부를 해야했기 때문에, 이번주차 핀토스는 늦게 시작하게 되었다.
그래서 이제야 코드를 볼 기회가 생기게 되었다.



쿵짝짝 쿵짝짝




사쿠라여?

출처: 타짜

마음을 다 잡고... 코드를 봐야겠다...

사실 일전의 포스팅에서 아마 함수별 역할이란 역할은 죄다 써놨을 것이다.
다만 이게 디스크 조각 마냥 여기저기 흩어져있어서
디스크 조각 모음처럼 하나로 모으는 과정이 필요할 것 같다.

Source file

필요한 코드, 사용한 코드에 대해서만 쓰려고 한다.
서술된 함수 순서가 소스 파일 내의 함수 순서랑 다르다는 생각이 들 수도 있다.
구현한 차례대로 정리되어있기 때문이다.

Process

-process.c-

process.c다.
경악을 금치 못한다.
노답 3형제 등장이다.

출처: https://www.teamblind.com/kr/post/주식-개노답-삼형제-1rTZdtd8

노답 함수는 아래 세 개가 전부가 아니긴 하다.

여담으로 사실 주식은 재밌다.

lazy_load_segment

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를 불러온다.

load_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에 이를 적당히 사용해서 넣어야할 것 같다.
아무튼 파일 크기만큼 페이지를 생성한다.

setup_stack

/* 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;
}

유저 스택을 생성한다.

Hash

-hash.h-

struct hash_elem

struct hash_elem {
	struct list_elem list_elem;
};

처음에 봤을 때 굉장히 신기하게 생겼다고 느꼈다.
구조체 안에 구조체가 통으로 있네?
그럼 사실상 hash_elem과 list_elem이 같은 게 아닐까?
다형성같기도 하다.

그리고 나는 처음에 요 hash_elem이 'hash 값(value)이 담긴 녀석들을 연결하는구나'라고 생각했다.
완전 잘못된 생각이었다.
얘는 hash table을 이어주는 녀석이다.
무슨 말인지는 아래에서 보충 설명하겠다.

hash_entry

#define hash_entry(HASH_ELEM, STRUCT, MEMBER)                   \
	((STRUCT *) ((uint8_t *) &(HASH_ELEM)->list_elem        \
		- offsetof (STRUCT, MEMBER.list_elem)))

그럼 얘는 사실상 메소드 오버라이딩이 되겠다.

근데 왤까.. 다 맞다고 해주는 것 같은 느낌도 든다.

hash

/* 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에서도 리스트 값끼리 연결할 때도 쓰이는 것 같다.

hash_iterator

/* 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끼리 연결리스트로 연결되어있다는 의미이다.
인줄 알았는데 해시 테이블을 순회할 때 사용하는 녀석이라고 한다.
따라서 해시 테이블의 정보와 현재 버켓, 현재 값을 가리키고 있다.

how to work

이쯤되면 hash가 도대체 어떤 놈인지, 어떻게 작동하는 놈인지 궁금해진다.

내 가설은 이렇다.

우선 해시는 배열로 관리되어야 한다. 그러니 일단 말록으로 주소들을 할당받는다.
근데 여기서 하나, hash.c에 is_power_of_2라는 함수가 있다. (2의 힘!)
즉, hash 배열은 2의 제곱수로 관리되는게 아닐까?

0x00000x00010x00020x0003

대략 이런 느낌이다.
각 주소가 결국 bucket이라고 생각하면 될 것 같다.
그리고 아래 hash.c의 hash_insert에서 확인할 수 있듯이 bucket은 value를 담는 통인 것 같다.

그럼 bucket은 무엇일까?
이건 find_bucket의 함수를 보고 유추해볼 수 있다. 이 역시 hash.c에 있다.
find_bucket의 반환값은 list*이다. 즉, list의 주소를 반환한다.
근데 보면

return &h->buckets[bucket_idx];

이런식으로 되어있다.
즉 buckets을 인덱스로 접근을 했는데, 그 값을 반환하는게 아니고 그 주소를 반환하는 것이다.
위의 표를 예시로 들면

0x00000x00010x00020x0003
ABCD

여기서 0x0000을 반환한 것이다.
반환값은 list* 이고 이게 0x0000과 같다는 것이다.
쉽게 생각해보자.
list* a = 0x0000이면
*a는 위의 배열에서 A를 의미한다.
즉 배열은 다음과 같은 구조일 것이다.

0x00000x00010x00020x0003
listlistlistlist

물론 얘는 연결리스트이기 때문에 값들이 여기에 담겨있지는 않다.
리스트의 구조를 다시 떠올려보면

/* 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
얘는 해시 체이닝으로 관리하고 있다.

-hash.c-

hash_init

해시를 초기화하는 함수인데, 추후 함수를 수정할 일이 있으면 그때 코드도 올리겠다.

hash_insert

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) 해당 해시 테이블에 새로운 원소를 삽입한다.

find_bucket

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

/* 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

-vm.h-

vm.c를 먼저 작성해버렸는데, 헤더부터 보는게 역시 맞았다.

vm_type

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의 타입과 관련이 있따.
나머지는 아직 모르겠다.

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 때문이다.

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를 참고하도록 하자.

-vm.c-

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. */
        /* TODO: Insert the page into the spt. */
	}
err:
	return false;
}

얘는 spt안에 가상 주소에 해당하는 페이지가 없을 경우 uninit 페이지를 만드는 함수이다.
새로 만든 페이지는 타입에 따라 다르게 초기화 해야한다.

spt_find_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;
}

spt에서 원하는 페이지를 찾는 함수이다.
spt를 어떤 자료구조로 만들었는지에 따라 내부 코드도 달라질 것이다.

spt_insert_page

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를 리턴한다.

vm_get_frame

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

	ASSERT (frame != NULL);
	ASSERT (frame->page == NULL);
	return frame;
}

페이지에 필요한 실제 물리 공간을 할당받는 함수이다.

vm_claim_page

/* 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에 페이지 정보들이 다 저장돼 있다는 것을 기본으로 생각하자.
그럼 우리는 그걸 꺼내서 쓰면 될 것이다.

vm_do_claim_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. */
	return swap_in (page, frame->kva);
}

PTE에 페이지의 물리 주소를 등록할 차례이다.
PTE하면 또 userprog에서 많이 봤을 것이다.
pml4와 관련된 함수를 사용하면 될 것 같다.

vm_try_handle_fault

/* 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함수이다.

supplemental_page_table_copy

/* 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로 복사한다.

supplemental_page_table_kill

Uninit

-uninit.h-

/* 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을 위한 구조이다.
눈에 잘 익혀두자.

-uninit.c-

page_operation uninit_ops

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 함수를 실행한다.

uninit_new

/* 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 구조체에 넣을 정보를 담는다.

uninit_initialize

/* 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);
}

누구냐 넌

-Lazy_Load with Uninit-

그럼 여태까지 살펴봤던 함수들로 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 함수가 실행된다.

vm_get_victim

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을 반환하면 된다.

vm_evict_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

-anon.c-

anon.h는 별볼일 없다...
anon.c에는 다음과 같은 구조체가 정의되어 있다.

page_operation anon_ops

/* 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,
};

File

-file.c-

그 누구랑 이름이 같다.

file_backed_destroy

do_mmap

do_munmap

Disk

swap을 다루기 위해서는 disk를 건드려야한다.
이쯤되니 드는 생각이, 핀토스 프로젝트는 짜임새가 참 좋다는 생각이 든다.
프로젝트 4가 파일 시스템인데, 그 프로젝트를 이해하기 위한 작업이라는 생각이 든다.
유저 프로그램 역시, 가상 메모리를 위한 커다란 준비였다고 볼 수 있는 것 같다.

-disk.c-

disk_get

/* 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)처럼 할당하면 되지 않을까 생각한다.

disk_size

/* 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섹터 단위로 기록하면 될 것 같다.

profile
안녕

0개의 댓글