[PintOS] PROJECT4: VIRTUAL MEMORY(Stack Growth, Memory Mapped Files, Swap In/Out) - 4주차 WIL (2) - Stack Growth

bongf·2022년 6월 21일
0

정글

목록 보기
18/20

1. CPU 스택의 연산

지난 주에 해결 안되었던 문제를 동료의 질문과 조교님의 답변, 그리고 참고한 책으로 해결할 수 있었다.

래 문제 상황을 정리하기 위해 개념을 다시 간단하게 정리하고 넘어가야 한다

(1) 기본 개념

스택연산의 기본 이해

subq &8, %rsp // 스택 포인터 8 바이트 먼저 감소시키기
movq %rbp, (%rsp) // rbp를 스택에 저장 
  • push 연산은 위 인스트럭션과 동일하다

페이지 폴트 이해

  • 참조한 가상주소에 상응하는 페이지가 메모리에 없는 경우 발생한다.

(2) Stack Growth 미션시에 의문. 왜 rsp값은 실제 페이지가 할당된 마지막 주소 - 8 보다 한참 아래 을까

  • 페이지 테이블에 없는 가상 주소를 참조한 경우 page_fault 가 발생하고, 이에 대해 확인해야 하는 조건은 다음과 같다.
    1. 해당 addr까지 페이지를 증가시켰을 때 USER_STACK의 범위인지 확인해야 한다. 스택이 커질 수 있는 범위 밖이면 안되기 때문이다. if(USER_STACK - (uint64_t)addr <= ONE_MB)
    1. 해당 주소가 rsp-8 인지를 확인한다. rsp는 마지막 스택 포인터를 가리키고 있다. rsp가 스택페이지의 border에 있는 경우 push 인스트럭션은 rsp보다 8바이트 아래의 주소를 접근을 시도해서 page_faultrk 발생하므로 페이지 폴트가 발생한 주소가 rsp보다 8바이트 아래 인지 확인해야 한다.

여기까진 잘 이해가 갔지만 문제가 발생했다. 처음에는 해당 부분을 확인하고 페이지를 한 페이지만 할당해줬다.

당연히 rsp 보다 -8 한 것만큼 아래이므로 한 페이지면 될 것 같다는 생각에서였다. 그런데 테스트는 실패했고 실제 주소를 찍어보니

이렇게 실제 rsp가 마지막으로 사용되었을 것이라 추정되는 페이지 (첫 페이지) 보다 한 참 아래에 f->rsp가 있었고, 그것에 -8한 주소를 요청해 page falut가 나는 것이었다.

페이지가 없는데 어떻게 rsp가 저기로 이동하는지, page도 없는데 거기서 어떤 작업을 했길래 rsp-8로 추가적인 코드 실행이 있었는지 이해가 가지 않았다'

rsp-8로 한 칸 한 칸씩 stack push 하고 pop 하는줄 알았기 때문이다.

(3) 해결 1 : rsp는 스택 포인터 외에도 다른 용도로 사용된다.

  • 조교님 답변으로 이에 대한 의문을 해결할 수 있었다.
  • rsp는 push, pop 외에도 다른 용도로 사용되기 때문에 페이지가 없는 곳으로 rsp가 이동할 수 있다. 예를 들어 크기가 매우 큰 배열을 선언한 경우와 같다.

조교님 답변을 우선 보면,

rsp는 push/pop instruction에 의해 +-8 bytes씩 이동하는것 이외에도 arithmetic instruction과 indirect memory reference instruction의 인자로 사용될 수 있습니다.

  • 조교님 말씀으로 지역변수가 저장될 때에도 rsp를 이동시키는 것으로 이해했다. 예를 들어 큰 배열을 선언한 경우 rsp는 실제 페이지가 있는 것보다 훨씬 더 아래에 대해 위치하게 된다.
  • 해당 rsp의 이동은 memory에 대한 access가 아니므로 페이지 폴트가 발생하지 않는다.
  • pt-grow-stack이라는 테스트를 실제로 디버깅 해봤을 때 test_main()에서 배열을 선언하고 arc4를 선언한 곳까지 에서는 페이지 폴트가 나지 않았다.
  • arc4_int() 함수를 호출하면 바로 페이지 폴트가 났는데, 디버깅 했을 때 arc4_init()이 실행되기 전에 페이지 폴트가 났다.
  • 이는 조교님 말씀 처럼 arc4_init() 함수를 실행하기 전에 return address를 저장하기 위해(아래수정) push 연산이 일어나고 이 때문에 rsp - 8 이 발생하며, 이는 push 연산이라 단순히 rsp 를 이동해 준 것과 달리 페이지 폴트가 발생한다.
  • 그러니까 배열 선언과 같은 다른 용도 때문에 rsp가 페이지가 없는 곳까지 이동하고 이에 대한 메모리 참조 연산 때문에 페이지 폴트가 난다. 그렇기 때문에 실제 마지막 스택 포인터가 가리키고 있던 곳과(페이지가 할당 된 곳) rsp 차이가 난다는 것을 알게 되었다.

추가 디버깅

좀 더 세밀하게 디버깅 해보니 return address 를 저장하기 위해서 push 연산이 일어나는 것이 아니었다. test_main()에서 명시되어있지 않지만 pintOS의 테스트는 실행시 아래와 같이 결과가 출력되는데 이를 위한 write 요청이 일어나 페이지 폴트가 발생하는 것이어었다.

  • stack_obj 선언으로 rsp가 한참 밑으로 내려간 위치에서 write 시스템콜이 호출되어 push 연산이 일어나면서 pagefault가 발생하게 된다

(4) 실제 OS에서는? %rbp : 스택 포인터

  • 출처 : 책, 컴퓨터 시스템, 위키

내용 계속

  • 이 부분에 관해 더 이해를 하고자 컴퓨터 시스템을 찾아보다가 %rbp에 쓰임에 대해서 알게 되었다.
  • 실제로 스택포인터는 위에서 봤던 것처럼 배열을 선언하고 연산을 하는 등 함 함수를 실행하면서 %rsp 값이 이리저리 바뀌게 된다.
  • 이제 함수가 끝나고 return 할 때를 생각해보면, 어디로 return해야 할지 모를 것이다. 위에서 본 것처럼 배열의 선언 등이 있다면 실제 이 함수가 처음 호출된 곳을 모른다. ( 그곳에 return address가 있을 텐데)
  • 그래서 %rbp 레지스터를 frame pointer로 사용한다. 함수가 호출되기 직적에 스택포인터의 복사본을 저장한다.
  • 그리고 함수가 return 할 때 frame pointer로 복귀한다.
  • 아래는 함수가 종료될 때 실제 실행되는 어셈블리어다. (컴퓨터 시스템 책의 내용)
  • 함수가 끝나면 rsp를 rbp로 이동시키고 popq rbp를 하면 이 함수를 불렀던 호출자로 돌아갈 return address를 참조할 수 있다.
  • 그러니까 rsp 값이 아무리 바뀌어도 return 할 때 rbp로 돌아가면 된다.
  • 위에 테스트 코드에 대한 어셈블리어도 이렇게 rbp를 먼저 push 하고 rsp를 저장한다.

(5) 코드 실행해보니

  • 실제로 페이지 폴트를 확인할 때 rbp를 기준으로 요청된 addr까지 페이지를 만드는 수정했더니 테스트를 통과했다.
profile
spring, java학습

0개의 댓글