vm_try_handle_fault()에 stack growth 추가 - sihyun10/pintos-lab-vm GitHub Wiki
들어가기에 앞서
Q. 왜 stack growth
가 필요할까?
- 유저 스택은 사용자 프로세스의 실행 중에 동적으로 커질 수 있다.
- 가상 주소를 접근했는데, 매핑된 프레임이 없다면
page fault
가 발생한다. - 이때 그 주소가 유저 스택 영역 내이며, 조건을 만족한다면 새로운 페이지를 할당해서 스택을 확장한다.
Q. stack growth
유의사항
- 스택의 최대 크기는
1MB
이다.- 즉, 유저 스택은
USER_STACK
에서 최대1MB
아래까지 확장 가능하다.
- 즉, 유저 스택은
- 예외적으로
rsp - 8
주소에 접근하는 경우도 허용된다.- 이는
push
명령을 고려한 예외상황이다.
- 이는
vm_try_handle_fault()
함수 설명
프로세스가 유저 스택 내의 아직 매핑 되지 않은 주소에 접근하여 page fault
가 발생한 경우,
그 주소가 스택 영역이라면 새로운 페이지를 할당하여 stack growth
를 수행하는 함수다.
bool vm_try_handle_fault(struct intr_frame *f UNUSED, void *addr UNUSED,
bool user UNUSED, bool write UNUSED, bool not_present UNUSED);
f
: 페이지 폴트 발생 순간의 레지스터 값들을 담고 있는 구조체addr
:page fault
가 발생한 주소user
: 유저 모드에서 발생했는지?write
: 쓰기 접근이었는지?not_present
: 페이지가 존재하지 않아 발생한fault
인지?
vm_try_handle_fault()
내에서
[stack growth 조건] _ - 페이지가 존재하지 않아야 한다 (
not_present == true
) - 유저 모드에서 발생한 페이지 폴트여야 한다 (
user == true
)- 유저 스택은 유저 모드에서만 접근 가능하기 때문이다
- fault 주소(
addr
)는 현재 유저 스택 포인터(rsp
)보다 같거나 높은 주소여야 한다- 일반적으로
addr >= rsp - 8
와 같은 범위를 허용한다 (push
고려)
- 일반적으로
- fault 주소(
addr
)는 USER_STACK보다 작거나 같아야 한다 (스택은 USER_STACK 아래 방향으로 성장) - fault 주소(
addr
)는 USER_STACK - 1MB보다 크거나 같아야 한다. (최대 스택 크기 1MB)
→ 위 조건을 모두 만족할 경우, 해당 주소에 대해 stack growth
를 수행한다.
USER_STACK
: 사용자 스택의 시작 주소- 최대 스택 크기 : 1MB
vm.h
파일에 정의
Pintos에서는 최대 스택 크기를 1MB로 제한한다.
(1 << 20)
은 1MB = 2^20 bytes
를 의미한다.
따라서 STACK_LIMIT
는 스택이 자랄 수 있는 가장 아래 주소, 즉 스택의 바닥 주소이다.
/* 최대 스택 크기 1MB 제한 */
#define STACK_LIMIT (USER_STACK - (1 << 20))
페이지가 존재하지 않아 fault가 발생한 경우에 처리해주어야 한다.
if (not_present)
{
void *rsp = f->rsp;
if (!user)
rsp = thread_current()->user_rsp;
// Stack Growth 판단
if ((uintptr_t)addr >= (uintptr_t)STACK_LIMIT &&
(uintptr_t)addr <= (uintptr_t)USER_STACK &&
(uintptr_t)addr >= (uintptr_t)rsp - 8)
{
vm_stack_growth(addr);
return true;
}
rsp 결정
void *rsp = f->rsp;
if (!user)
rsp = thread_current()->user_rsp;
- 유저 모드에서 발생한
fault
라면f->rsp
- 커널 모드이면
user_rsp
참조
syscall.c
파일에서 syscall_handler()
#ifdef VM
// 유저 스택 포인터 저장
thread_current()->user_rsp = f->rsp;
#endif
user mode -> kernel mode
진입 직후,
시스템 콜 발생 시 유저 스택 포인터(rsp
)값을 저장해두어야
이후 page fault
가 발생 시 스택 접근인지 여부를 판단 가능하다.
Stack Growth 조건
if ((uintptr_t)addr >= (uintptr_t)STACK_LIMIT &&
(uintptr_t)addr <= (uintptr_t)USER_STACK &&
(uintptr_t)addr >= (uintptr_t)rsp - 8)
- Stack 영역 범위 제한 (최대 1MB)
rsp - 8
이상 위치만 허용 (addr >= rsp - 8
)addr == rsp - 8
인 경우 &&addr >= rsp
인 경우를 합친거다.
최종 코드
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;
// 잘못된 주소거나 커널 영역 접근 거부
if (addr == NULL || is_kernel_vaddr(addr))
return false;
// 페이지가 존재하지 않아 fault가 발생한 경우
if (not_present)
{
void *rsp = f->rsp;
if (!user)
rsp = thread_current()->user_rsp;
// Stack Growth 판단
if ((uintptr_t)addr >= (uintptr_t)STACK_LIMIT &&
(uintptr_t)addr <= (uintptr_t)USER_STACK &&
(uintptr_t)addr >= (uintptr_t)rsp - 8)
{
vm_stack_growth(addr);
return true;
}
// 기존 SPT에서 찾기
struct page *page = spt_find_page(spt, addr);
if (page == NULL)
return false;
// 쓰기 불가능한 페이지에 write 시도한 경우
if (write && !page->writable)
return false;
// 페이지를 메모리에 매핑
return vm_do_claim_page(page);
}
return false;
}