PintOS Project3-3

정글일지38

Stack growth

질문

User programs are buggy if they write to the stack below the stack pointer, because typical real OSes may interrupt a process at any time to deliver a “signal,” which modifies data on the stack. However, the x86-64 PUSH instruction checks access permissions before it adjusts the stack pointer, so it may cause a page fault 8 bytes below the stack pointer.

유저 프로그램은 스택에 wtire 할 때 스택 포인터보다 아래에 하면 버그가 발생한다. 일반적으로 실제 OS들은 스택의 데이터를 수정하는 “신호”를 전달하기 위해 언제든지 process에 interrupt하기 때문이다. 하지만 x86-64 PUSH 인스트럭션은 스택 포인터에 적용하기 전에 접근 허가를 확인해서, 스택포인터보다 8바이트 아래에 page fault를 발생시킨다.

위 문단이 의미하는 내용은..?

스택은 함수 호출 정보나 지역 변수, 반환 주소와 같은 임시 데이터를 저장하기 위해 사용되는 메모리 구역이다. 메모리에서 스택은 “downward(아래로)” 커지는데, 이 말은 스택의 top을 가리키는 스택 포인터(SP)가 데이터가 쌓일수록 더 아래(below)의 메모리 주소를 가리킨다는 뜻이다. 스택 포인터는 현재 스택 프레임의 top(끝)을 가리킨다. 함수가 호출되면, 스택포인터는 공간을 할당하기위해 감소함(낮은 주소를 가리킴). 함수가 반환하면, SP(스택 포인터)는 함수의 스택 프레임 호출때까지 다시 증가하여 올라간다.

쉽게 말해, 고드름 처럼 자란다고 생각하면 될듯 하다. 고드름이 처음에는 땅에서 2m높이에서 자라지만 고드름이 점점 커져서 아래로 자라면 땅에서 1m길이 까지 내려올 수도 있다. 고드름은 자랐지만 고드름의 주소(해발고도)는 2에서 1로 감소했다!

이러한 규칙에서, 만약 프로그램이 스택 포인터보다 아래의 주소에 작성하면, 버그로 판정된다. 왜냐하면 운영체제가 “신호”를 전달하기 위해 언제든지 프로세스를 interrupt할수 있기 때문이다. 신호는 운영체제가 프로세스에게 특정 event가 발생했음을 알리는데 사용되는 방법이다. 신호가 전달될 때, 운영체제는 일반적으로 스택의 데이터를 수정한다. 스택 위에 신호와 관련된 context가 담긴 새로운 프레임을 ‘push’한다. signal(신호) handler function은 신호를 처리하는데 이 정보들을 사용하게 된다. signal handler function은 이러한 신호들을 다루기 위해 프로세스가 등록한 함수들이다.

유저 프로그램이 스택 포인터보다 밑에 write하면, 이 데이터는 신호가 전달될 때 덮어씌여질 수 있다. 왜냐하면 운영체제는 메모리 지역이 free한 상태여서 사용할 수 있는 것으로 가정하기 때문이다. 이 결과로, 정의되지 않은 crash,bug등이 유저프로그램에서 초래될 수있다.

x84-64의 ‘PUSH’ 인스트럭션은 처음에 SP를 감소시킨 후에 스택 포인터가 가리키는 새로운 위치에 데이터를 작성한다. x86-64 아키텍쳐는 메모리 보호 기능을 보유하고 있어서 프로그램이 유도되지 않은 메모리 지역으로 접근하려고 시도하면 page fault를 발생시킨다. 만약 ‘PUSH’ 인스트럭션이 SP를 보호받는 메모리 지역(일반적인 환경이 아닌)으로 이동시키면, page fault가 발생한다. page fault는 프로그램이 RAM에서 현재 가용 메모리 지역이 아닌 곳에 접근하려할 때 촉발되는 interrupt이다.

결론적으로 프로그램이 SP를 보호중인 구역같은 곳으로 부적절하게 조절하면, PUSH 인스트럭션은 데이터를 메모리에 작성하기 전에 page fault를 발생시킨다! 왜냐하면 PUSH 인스터력션이 데이터 작성 시도 전에 접근 권한을 확인하기 때문이다.

해야할 일

함수

bool vm_try_handle_fault(Struct intr_frame *f, void *addr, bool user, bool write, bool not_present);

이 함수는 userprog/exception.cpage_fault()에서 호출된다. 이 함수에서, page fault가 stach growth에 타당한 case인지 아닌지 확인하도록 해야한다. page fault가 stack growth로 처리될 수 있음을 확인하면, vm_stack_growth를 page fault가 발생한 주소로 호출해야한다.

void vm_stack_growth(void *addr);

하나 이상의 anonymous page를 할당해서 스택 크기를 증가시키고 주소 addr가 더이상 page fault 주소가 어니도록 해준다. 할당 할때 주소 addr을 PGSIZE 까지 round down해야 한다.

참고 사항

대부분의 운영체제들은 스택 사이즈에 제한을 두고 있다. 어떤 운영체제들은 제한을 유저가 조절할 수 있도록 해놓았는데 많은 Unix 시스템에서 ulimit 명령어가 그 예이다. 대다수의 Linux 시스템에선, 기본 설정된 제한은 8MB이다. 이 프로젝트에서는, 최대 1MB로 둬야 한다.

구현 결과

모든 stack-growth 테스트가 통과해야함