rip : register instruction pointer
cpu가 다음에 어떤 명령어를 실행할지에 대한 정보를 담고 있다.
rsp : register stack pointer
스택은 높은 주소에서 낮은 주소로 자란다.
스택이 커질수록 rsp는 낮은 주소가 된다.
기존의 pml4 시스템 : 할당된 프레임에 대해 pml4로 관리
만들어야 할 vm 시스템 : 아직 프레임이 할당되지 않은 페이지들을 관리
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. */
/* 할 것: lazy_load_segment에 정보를 넘기기 위해 aux를 설정하라 */
struct aux_pak aux;
aux.file = file;
aux.ofs = ofs;
aux.upage = upage;
aux.read_bytes = read_bytes;
aux.zero_bytes = zero_bytes;
aux.writable = writable;
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;
}
load_segment에서 aux에 값 구겨넣고
static bool lazy_load_segment(struct page *page, void *aux)
{
struct aux_pak *aux_pak = (struct aux_pak *)aux;
struct file *file = aux_pak->file;
off_t ofs = aux_pak->ofs;
uint8_t upage = aux_pak->upage;
uint32_t read_bytes = aux_pak->read_bytes;
uint32_t zero_bytes = aux_pak->zero_bytes;
bool writable = aux_pak->writable;
/* 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. */
/* 할것: 파일에서 세그먼트를 load한다 */
/* 할것: 이 함수는 주소 VA에서 첫번째 페이지 폴트가 일어날 때 호출된다. */
/* 할것: VA는 이 함수가 호출될 때 사용 가능하다 */
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;
// 페이지 단위 정렬을 맞춰주기 위해 남는 만큼 0으로 초기화해줄 바이트 크기
/* 페이지만큼의 메모리를 얻는다 */
uint8_t *kpage = palloc_get_page(PAL_USER);
if (kpage == NULL)
return false;
/* 페이지를 로드한다. (파일을 읽는다) */
if (file_read(file, kpage, page_read_bytes) != (int)page_read_bytes)
{
palloc_free_page(kpage);
return false;
}
memset(kpage + page_read_bytes, 0, page_zero_bytes);
/* 프로세스의 주소 공간에 그 페이지를 추가한다. */
if (!install_page(upage, kpage, writable))
{
printf("fail\n");
palloc_free_page(kpage);
return false;
}
}
return true;
}
lazy_load_segment에선 다시 풀면 되지 않을까?
메모리 할당 로직은 똑같고?
/* Create a PAGE of stack at the USER_STACK. Return true on success. */
/* USER_STACK에 페이지만큼의 스택을 만든다. 성공하면 true 반환. */
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: 스택을 `stack_bottom`에 매핑하고 페이지를 즉시 클레임해라.
* TODO: 성공하면, rsp(스택 포인터)를 적절히 설정해라.
* TODO: 해당 페이지를 스택이라고 표시해라. */
return success;
}
이게 뭐하는 함수임?
/* Create a minimal stack by mapping a zeroed page at the USER_STACK */
/* USER_STACK에 0으로 초기화된 페이지를 매핑해서 최소한의 스택을 만든다 */
static bool
setup_stack(struct intr_frame *if_)
{
uint8_t *kpage;
bool success = false;
kpage = palloc_get_page(PAL_USER | PAL_ZERO); // 0으로 초기화된 프레임 생성
if (kpage != NULL)
{
success = install_page(((uint8_t *)USER_STACK) - PGSIZE, kpage, true);
//
if (success)
if_->rsp = USER_STACK;
else
palloc_free_page(kpage);
}
return success;
}
일단 Project2 까지 쓰이던 setup_stack을 분석.
install_page가 무슨 함수인지부터 알아봐야할듯
/* 유저 가상 주소 upage에서 커널 가상 주소 kpage(frame 취급)로의 매핑을 페이지 테이블에 추가
* writable이 true이면, 유저 프로세스가 페이지 수정 가능. 아니면 읽기만 됨.
* upage는 이미 매핑돼있어야 함. kpage는 palloc_get_page()로 얻어오셈.
* 성공하면 true 반환. upage가 이미 매핑돼있거나 메모리 할당 실패하면 false 반환. */
static bool
install_page(void *upage, void *kpage, bool writable)
{
struct thread *t = thread_current();
/* Verify that there's not already a page at that virtual
* address, then map our page there. */
return (pml4_get_page(t->pml4, upage) == NULL && pml4_set_page(t->pml4, upage, kpage, writable));
}
install_page는 단순히 pml4에 매핑해주는거인듯.
pml4_get_page와 pml4_set_page 두개 해주고 true false 반환.
pml4
문제: install_page가 project3를 위한 setup_stack 스켈레톤에는 존재하지 않음.
추측: 페이지를 즉시 'claim'하라는 걸 보니까 vm_claim_page() 쓰면 되는거인듯?
vm_claim_page()에 pml4_set_page() 있다.
pml4_get_page는 spt_find_page()으로 대체되는건가?
의문: 페이지만큼의 스택을 만들어서 뭐하겠다는거지?
추측: 프로그램이 시작되면 물리 메모리(지금은 가상 주소)를 조금이라도 사용은 할 테니까 최소한의 프레임 할당시켜놓기
의문: stack이 더 필요하면 어떻게 하는거지?
해결: vm_stack_growth가 있음. 나중에 이거 만들어서 해결하는듯.
TODO 정리
1. 스택을 stack_bottom에 매핑해라
2. 페이지를 즉시 클레임해라
3. rsp를 설정해라
4. 해당 페이지가 스택에 들어갔다고 표시해라
스택을 stack_bottom에 매핑한다는 것 : 이전의 install_page이 하던 일
(이전의) install_page = pml4_get_page() + pml4_set_page()
pml4_set_page() → vm_claim_page() : 이렇게 대체하면 될듯
pml4_get_page()가 쓰였던 이유 : 이미 프레임이 할당돼있었는지 확인하기 위함
spt_find_page()로 대체하는 건 아닐듯. 이건 spt에서 "upage"가 있는지 없는지 확인.
지금은 kpage의 할당 여부를 확인해야됨.
spt는 각 쓰레드가 갖고 있는 가상 메모리를 기록하는 것.
stack_bottom이라는 유저 가상 주소를 spt에 넣고
spt를 hash 함수로 구현했으니까 bucket에 정보들을 넣으면 됨.
bucket에 넣을 정보들은 또 struct 선언하고 malloc 해준 다음 넣으면 될듯?
라고 생각했는데 hash.c 보니까 내가 생각했던
페이지를 해시 함수를 통해서 키로 바꾸고, 그 키로 버킷에 접근하는
게 아니라 그냥 페이지를 해시 함수로 바꾼 키 인덱스에 hash_elem이 넣어지는 거였음.
일단 어떻게 되는건지 잘 모르겠지만,
spt의 bucket이 아니라 페이지에 해당 정보 넣으면 해결.
/* Claim the page that allocate on VA. */
bool vm_claim_page(void *va UNUSED)
{
struct page *page = (struct page *)malloc(sizeof(struct page));
return vm_do_claim_page(page);
}
만들어지는데
spt에 페이지를 어떻게 넣어야 하는거지? vm_claim_page에서 page를 할당하는데?
va가 안 쓰이고 있음.
page->addr = va;
이걸 해주면 되겠지?
동기한테 해시 테이블에 관해 물어보니,
빈 테이블을 만들어서 주소만 찾으려는 주소로 갈아낀 후에
find 함수 쓰면 hash_elem을 찾을 수 있고,
그 hash_elem를 사용한 hash_entry로 페이지를 찾는다는 이야기를 들음.
주소에 할당한 페이지를 해시 테이블에서 찾은 다음
page에 해당 페이지가 stack에 있다고 적는다.
그걸 어디에 적어야 되는거지?
간단하게는
bool frame
bool disk
bool swap
이렇게 3개 선언하면 될 것 같긴 한데 (최적화하려면 비트마스킹)
union
{
struct uninit_page uninit;
struct anon_page anon;
struct file_page file;
#ifdef EFILESYS
struct page_cache page_cache;
#endif
};
기존에 있던 union 정보는 활용 못하나?
찾아봤더니 그런 용도로 쓰기엔 애매한듯.
"지금 union은 무슨 struct로 선언돼있는가" 같은 건 어려울듯?
union에 이름 붙이면 가능하긴 할텐데,
다른 코드들에 어떻게 연결돼있는지 잘 모르겠어서
일단 안 건드리고 비트마스킹 해보는 식으로 접근.
문법을 알아야 되는거라 간단하게 gpt에게 부탁.
그럼 이제 초기화는 어디에서 해야되는거지? 하고 돌아다니다가
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;
}
}
순간 type이 disk, swap, frame 그거 알 수 있는거 아닌가? 했지만
swap in 했는지 swap out 했는지를 알아야 되는거라 살짝 다름.
/* 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,
}};
}
페이지가 만들어질때 va가 초기화되는데...?
이거 uninit 페이지라 페이지는 무조건 여기서부터 시작하는건데
그럼 내가 봤던 malloc으로 페이지 선언하라고 했던 힌트는 뭐지
갑자기 근간이 다 흔들거리기 시작한다.
uninit.c를 봤더니 그 힌트가 잘못됐던 거가 아닐까 싶음.
글 서두에서도 정답은 아니고 그냥 풀면서 끄적인 메모라고 했었음.
그럼 vm_claim_page부터 다시 점검해야할듯.
Claims the page to allocate va. You will first need to get a page and then calls vm_do_claim_page with the page.
위 함수는 인자로 주어진 va에 페이지를 할당하고, 해당 페이지에 프레임을 할당합니다. 당신은 우선 한 페이지를 얻어야 하고 그 이후에 해당 페이지를 인자로 갖는 vm_do_claim_page라는 함수를 호출해야 합니다.
struct page *page = (struct page *)malloc(sizeof(struct page));
uninit_new(page,);
malloc해서 page를 위한 자리를 얻어낸 뒤,
uninit_new를 사용하여 해당 page의 값들을 초기화해주면 될 거임.
이전에 변수 선언 없이 포인터 값만 malloc으로 받은 뒤에
역참조로 초기화해줘도 되는건가 해서 gpt한테 물어봤더니 된다고는 함.
근데 믿을 수가 없고 테스트가 당장 불가능하기 때문에 다른 곳에서 테스트를 해봄.
int main() {
int *p;
*p = 1;
printf("%d\n", *p);
return 0;
}
되는거 확인.
이제 안심하고 uninit_new을 사용하려는데
이상하게 인자가 너무 많음.
이거 분명 vm_alloc_page_with_initializer()에서 했었음.
페이지 만들 때 엄청 여러 과정을 거쳤었음.
/* 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`. */
주석 읽어보니까 이제야 이해됨.
vm_alloc_page 쓰면 될듯.
근데 그럴라면 type이 뭔지를 알아야 되는데
vm_claim_page에는 va밖에 안 들어오네?
분명 깃북 앞 부분에 "템플릿 바꾸지 마세요"라고 했었다.
생각해보니 vm_claim_page()은 claim을 하려는 함수이다.
claim을 한다는 건, va에 이미 페이지가 있다는 것이다.
굳이 또 다시 malloc을 한다거나 vm_alloc_page()을 사용하여
페이지를 만드는 것이 아니라,
va로 해시 테이블(spt)을 탐색하여 내가 넣었던
uninit 페이지를 찾고, 그걸 page 변수에 담은 후에
vm_do_claim_page에 인자로 넘겨주면 될듯.
그리고 그건 내일의 내가.