프로세스 메모리 레이아웃

dahyeon·2022년 8월 22일
1
post-custom-banner

우리가 작성한 코드는 하나의 프로그램이다. 프로그램을 실행하기 위해서는 컴퓨터의 메모리에서 공간을 할당받아야 한다. 프로세스는 할당받은 공간에 코드, 전역 변수, 지역 변수, 함수, 포인터 등 실행할 때 필요하거나 실행하면서 생성되는 여러 데이터들을 저장한다. 각 프로세스의 메모리 공간은 여러 영역으로 구분되어 있으며, 데이터들은 종류에 따라 각기 다른 영역에 저장된다.

본 포스팅에서는

✔️ 프로세스 메모리 레이아웃과 각 영역에 어떤 데이터가 저장되는지 알아볼 것이다.


프로세스 메모리 레이아웃(C언어 기준)

  • 메모리 레이아웃(Memory layout)

일반적인 프로세스는 다음 그림과 같이 메모리 영역이 구분되어 있으며, 이러한 메모리 구성을 메모리 레이아웃이라고 부른다.

Text segment

Text 영역에는 프로그램의 코드가 저장된다.

프로그램을 컴파일하면 작성한 코드가 기계어로 번역되어 바이너리 파일이 생성되는데, 이 파일에는 프로세서가 실행 가능한 명령어(executable instructions)들이 포함되어 있다. 이러한 명령어들이 text 영역에 저장되며, 이 영역은 외부에 의해 쉽게 변경되지 않도록 보통 읽기만 가능하다는 속성을 갖는다. CPU는 text 영역에 저장된 명령어들을 하나씩 가져가서 처리한다.

Initialized data segment

초기화 된 전역 변수/정적 변수가 저장되는 영역이다. Text 영역과 달리 읽기 전용은 아니며, 변수의 값은 프로그램 실행 중에 변경될 수 있다.

🤔 (C에서) 정적 변수란? 참고 링크

선언된 범위(Scope) 밖에서도 그 값을 보존하는 변수. 새로운 범위에서 다시 초기화할 수 없다.

Uninitialized data segment

“bss”영역이라고도 부르며, 초기화되지 않은 전역 변수/정적 변수가 저장된다. 실행될 때 커널에 의해 0 또는 포인터의 경우 null 포인터로 초기화된다. 읽기-쓰기 모두 가능하다.

#include <stdio.h>

// Uninitialized global variable stored in the bss segment
int global_var1; 
const int global_var2 = 30;

int main()
{
    // Uninitialized static variable stored in bss
    static int static_var1;
		static int static_var2 = 10;
    
    return 0;
}

위 코드에서 global_var1, static_var1은 uninitialized data segment에 저장되며, 실행될 때 커널에 의해 0으로 초기화되기 때문에 printf 함수를 통해 값을 확인해보면 0이 나온다.

반면 global_var2, static_var2는 initialized data segment에 저장된다.

Stack

함수가 호출되면 스택 영역에 함수의 지역 변수와 매개변수가 저장된다. 스택 영역에 저장되는 함수의 호출 정보를 스택 프레임(stack frame)이라고 한다.

  • 스택은 프레임들이 쌓여있는 하나의 큰 배열이라고 볼 수 있다. 가장 늦게 저장된 데이터가 가장 먼저 인출되는 LIFO(Last In First Out) 방식으로 작동한다.
  • 프레임 포인터(frame pointer)는 현재 프레임의 베이스 주소(base address, 프레임 시작 위치)를 가리킨다. 인텔 x86 아키텍처에서는 ebp라고 하며, 64bit 아키텍처에서는 rbp라고 한다.
  • 스택 포인터(stack pointer)는 현재 쌓여있는 데이터의 가장 끝 부분(top, 스택의 최상단)이면서 다음 아이템이 저장될 부분을 가리킨다. esp 또는 rsp라고 한다.

함수가 호출되면 (참고 링크)

1) 기존 프레임 포인터 값을 스택에 넣어 저장한다.

2) 현재의 스택 포인터 값을 프레임 포인터에 저장한다. 프레임 포인터는 호출된 함수 프레임의 시작 위치를 가리키게 된다.

3) 스택 포인터가 이동하여 (크기는 감소) 함수 호출에 필요한 공간을 확보한다. 스택 포인터 값은 항상 스택의 최상단을 가리킨다.

4) 함수가 실행되면서 지역 변수들이 스택에 저장된다. 지역 변수들의 레퍼런스는 프레임 포인터 값을 기준으로 설정된다.

5) 함수 실행이 끝나면 스택 포인터는 프레임 포인터 값으로 되돌아간다.

6) 프레임 포인터는 저장된 프레임 포인터 값으로 복원되고, 호출된 함수의 프레임이 인출된다. 스택 포인터가 반환 주소가 저장된 곳을 가리키므로, 반환 주소가 가리키는 다음 명령이 이어서 실행된다.


다음의 코드를 실행할 때 ebp, esp의 변화를 살펴보자.
void
bar(int a, int b)
{
    int x, y;

    x = 555;
    y = a+b;
}

void
foo(void) {
    bar(111,222);
}

1) 함수 foo가 실행되었다. ebp 값이 스택에 push되고, 이어서 매개 변수 값들이 저장된다.

2) foo에서 bar가 호출되었다. 스택 영역에 반환 주소(return address)가 저장된다. 반환 주소에는 호출된 bar 함수가 종료된 후, foo에서 이어서 실행할 명령어의 주소가 저장된다.

3) bar가 실행되면서 프레임 포인터가 스택 포인터 위치로 이동한다. foo의 프레임 포인터 값이 스택에 저장된다. 스택 포인터가 16bytes 크기만큼 이동해서 함수 실행에 필요한 공간을 할당한다.

4) bar의 지역변수가 스택에 저장된다. ebp-4, ebp-8은 각각 프레임 포인터(ebp)로부터 4bytes, 8 bytes 떨어져있음을 의미한다.

5) bar의 실행이 끝나면 스택 포인터가 프레임 포인터 위치로 이동한다. 스택에 저장된 ebp 값으로 ebp가 복원되고, bar 함수의 스택 프레임이 해제된다. 스택 포인터는 foo 함수의 반환 주소를 가리키게 되고, foo 함수가 이어서 실행된다.

Heap

힙은 주로 동적 메모리 할당에 사용된다.

🤔 동적 메모리 할당이란? 참고 링크

프로그램을 실행 단계(run time)에서 메모리 공간을 할당받는 것을 말한다.

  • C에서는 malloc() , realloc() 함수를 통해 직접 힙 영역에 메모리를 할당할 수 있다. 할당받은 메모리는 사용 후 free() 함수를 통해 다시 운영체제로 반환한다.
  • BSS 영역이 끝나는 곳부터 시작해 스택과는 달리 점점 주소가 증가하는 방향으로 할당된다.
  • 주로 참조 타입의 데이터 - objects, maps 등이 저장된다.
  • RAM의 크기는 한정되어 있기 때문에 메모리 관리가 필요하다. 동적으로 할당한 메모리 영역 중 필요 없게 된 영역을 해제하는 기능을 가비지 컬렉션(garbage collection)이라고 한다.

참고 자료

메모리 레이아웃 관련
Memory Layout of C Programs - GeeksforGeeks
🚀 Demystifying memory management in modern programming languages
코딩교육 티씨피스쿨

text segment 관련
text, data, bss, and dec

stack 관련
What is a Stack Frame? - Definition from Techopedia
Stack frames
스택 프레임(Stack Frame)
Stack Memory: An Overview (Part 3)

profile
https://github.com/dahyeon405
post-custom-banner

0개의 댓글