Open sylee6529 opened 10 months ago
추가사항
writable
멤버 추가spt_find_page
, spt_insert_page
, vm_get_frame
, bool vm_do_claim_page
구현page_hash_func
, spt_less_func
정의supplemental_page_table_init
구현1차로 코딩한 후, make
명령어를 돌렸는데 아래와 같은 에러 발생
In file included from ../../include/vm/uninit.h:3:0,
from ../../vm/uninit.c:11:
../../include/vm/vm.h:71:28: error: field ‘uninit’ has incomplete type
In file included from ../../include/vm/uninit.h:3:0,
from ../../vm/uninit.c:11:
../../include/vm/vm.h:71:28: error: field ‘uninit’ has incomplete type
struct uninit_page uninit;
^~~~~~
In file included from ../../include/vm/uninit.h:3:0,
from ../../vm/uninit.c:11:
../../include/vm/vm.h:125:52: error: unknown type name ‘vm_initializer’
bool writable, vm_initializer *init,
In file included from ../../include/vm/uninit.h:3:0,
from ../../vm/uninit.c:11:
../../include/vm/vm.h:125:52: error: unknown type name ‘vm_initializer’
bool writable, vm_initializer *init,
여기서 중요한 부분은, error: field ‘uninit’ has incomplete type
, error: unknown type name ‘vm_initializer’
이런 에러들이 header 파일에서 발생
✔️ 1차) header 파일을 살펴보자
vm.h
, uninit.h
)✔️ 2차) 현재 코드 내용에서 에러가 났을 수 있다
✔️ 3차) 해결 - include header 파일의 순서가 어긋남
지금까지의 Project 1, 2에서는 해당 포맷팅을 동일하게 사용했지만 문제가 없었다. 하지만 이번 프로젝트에서는 순서가 민감한 부분이 있었던 것 같다.
설정 변경을 통해 header 파일 순서가 바뀌지 않게 하였고, 해결되었다
clang
을 통해 포맷을 설정했는데, 여기에 sortIncludes
라는 옵션을 꺼주면 해결된다.
initd
의 process_exec
->vm_alloc_page_with_initializer
안에서 error 발생 -> 바로 종료
Anonymous Page 구현 중 문제
initd
의process_exec
->vm_alloc_page_with_initializer
안에서 error 발생 -> 바로 종료
해결 - (나만의) 상식과 그렇지 않은 코드
원래 나의 코드(아래)는 다음과 같았다. hash_insert의 return 값이 hash_elem이니, 성공하면 값을 주고 실패하면 null을 줄 것이라 기대하고, NULL이 아닌 경우 true를 반환하고, NULL이면 false를 반환하는 코드를 짰었다.
하지만, hash_insert 주석 중 한 문장... Inserts NEW into hash table H and returns a null pointer, if no equal element is already in the table.
성공하면 null을 반환한다. 그래서 NULL일 때 true를 반환하도록 고쳤더니 해결. 간단하게 삼항연산자를 사용할 수 있다.
vm_alloc_page_with_initializer
에서 첫번째 줄 thread_current
에서 바로 터지는 현상
vm_alloc_page_with_initializer
에서 첫번째 줄thread_current
에서 바로 터지는 현상
1. load_segment 마지막 줄에 offset 업데이트 누락
ofs += page_read_bytes;
load_lazy_segment()
를 보면 file_seek(file, ofs);
부분이 있는데, 여기서 ofs는 load_segment
에서 설정한 aux->ofs
을 가져와 그 위치부터 파일을 읽는다. 이 말은, 파일을 읽은 다음 다음 파일을 읽기 전에, 다음 세그먼트 시작 위치를 가르키는 ofs 위치를 갱신해주지 않으면, 계속 같은 위치에서 파일을 읽게 될 것이라는 이야기다.
이를 신경쓰지 않으면, 실행은 되나, 어떤 출력 결과물도 나오지 않는다.
2. setup_stack 수정
이 경우, 생각하지 못한 시나리오 때문이라기 보다는, vm_alloc_page_with_initializer
함수의 코드를 vm_claim_page
까지 하는 코드에서 spt_insert_page
까지만 하는 코드로 수정했는데, 이를 호출하는 함수인 setup_stack
에는 이를 반영하지 못하여 일어난 일이었다. 물리 메모리 할당하는 코드까지 추가하면 완료. (kernel panic의 주 원인이었다)
before
// SPT에 페이지 삽입 & 바로 할당
success = vm_alloc_page(VM_MARKER_0, stack_bottom, true);
if (!success) {
return false;
}
// stack page 넣은 다음 주소에 rsp를 설정
if_->rsp = stack_bottom;
return success;
after
// SPT에 페이지 삽입 & 바로 할당
if (vm_alloc_page(VM_ANON | VM_MARKER_0, stack_bottom, true)) {
if (vm_claim_page(stack_bottom)) {
if_->rsp = USER_STACK;
success = true;
}
}
return success;
3. vm_get_frame 에 메모리 할당 필요 before
struct frame *frame = NULL;
after
struct frame *frame = (struct frame *)malloc(sizeof(struct frame));
malloc을 통해 할당받은 frame 주소 값에 있는 kva 멤버 값에 palloc_get_page
로 user pool에서 할당을 받는데, 일단 frame 값을 할당받지 않으면 주소 값을 가지고 있지 않아 (frame이 가르키는 메모리 공간이 없음) 문제가 생길 수 있다.
이것도 이건데, 기존의 템플릿 코드를 유심히 보지 않고 나둬서 생긴 일이다. 이를 간과하지 않아야 한다. (..)
(코드는 아래)
struct frame *frame = (struct frame *)malloc(sizeof(struct frame));
frame->kva = palloc_get_page(PAL_USER);
//...
load()
함수에 대해코드 상 하는 일은 대략적으로 다음과 같다.
load_segment()
를 통해 이뤄짐setup_stack()
을 통해 이뤄짐check_address
에 spt entry를 찾아 return 하도록 수정vm_try_handle_fault
에서 flag 파라미터에 대한 처리 로직 추가if (not_present) {
page = spt_find_page(spt, addr);
if (page == NULL) {
return false;
}
Project 2 테스트케이스 해결 못하는 문제 (52 fail 정체)
- read-boundary, fork, exec, wait, sync 등 조금조금씩 안 됨 read-boundary 같은 경우에는 아래의 코드에서 find_page를 못해 false 로 반환되며 -1 exit하는 모습이 있었음...
if (not_present) { page = spt_find_page(spt, addr); if (page == NULL) { return false; }
syscall.c
에서 check_address
부분을 수정하면 해결이 된다는데, 그래도 해결이 되지 않았다. 코드에 있는 빈틈을 찾아보니 다음과 같았다.before
if (!user || is_kernel_vaddr(addr)) {
return false;
}
after
if (addr == NULL) return false;
if (is_kernel_vaddr(addr)) return false;
물리 페이지 로드에 필요한 vm_do_claim_page
함수에서, 조건을 많이 체크하여 로드 여부를 결정했었는데, 그렇다보니 되어야 할 케이스까지 false가 반환이 되었던 것 같다. 받으면 무조건 물리 메모리에 로드하도록 느슨하게 바꾸니 해결되었다.
before
/* TODO: Insert page table entry to map page's VA to frame's PA. */
bool set_page = false;
if (!pml4_get_page(thread_current()->pml4,
page->va)) { // NULL이어야 기존것이 아님.
set_page = pml4_set_page(thread_current()->pml4, page->va, frame->kva,
page->writable);
if (set_page) {
return swap_in(page, frame->kva);
}
}
return false;
after
/* Set links */
frame->page = page;
page->frame = frame;
/* TODO: Insert page table entry to map page's VA to frame's PA. */
// 가상 주소와 물리 주소를 매핑
struct thread *current = thread_current();
pml4_set_page(current->pml4, page->va, frame->kva, page->writable);
return swap_in(page, frame->kva);
project2 테스트케이스에서 fork-read만 안 되는 현상이 있었다.
열심히 보니 vm.c
에서는 문제가 없어보였다. 근데 이전에 문제를 해결할 때도 그랬지만, 코드 수행을 위한 체크를 너무 빡빡하게 수정했던 것이 되려 걸림돌이 되었던 경우가 많아서 다시 보는데,
lazy_load_segment
에서 메모리 할당 해제를 위해 일부러 코드를 추가했는데, 팀원분이 이 부분 free를 해주면 오히려 테스트 통과가 되지 않더라 라는 이야기를 해주셔서 반영해보니 해결되었다.
aux 부분이 fork할 때 자식이 참조하는 정보가 날라가서 안 될 수도 있다(?) 라는 가설이 가장 설득력있어 보인다.
rsp
에는 어떤 값을 넣을까?A. 주소를 빼기 전 값을 넣어야 한다. 왜냐하면 rsp 값으로부터 PGSIZE 만큼 낮은 주소로 확장해 나가는데, 주소를 뺀 후의 값을 rsp를 넣으면 낮은 주소로 확장해 나갈 때, 접근할 수 없는 영역이게 되기 때문이다.
높은 주소가 위, 낮은 주소가 아래일 때, 스택은 아래로 성장하는 상황이라 할 때, rsp는 '천장'에 있게 되는 셈이다.
FAIL tests/vm/pt-write-code2 FAIL tests/vm/pt-grow-stk-sc
Todo: 아래의 특수 테스트 케이스를 해결하기
FAIL tests/vm/pt-write-code2 FAIL tests/vm/pt-grow-stk-sc
slack 질답 채널에 올라온 질문으로 알게 된 사실인데, pintos-kaist 설계 상, CR0 레지스터의 WP bit가 세팅되어 있지 않기 때문에 writable이 false인 file도 read할 때 page fault가 발생하지 않고 그냥 쓰게 되어 있다
여기서 중요한 것은, "read를 하는데 왜 writable을 체크하는가"에 대한 것인데, read가 fd를 가져와 buffer에 '쓰는' 것이기 때문이다. 이 함수 안에서 file_read
함수가 쓰이는데, 그것의 주석은 다음과 같다.
SIZE 바이트만큼 FILE에서부터 BUFFER로 읽어들인다. ...
read는 FILE->BUFFER로 바이트를 읽고, wirte는 BUFFER->FILE로 바이트를 쓴다. 전자는 읽는다고 표현되어 있지만, 'buffer에 쓰는 작업'이라고 생각하는게 맞는 것 같다. buffer에 쓴다는건 메모리에 쓴다는 것이니, 현재 buffer에 쓰지 못하게 되어 있다면 못하게 하는 것 = read-only 페이지를 read하지 못하게 하는 것과 동일하다라고 받아들였다.
이렇게 추가하면 write-2 테스트케이스는 통과. (물론 pt-grow-stk-sc 테스트 케이스를 위해 함수를 바꿔야 해서 저렇게 추가한 코드는 수정이 필요할 듯)
주소의 유효성을 검증하면 보통, 시작 주소에 해당하는 page가 있는지 찾는데, 생각해보면 size가 엄청 큰 buffer인 경우 여러 페이지를 갖고 있을 수 있다. 그래서 PGSIZE만큼 addr을 빼면서 순회하고, page를 찾는 로직을 추가한다.
for (void *addr = end_addr; addr >= start_addr; addr -= PGSIZE) {
struct page *pg = check_address(addr);
if (pg == NULL) {
exit(-1);
}
if (to_write == true && pg->writable == false) {
exit(-1);
}
}
팀원분과 2개정도 차이가 나지만, 나중에 구현할 것에 관한 테스트여서 신경쓰지 않고 진행하기로 했다. 다음은 mmap 구현 가보자고
내용은 run: 512 bytes read starting at offset 0 in "sample.txt" differ from expected: FAILED
다른 팀의 팀원분의 조언으로 supplemental_page_table_kill
에서 사용하는 함수 spt_destructor
에서 free
가 아니라 vm_dealloc_page
를 해야 맞다고 조언해주셨다. destroy가 잘 되면 exit도 잘 될 거라고 해서, 아래와 같이 코드를 수정했더니 fork 관련한 테스트 케이스가 터지기 시작했다.
void spt_destructor(struct hash_elem *e, void *aux) {
const struct page *p = hash_entry(e, struct page, hash_elem);
// free(p);
vm_dealloc_page(p);
}
file_close
를 하고 있었다. 이를 없애주니 말끔히 해결. (물론 이야기를 들어보니, free
해주는게 맞다고 하는데 free를 하면 코드에 문제가 생겨서 일단 보류하기로 했다.)(참고로 syn-read 문제가 해결되지 않아 6 fail도 가능하다)
할 일
구현 목표
mmap 구현 (완료)
진행상황
5-6 fail (확률적으로 pass하는 테스트 때문에)
기타