이제 스택 확장을 구현해보자. 그 전에 과제 설명서부터 !!
늘 하던대로 과제 설명서를 캡처해서 올리고 싶지만... project 3부터는 과제 하나하나의 설명이 너무 길어서 캡처가 힘들다
내가 사용하던 과제 설명서 링크를 올려둘테니, 여기서 스택 확장 쪽을 참고하자
https://www.notion.so/1fb7bb7778c980f9976afc46cb450710#1fd7bb7778c9803b80cecad309319319
과제 설명서 | Notion
여러분의 OS는 여러 실행 스레드를 적절한 동기화와 함께 처리할 수 있고, 여러 사용자 프로그램도 동시에 로드할 수 있습니다.
verdant-bathtub-bae.notion.site
스택 확장의 조건이 어떻게 되느냐?

스택 접근처럼 보이는 페이지 폴트에서만 이를 처리해야 한다고 한다. 이 뜻이 참 애매모호하다. 스택 바텀에서 얼마나 떨어져 있는 곳까지를 스택 확장 폴트로 처리할 것인가?? 뭔가 명확한 기준이 있으면 좋겠는데... 과제 설명서에서는 이는 주어지지 않는다. 하지만 테스트 케이스를 까본 결과 스택 바텀에서 한 페이지까지는 스택 확장 폴트로 처리가 되는것 같다.
그래서 나는 vm.h 상수로 미리 스택 확장 폴트 범위를 정의해뒀다
#define STACK_GROW_RANGE 4192
왜 4096이 아니냐면... 그냥 오타가 났다. 이래도 테스트가 잘 통과하는걸 보니 문제는 없는 듯 하다.
그전에 또 !! 해줘야 할 것이 있다. 스택 바텀을 나타내는 rsp... 그런데 커널 모드에서 페이지 폴트가 발생하면 어떻게 될까? 커널 스택의 rsp 값이 인터럽트 프레임에 저장되게 된다. 그러면 우리는 이 rsp값으로 스택 확장 폴트를 판단하게 되고 당연히 테스트 여기저기서 펑펑 터지게 될 것이다.
그래서 !! 유저 모드에서의 rsp만 따로 저장해둘 필요가 있다 !!
그러면 유저 모드에서의 rsp는 어떻게 판단할 것인가? 시스템 콜은 항상 유저 모드에서만 발생한다. 그러면 이 때의 rsp값을 어딘가에 저장해두면 될 것 같다. 그럼 유저 모드에서의 페이지 폴트도 있지 않을까? 이 두가지 상황에서만 rsp값을 저장하면 될 것 같다.
struct thread
{
...
/* project3 stack growth 유저스택 rsp */
void *user_rsp;
...
}
이렇게 스레드 구조체에 user 스택의 rsp 값 필드를 만들어두자.
void syscall_handler(struct intr_frame *f UNUSED)
{
/* 커널 내에서의 페이지 폴트 시 rsp가 망가지는 것을 대비 */
if (f->cs == SEL_UCSEG)
thread_current()->user_rsp = f->rsp;
...
}
그리고 시스템 콜의 진입점인 syscall.c의 syscall_handler에 user_rsp값을 저장해두자. SEL_UCSEG는 유저 모드임을 나타내는 상수이다. 물론 유저 모드에서만 시스템 콜이 발생하겠지만... 혹시 모르니까?
static void
page_fault(struct intr_frame *f)
{
if (f->cs == SEL_UCSEG)
thread_current()->user_rsp = f->rsp;
...
}
똑같이 exception.c의 page_fault의 맨 처음에서 user_rsp값을 저장해두자. 유저 모드에서도 페이지 폴트가 충분히 발생할 수 있으니까 !!
이러면 스택 확장을 위한 모든 준비가 끝났다.
vm_try_handle_fault
기존의 handle_fault 함수는 단순히 이 폴트가 bogus 폴트인지, 진짜 폴트인지 확인만 해 주었다. 이제는 스택 확장 폴트인지 확인도 해주어야 한다.
bool vm_try_handle_fault(struct intr_frame *f UNUSED, void *addr UNUSED,
bool user UNUSED, bool write UNUSED, bool not_present UNUSED)
{
...
addr = pg_round_down(addr);
uintptr_t rsp = thread_current()->user_rsp; // 유저 스택의 rsp 가져오기
if ((uintptr_t)addr >= rsp - STACK_GROW_RANGE && addr < USER_STACK && addr >= USER_STACK - (1 << 20))
{
vm_stack_growth(addr);
return true;
}
...
}
우리는 이렇게만 추가해주면 된다 !!
여기서는 딱 두가지 조건만 검사한다
- 폴트가 발생한 주소가 설정한 rsp - 폴트 확장 범위 (밑으로 자라니까 뺴기) 보다 크거나 작은가?
- 폴트 발생 주소가 유저 스택 주소 범위 내에 있는가? (유저 스택은 1MB 범위)
두 조건을 모두 만족하면 vm_stack_growth 함수를 호출한다
vm_stack_growth
static void
vm_stack_growth(void *addr UNUSED)
{
/* 스택 최하단에 익명 페이지를 추가하여 사용
* addr은 PGSIZE로 내림(정렬)하여 사용 */
vm_alloc_page(VM_ANON, addr, true); // 스택 최하단에 익명 페이지 추가
vm_claim_page(addr);
}
여기서 해주는 일은 별거 없다. 그냥 폴트 주소에 미초기화 페이지를 하나 만들어주고, vm_claim_page를 통해 바로 익명 페이지로 초기화만 시켜주면 된다 !!
아 쉽다 쉬워
'크래프톤 정글' 카테고리의 다른 글
| [pintOS] mmap 구현기 (0) | 2025.06.05 |
|---|---|
| [pintOS] vm 트러블슈팅 (1) | 2025.06.04 |
| [pintOS] uninit 페이지 구현기 (0) | 2025.06.03 |
| [pintOS] uninit 페이지 초기화 흐름 (0) | 2025.05.26 |
| [pintOS] frame와 page, SPT와 프레임 테이블 (0) | 2025.05.26 |