지금까지는 하나의 큰 페이지로 스택을 만들어주고 고정된 크기로 이용했다면, 이제는 필요할 때마다 1 페이지씩 자라나는 스택을 만들어줄 것이다. 즉,
<aside> 💡 Page Fault에 의해 동적으로 자라나는 스택을 구현할 것이다.
</aside>
페이지 폴트가 발생하였을 때, 이 페이지 폴트가 stack growth에 대한 페이지 폴트일 때 스택을 키운다.
우리가 현재 하고 있는 것은 유저 스택을 Grow시키는 것이다. 페이지 폴트는 크게 유저 가상 주소 공간에서 발생하는 경우와 커널 가상 주소 공간에서 발생하는 경우로 나뉠 수 있다.
유저 가상 주소 공간에서 페이지 폴트가 발생하는 경우나 혹은 시스템 콜의 경우에는 해당 프로세스의 인터럽트 프레임의 RSP 멤버를 그대로 써도 된다. 페이지 폴트가 발생해서 모드 전환이 되었을 때, 유저 프로그램의 스택 포인터 값이 intr_frame->rsp에 저장이 되어 페이지 폴트 핸들러나 시스템 콜 핸들러에 인자로 전달되기 때문이다.
하지만 커널 주소 공간에서 페이지 폴트가 발생하는 경우는 상황이 다르다. 예를 들어 문맥 교환 중에 페이지 폴트가 난다거나 하는...
Since the processor only saves the stack pointer when an exception causes a switch from user to kernel mode, reading rsp
out of the struct intr_frame
passed to page_fault()
would yield an undefined value, not the user stack pointer.
페이지 폴트나 시스템 콜 핸들러가 호출될 때 struct thread에 현재 유저 스택 포인터를 미리 저장해야 한다.
Q. 페이지 폴트가 유저 공간에서 발생했는데 RSP가 커널 스택을 가리키고 있을 수가 있나...?
유저 모드에서 커널 모드로 바뀌면서 시스템 콜이 호출될 때, 미리 유저 스택 포인터를 스레드 구조체 내에 저장해 놓는다. 시스템 콜 핸들러가 받은 인터럽트 프레임의 RSP 값은 유저 프로세스의 유저 스택 포인터 값이다.
/* The main system call interface */
void syscall_handler (struct intr_frame *f) {
#ifdef VM
thread_current()->rsp_stack = f->rsp; // syscall을 호출한 유저 프로그램의 유저 스택 포인터
#endif
...
이제 페이지 폴트 핸들러를 수정해야 한다.
<aside>
💡 스택의 맨 밑(stack bottom)보다 아래에 접근하게 되면 페이지 폴트가 발생하게 된다. 이 페이지 폴트가 Stack Growth에 관련한 것인지 확인하는 과정을 페이지 폴트 핸들러에 추가하고, 맞다면 vm_stack_growth()
를 호출해 스택을 늘린다.
</aside>
매핑되지 않은 페이지에 접근했으므로 페이지 폴트가 뜰 텐데, 이제 페이지 폴트 핸들러가 이 페이지 폴트가 어떤 유형인지를 체크하게 된다.