6 min read

pintos - project3(Stack Growth) 수도 코드

이번 포스팅에서는 project3의 3번째 과제 Stack Growth를 다루겠습니다.기본적으로 이번 챕터의 제목과 같이 Stack 영역에 페이지를 새롭게 할당하는 기능을 구현합니다.

우선 깃북을 따라가며 구현을 해보겠습니다.

//vm.c
bool vm_try_handle_fault (struct intr_frame *f, void *addr,
    bool user, bool write, bool not_present);
  • 구현하기 앞서 해당 함수들의 인자들부터 파악해봅시다.
  1. f : 시스템 콜 또는 페이지 폴트가 발생했을 때,그 순간의 레지스터 값들을 담고 있는 구조체.
  2. addr : 페이지 폴트를 일으킨 가상 주소
  3. user : 해당 값이 true일 경우 현재 쓰레드가 유저 모드에서 돌아가다가 페이지 폴트를 일으켰음을 나타낸다.즉,현재 쓰레드의 rsp값이 VM의 유저 영역을 나타내는지 커널 영역을 나타내는지를 알려준다.
  4. write : true일 경우,해당 페이지 폴트가 쓰기 요청이고 그렇지 않을 경우 읽기 요청을 나타냄
  5. not-present : 해당 인자가 false인 경우는 read-only 페이지에 write를 하려는 상황을 나타냄.주어진 테스트 케이스에서는 mmap-ro 케이스가 해당 인자를 체크함
  • 인자들에 대한 이해를 하셨으면 해당 함수가 어떤 기능을 수행하는지 이해해야합니다.먼저 exception.c의 page_fault()함수를 보시면 #VM 옵션이 붙어있을 경우 해당 함수를 호출합니다.즉,페이지 폴트가 발생했을 경우 이를 처리하기 위해 호출되는 함수입니다.
  • 조금 더 구체적으로 말하면 해당 페이지 폴트가 발생한 가상 주소가 및 인자들이 유효한지 체크하고 stack growth를 하기위한 주소를 계산해서 실제로 vm_stack_growth() 함수를 호출해주는 역할을 합니다.
  • 결국 위의 기능을 깃북의 표현을 빌리자면 해당 page-fault가 유효한지,즉 vogue-fault인지를 판별해주는 함수입니다.
  • 구현에 도움을 드리자면 우선 인자로 들어오면 addr의 유효성을 검증하고,뒤이어 현재 쓰레드의 rsp_stack를 받아오거나 인터럽트 프레임의 rsp를 받아와 현재 쓰레드의 rsp 주소를 설정합니다.
  • 이후 해당 rsp가 유효하면 실제 vm_stack_growth()를 호출합니다.
  • 그 다음 해당 주소에 페이지를 만들었으니,spt_find로 해당 페이지를 찾은 뒤 해당 페이지의 구조체와 write인자를 통해 쓰기 검사를 진행합니다.
  • 이후 해당 페이지에 대해 vm_do_claim_page()을 진행하며 실제로 물리 프레임과 해당 페이지를 연결시켜줍니다.(UNINIT에서 ANON 타입으로 변경될것입니다)
  • 위의 과정들을 정상적으로 거친다면 true 값을 리턴하며 함수를 종료합니다.

참고로 추가적으로 thread.h 파일에 void* rsp_stackvoid* stack_bottom;을 추가해줍니다. rsp_stack의 경우 현재 쓰레드의 rsp 주소값을 담는 변수이고 stack_bottom현재 쓰레드의 stack영역의 끝 지점을 파악하기 위해 선언하는 변수입니다.즉,stack을 벗어나는 접근이 있을 경우를 예외처리하기 위해 사용합니다.

//vm.c
void vm_stack_growth (void *addr);
  • 해당 함수에서는 들어온 가상주소를 통해 UNINT 페이지를 만들고 위에서 생성한 stack_bottom 값을 설정해 줍니다.즉,기존에 rsp값에서 한개의 페이지 사이즈만큼 마이너스해줘야합니다.
  • 조금 더 설명하면 함수 이름 그대로 stack을 grow 시켜 주는데 이를 UNINIT페이지를 할당함 grow시켜 줍니다.
  • 그리고 성공적으로 grow된 페이지는 추후 vm_try_handle_fault()함수에서 claim이 되어 물리 frame과 연결되어집니다.

이렇게 깃북에서 구현하라는 함수를 구현했습니다.

지금부터는 추가적으로 userprog/syscall.c에서 수정해야할 사항들을 알려드리겠습니다.

struct page* check_address(void * addr)
  • 기존의 시스템 콜에서 저희가 생성했던 함수입니다.
  • 잘 생각해보면 인자로 들어오는 addr은 당연히 가상 메모리의 주소일 것이고,해당 주소를 가지는 페이지 또한 무조건 존재해야하는 상황일것입니다.
  • 즉,시스템콜을 호출한 쓰레드의 SPT가 해당 페이지를 가지고 있어야 한다는 뜻입니다.
void check_valid_buffer(void* buffer, unsigned size, void* rsp, bool to_write)
  • 해당 함수는 read,write의 인자로 들어오는 buffer, size 그리고 해당 쓰레드의 rsp값의 유효성을 검증합니다.
  • 구현적으로 도움을 드리자면 buffer라는 가상주소를 포함하는 페이지가 존재할것입니다.그리고 이 주소로부터 size만큼을 저희는 사용해야합니다.즉,buffer라는 가상주소를 담는 페이지가 시작페이지이고 (buffer+size-1)의 주소를 갖는 페이지는 끝페이지입니다.
  • 위 두 페이지들을 PGSIZE만큼 iter를 돌면서 check_addrress() 통해 검증을 해줍니다.
  • 또한 iter 내에서 해당 함수들의 write에 대한 검증 또한 진행해줍니다.

사실 위 두 함수의 경우 저희 팀에서도 구현할때 다른 블로그을 참조하며 작성하였습니다.쉽게 떠올리기 힘든 부분인것 같았습니다.만약 제 깃헙을 참조를 하실 경우 구현을 하고 나더라도 꼭 코드를 이해하셨으면 좋겠습니다.

이까지 진행을 하시면 아마 50개 가량의 테스트만 fail이고 project2의 테스트는 모두 통과가 됩니다.