728x90
➡️ 과제 설명(Gitbook)
✨ Summary
- 페이지 폴트가 스택으로 인해 발생했는지 확인하고 실제 스택 영역을 확장한다.
👷 구현 순서
1. vm_try_handle_fault
- 스택의 크기는 통상적으로 1MB이다.
- 스택에 접근하는 경우에만 할당한다.
- Original Code
/* vm/vm.c */
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 */
/* page_fault로 부터 넘어온 인자
* f : 페이지 폴트 발생 순간의 레지스터 값들을 담고 있는 구조체
* addr : 페이지 폴트를 일으킨 가상주소
* not_present : 페이지 존재 x (bogus fault), false인 경우 read-only페이지에 write하려는 상황
* user : 유저에 의한 접근(true), 커널에 의한 접근(false) - rsp 값이 유저 영역인지 커널영역인지
* write : 쓰기 목적 접근(true), 읽기 목적 접근(false)
*/
// page fault 주소에 대한 유효성 검증
// 커널 가상 주소 공간에 대한 폴트 처리 불가, 사용자 요청에 대한 폴트 처리 불가
if (is_kernel_vaddr (addr) && user) // real fault
return false;
if (not_present){
if (!vm_claim_page(addr)) return false;
else return true;
}
return false;
}
- code
/* vm/vm.c */
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;
static void *STACK_MINIMUM_ADDR = USER_STACK - (1 << 20); // 스택 최대 크기(주소 하한선) -> 1MB
// ↳ 스택은 아래로 증가하기 때문 (Ref. Gitbook)
// struct page *page = NULL;
/* TODO: Validate the fault */
/* TODO: Your code goes here */
/* page_fault로 부터 넘어온 인자
* f : 페이지 폴트 발생 순간의 레지스터 값들을 담고 있는 구조체
* addr : 페이지 폴트를 일으킨 가상주소
* not_present : 페이지 존재 x (bogus fault), false인 경우 read-only페이지에 write하려는 상황
* user : 유저에 의한 접근(true), 커널에 의한 접근(false) - rsp 값이 유저 영역인지 커널영역인지
* write : 쓰기 목적 접근(true), 읽기 목적 접근(false)
*/
// page fault 주소에 대한 유효성 검증
// 커널 가상 주소 공간에 대한 폴트 처리 불가, 사용자 요청에 대한 폴트 처리 불가
if (is_kernel_vaddr (addr) && user) // real fault
return false;
// 페이지 폴트가 발생하는 스택 포인터 위치
void *rsp_stack = f->rsp;
if (not_present){
if (!vm_claim_page(addr)){ // 스택을 증가 시켜야하는 경우, 즉 spt에 현재 할당된 스택 영역을 넘거가는 경우
if (rsp_stack - sizeof(void*) <= addr && STACK_MINIMUM_ADDR <= addr && addr <= USER_STACK) {
vm_stack_growth(thread_current()->stack_bottom - PGSIZE);
return true;
}
return false;
}
else
return true;
}
return false;
}
- 페이지 폴트가 발생하는 경우
- 유효한 주소가 아닌 경우
- 실제 할당하려는 페이지가 없는 경우
- ✅ 스택 공간이 부족한 경우 → 현재 구현하려는 상황
!vm_claim_page(addr)
→ 가상 주소 addr에 대해서 페이지를 할당할 수 없는 경우- 해당 포인터가 스택에 접근하려는 경우인데, 기존 스택 페이지를 넘어가는 경우에는 스택 자체의 크기를 확장한다.
- 새로 할당하려는 페이지가 스택을 위한 페이지인지 확인한다.
- rsp_stack - sizeof(void*) == addr
- rsp로 부터 -8(byte)를 한 위치가 요청하는 주소값인지 확인한다.
💡 x86-64 포인터 변수의 크기 : 8byte
- rsp로 부터 -8(byte)를 한 위치가 요청하는 주소값인지 확인한다.
- STACK_MINIMUM_ADDR <= addr
- 스택 영역 사이즈의 최대 크기(1MB)를 넘어가는 지 확인한다.
On many GNU/Linux systems, the default limit is 8 MB. For this project, you should limit the stack size to be 1MB at maximum.
_Gitbook Stack Growth
- addr <= USER_STACK
- 스택 영역의 최대 값(0x47480000)을 넘어가는 지 확인한다.
- 스택 시작 주소
- 스택 영역의 최대 값(0x47480000)을 넘어가는 지 확인한다.
😵💫 헷갈리는 용어 및 개념들
- 스택은 아래로 확장된다.
- %rsp(rsp_stack) : 스택의 공간에서 가장 끝부분을 가리키는 레지스터
- %rsi(USER_STACK) : 스택의 시작 부분을 가리키는 레지스터
&struct thread → stack_bottom
: 스택으로 할당된 페이지의 끝을 가리킨다.
- ⭐ Ref. 핀토스의 스택 구조 참조!
Kernel Stack 🆚 User Stack
/* userprog/syscall.c */
void
syscall_handler (struct intr_frame *f UNUSED) {
// TODO: Your implementation goes here.
struct thread *curr = thread_current();
#ifdef VM
curr->rsp_stack = f->rsp;
#endif
...
}
/* vm/vm.c */
bool
vm_try_handle_fault (struct intr_frame *f UNUSED, void *addr UNUSED,
bool user UNUSED, bool write UNUSED, bool not_present UNUSED) {
...
void *rsp_stack = is_kernel_vaddr(f->rsp) ? thread_current()->rsp_stack : f->rsp;
if (rsp_stack - sizeof(void*) == addr && STACK_MINIMUM_ADDR <= addr && addr <= USER_STACK) {
vm_stack_growth(thread_current()->stack_bottom - PGSIZE);
return true;
...
}
- 처음엔 커널 스택에 대한 요청인지, 유저 스택에 대한 요청인지 판단하기 위해 위와 같은 로직을 넣었다.
- But,
f→rsp
자체만 사용하면 커널 스택에 대한 요청인지 유저 스택에 대한 요청인지를 구분할 필요가 없다.
- +) 커널 스택과 유저 스택
Ref. 유저 스택과 커널 스택
2. vm_stack_growth
- 앞선 함수에서 스택 공간에 대한 요청임을 확인했다면 실제로 스택의 크기를 확장한다.
/* vm/vm.c */
static void
vm_stack_growth (void *addr UNUSED) {
/* vm_try_handler 수정해서 stack growth인 경우 함수를 호출하도록 처리
0. 증가 시점 : 할당해주지 않은 페이지에 rsp가 접근했을 때 : stack growth에 대한 page_fault 발생시
1. stack_bottom 설정
2. 확장 요청한 스택 사이즈 확인
3. 스택 확장시, page 크기 단위로 해주기
4. 확장한 페이지 할당 받기
* 커널에서 페이지 폴트 발생시, intr_frame 내의 rsp는 유저스택 포인터가 아닌 쓰레기 값을 가짐 -> 커널에서 발생시 유저 스택 포인터를 thread 구조체에 저장*/
if(vm_alloc_page(VM_ANON | VM_MARKER_0, addr, 1)) { // 스택 마커 다시 표시
vm_claim_page(addr); // 페이지, 프레임 연결
thread_current()->stack_bottom -= PGSIZE; // 증가된 스택 사이즈 만큼 stack_bottom 옮겨주기
}
}
- ‘vm_try_handle_fault’ 에서
vm_stack_growth
를 위한 인자로thread_current()->stack_bottom - PGSIZE
을 넣어줬으므로addr
는 한 페이지를 확장한 이후의 포인터 값이다.
- ⭐ 새로운 페이지 할당인데, 기존 stack_bottom에서 ‘+’가 아니라 ‘-’인 이유?
- 스택은 아래로(높은 주소에서 낮은 주소로) 증가하므로
- PGSIZE
를 해준다!!
- 스택은 아래로(높은 주소에서 낮은 주소로) 증가하므로
vm_alloc_page(VM_ANON | VM_MARKER_0, addr, 1)
addr
에 대해 새 페이지를 할당한다.- 이에 맞춰서 스택 마커도 표시한다.
vm_claim_page(addr)
- 할당한 페이지에 대해 프레임을 연결해준다.
thread_current()->stack_bottom -= PGSIZE
- 페이지 할당, 프레임 연결이 모두 완료되었다면 새로 바뀐 stack_bottom의 값을 새로운 스택 크기의 범위로 설정해준다.
728x90
'🌱 Dev Diary > 📄 TIL' 카테고리의 다른 글
알쓸 정규표현식 (32) | 2024.05.07 |
---|---|
Project 3. Memory Mapped Files (0) | 2023.01.19 |
Project 3. Stack Growth(1) (0) | 2023.01.19 |
Project 3. Anonymous Page(2) (0) | 2023.01.19 |
Project 3. Anonymous Page(1) (0) | 2023.01.19 |