[0814 발표] 스택에 값을 푸시하는 방법

방법이있지·2025년 8월 13일
post-thumbnail

vm_stack_growth

이번 핀토스 Virtual Memory 과제를 수행하면서 vm_stack_growth라는 함수를 구현해야 했는데요,
스택 영역에 데이터를 푸시할 때 할당된 메모리가 부족할 때, 추가로 페이지를 할당해 주는 함수입니다.

그런데 프로젝트 깃북에서는 1개 이상의 페이지를 할당해야 한다고 설명이 되어 있더군요.

전 처음엔

분명 어셈블리어 push 명령어는 8바이트 단위로 이루어졌었지. 한 페이지는 4096바이트인데 그것보단 훨씬 작은 크기잖아. 그러면 무조건 한 페이지만 할당하면 되는 거 아닌가?

라고 생각을 했었습니다.

스택 성장 방법

문제는 스택의 영역을 확장하는 방법이 두 가지였다는 점입니다.

첫째, 제가 생각했던 것처럼 push 명령어를 사용해, rsp(스택포인터)를 8바이트 낮춘 뒤 해당 위치에 데이터를 복사할 수 있습니다.

push rax          ; rsp를 8바이트 낮추고 rax에 저자된 데이터 복사

그런데 한 가지 방법이 더 있더군요.

둘째, subrsp를 원하는 크기만큼 낮추고, mov로 데이터를 직접 복사하는 방법이 있습니다.

sub rsp, 65536              ; rsp를 65536B 낮춤
mov rdi, rsp                ; rdi에 저장된 값을 스택에 복사

이 경우 rsp를 복사할 데이터의 크기만큼 낮추게 되는데, 한 페이지의 크기인 4096바이트를 훌쩍 뛰어넘을 수 있습니다.

특히 이번 테스트 케이스인 pt-grow-stk에선 65536바이트 크기의 배열이 스택에 잘 푸시되는지 확인하게 되는데, 무려 65536/4096=1665536 / 4096 = 16이니까 16페이지에 달하는 크기인 거죠.

올바른 스택 확장 방법

이런 경우 한 페이지만으로 데이터를 다 담을 수 없습니다. 즉 기존 스택 영역이랑 겹칠 때까지, 계속 주소를 증가시키면서 페이지 할당을 반복해야 합니다.

static void vm_stack_growth(void *addr) {

    void *c_addr;
    // 페이지폴트 발생 주소의 페이지 경계 맞춤 (pg_round_down)
    for (c_addr = pg_round_down(addr); ; c_addr += PGSIZE)
    // 페이지 할당 시도
        if (!vm_alloc_page_with_initializer(VM_ANON | VM_MARKER_0, c_addr, true, NULL, NULL)){
        	// 이미 스택 할당된 페이지 도달 시 종료
            break;
    };
}

일단 매개변수 addr로는 페이지 폴트가 발생한 주소가 전달됩니다.

vm_alloc_page_with_initializer가 실제로 페이지를 할당하는 함수인데, 여기에 가상주소 c_addrPGSIZE씩 증가시키며 반복 할당을 합니다.

그리고 이 함수는 가상주소가 이미 할당되어 있는 경우 false를 반환합니다. 그때까지 무한 반복을 해 주면 됩니다.

후기

핀토스는 "모르면 맞아야지" 식의 과제인 것 같습니다. 너무 어려워요.
ㄹㅇ 이번에도 스택 확장되는 방식이 저런 식인 줄 제가 어떻게 알았겠냐고요.
그래서인지 수많은 테스트 케이스 Fail을 마주하면서, ㄹㅇ 맞으면서 공부를 한 것 같습니다.
확실히 디버깅하면서 문제의 원인을 파악하니 운영체제가 작동하는 전체 과정의 큰 그림이 잘 그려지긴 하더군요.
하지만 한 번으로 충분하지 두 번은 못 할 것 같아요^^

profile
뭔가 만드는 걸 좋아하는 개발자 지망생입니다. 프로야구단 LG 트윈스를 응원하고 있습니다.

1개의 댓글

comment-user-thumbnail
2025년 8월 13일

대본

안녕하세요. 저는 vm_stack_growth 함수를 구현하면서 겪은 트러블슈팅을 공유해 보겠습니다. 아시다시피 스택 메모리가 부족할 때, 추가로 가상페이지를 추가로 할당하는 함수입니다. 그런데 깃북에선 one or more anonymous pages, 즉 1개 이상의 가상 페이지를 할당해야 한다는 언급이 있었습니다. 여기서 "1개 이상"인게 이해가 안 갔습니다. 분명 푸시는 8바이트 단위로 알고 있었어서, 한 페이지를 넘을 일이 없지 않나 싶었습니다.

실제로 push 명령어로 rsp를 8바이트 낮추고, 데이터를 복사할 수 있습니다. 문제는 스택 푸시가 꼭 push 명령어로만 되지는 않는다는 점입니다. sub 명령어로 rsp를 원하는 만큼 낮춘 후, mov로 데이터를 복사하는 식으로도 푸시가 가능합니다. 실제로 65536바이트의 데이터가 스택에 잘 푸시되는지 체크하는 테스트 케이스가 있었죠.

1페이지가 4096바이트니까 총 16페이지가 필요합니다. 그러니까 한 페이지만으론 턱없이 부족하겠죠. 결국 페이지폴트 발생 주소에서 시작해서, 기존 스택 영역과 부딪히기 전까지 페이지 할당을 반복해야 테스트 케이스를 통과할 수 있었습니다.

핀토스는 참 "모르면 맞아야지" 식의 과제라는 생각이 듭니다. 스택 포인터를 직접 원하는 만큼 낮출 수 있다는 사실 자체를 몰랐는데, 그걸 누가 알려주지도 않잖아요? 직접 트러블 슈팅하면서 파악하는 게 유일한 돌파구였죠.
물론 핀토스 과제 진행하며 수많은 테스트 케이스 Fail을 마주하면서, 문제의 원인을 파악하려고 하는 과정에서 지금까지 헷갈렸던 운영체제의 원리들을 보다 잘 이해할 수 있었던 건 맞습니다.

그런데 어쨌든 많이 맞아서 아팠던 것 같아요. 나만무는 얼마나 아플지 참 기대가 됩니다. 감사합니다.

답글 달기