메모리 영역은 크게 5가지로 나뉜다. Windows와 Linux에 차이는 있지만 기본적인 뼈대는 두 OS 모두 같을 것이다.
1. 메모리 영역
메모리 영역은 위와 같이 5개로 나뉜다. 기본적으로 code, data, bss는 컴파일 과정에서 사이즈가 할당되며 해당 사이즈는 고정으로 변하지 않는다.
기본적으로 코드를 작성해서 컴파일 후 .exe와 같이 실행파일을 만들게 되면 .exe와 같은 실행파일에는 bss, data, code 데이터만 들어가 Hard Disk에 저장된다.
그리고 해당 파일(.exe)을 실행하면 bss를 메모리에 로드하고 bss를 통해서 지역 stack 주소 값을 계산한다.
이후에는 stack에 저장된 int a = 4와 같은 지시문이 실행되면서 4 byte공간을 할당하고 a라는 변수에 4라는 값을 저장 즉 stack (지역)을 실제적으로 메모리 할당한다. Heap도 마찬가지로 malloc등의 지시자를 runtime 과정에서 실행하면서 Heap 영역에 메모리를 할당한다.
결과적으로 stack, heap에 저장되는 지역변수, 동적변수는 실제 .exe파일을 실행하기 전에는 메모리 할당 data가 저장된 공간이 존재하지 않는다. 해당 공간은 .exe를 실행함으로써 메모리 할당이 이루어지고 실제적으로 데이터가 저장된 주소 공간이 만들어 지는 것이다.
<heap은 bss경계선을 기준으로 위로 올라가고 stack은 최대 주소 값(약 FFFFFFFFF)에서 부터 아래로 점점 내려오는 구조로 메모리를 할당한다.(FFFFFFFF에서 4byte할당 시 FFFFFFFC가 되는 형태이다)>
1) Code
Code는 메모리의 가장 아래 공간에 위치하며 해당 공간을 text라고도 한다. 보통 기계어가 저장되는 공간이다.
또한 함수와 지시문, 분기문, 상수 등이 저장되는 공간이다. Code영역은 실제적으로 기계어로 지시되며 .exe파일 실행시 명령문들을 담고있다. 해당 영역은 컴파일 시에 결정되며 이후 Read-only성격으로 수정이 불가능하고 프로그램 종료시 까지 메모리에 올라와있게 된다.
2) Data(bss포함)
data영역은 전역변수와 정적변수(static)가 저장되는 공간으로 프로그램 시작 시 할당되며 프로그램 종료시까지 유지된다.
data영역은 또 3가지로 나뉘게되는데
- readonly
- read&write
- 미할당
위와 같이 3가지 영역으로 구분된다.
readonly의 경우 .rdata 라고 하며 해당 영역에 저장되는 값들은 const, final, 문자열과 같은 상수 값들이다. 그리고 write & read의 경우에는 .data영역이라고하며 기본적으로 정적 변수 등이 저장되는 공간이다. 마지막으로 미할당 영역은 BSS영역이라고 하며 선언은 되었으나 아직 값이 할당되지 않은 변수들에 대한 부분으로 main문이 시작되기 전에 NULL 값을 가지는 변수들로 동적 할당 등을 통해 main문이 실행 되는 runtime 과정에서 추가적으로 값이 할당된다.
ex) char *ptr ="Hi";와 같이 전역변수를 선언하면
ptr이라는 변수는 .data영역에 저장되고 "Hi"라는 변수는 .rdata에 저장된다.
3) Stack
지역 변수들이 저장되는 공간으로 push, pop을 통해 메모리 할당과 해제가 진행되며 빠른속도를 바탕으로 메모리를 필요에따라 할당하고 해제하는 공간이다.
함수 호출 시 해당 함수의 메모리, 지역 변수 등을 할당할 수 있으며 잘못된 재귀 등을 통해 너무 많은 변수 및 함수를 할당하게되면 stack영역을 넘어서 heap영역까지 도달하게 되고 이 경우에 stack overflow 문제가 발생한다.
stack영역은 기본적으로 프로그램 실행을 위해 사용하는 함수나 변수들의 메모리를 할당하는 임시 공간과 같은 개념으로 변수와 함수의 역할이 끝나면 자동으로 메모리 공간을 해제하는 장점을 가지고 있다. 하지만 heap의 경우에는 컴파일러나 가상머신이 해제를 도와주긴하나 원칙적으로 할당된 메모리 공간은 자동으로 해제되지 않고 남아있게된다. 때문에 안전한 프로그래밍을 위해서는 개발자에게 자신이 할당한 메모리 공간을 해제하기 위한 노력이 반드시 요구된다.
- Stack과 레지스터
기본적으로 stack에 변수, 함수를 할당하고 해제할 때는 register가 함께하며 register에 대한 이해가 필수적이다. 이 때 가장 핵심이 되는 레지스터는 SP(Stack pointer), BP(Base pointer), IP(Instruction Pointer)이다. CPU의 bit에 따라 ESP, EBP, EIP로 불리기도 한다.
기본적으로 SP는 스택포인터 즉 스택의 현재 진행 중인 지점을 가리킨다. 그리고 BP의 경우 스택의 경계 즉 BP는 stack의 맨위(heap과 가장 먼) 곳인 stack의 시작점을 가리키며 함수 별 구분을 위해 각 함수의 가장 base가되는 맨 위 주소값을 가리킨다. 예를들어 main문이 시작되면 맨처음 main문이 시작된 스택 주소 값을 BP는 가리킨다. 그리고 변수들이 할당되면 SP와 같이 stack에서 heap쪽으로 값이 push되며 heap쪽으로 내려 갈 것이다. 여기서 만약 다른 함수를 호출하게된다면 해당 함수의 내용이 SP를 기준으로 push될텐데 이 때 이 함수의 시작 주소값으로 BP를 옮겨준다. 그리고 해당 함수에서의 변수 할당 등이 진행되면 SP가 main때와 마찬가지로 내려가며 값들을 push할 것이다. 해당 함수가 리턴되고 다시 main문으로 나온다면 BP값 역시 main문의 시작 주소 값으로 바뀌게 된다. 마지막으로 IP는 실행할 명령어를 저장하는 공간이다.
- Stack 할당 과정
sfp는 함수의 시작지점을 의미하고 rip은 다음 실행할 명령을 저장해두는 공간이다.
위와 같이 int a=4, int b=5 가 순서대로 실행되면 stack에서는 esp를 이동해가면서 실제적으로 a=4와 b=5를 push한다.
여기서 test라는 함수를 호출하면 stack은 위와 같이 된다. main문에서 tset()함수를 호출함으로써 CPU는 test함수 주소 값으로 이동해서 작업을 진행한다. 이 때 test()함수 리턴 시 main문을 이어서 진행해야 하므로 test 함수 리턴 이후 진행해야되는 주소 값에 대한 정보를 rip에 저장하여 stack에 push한다.
그리고 새로운 함수 test()에 대한 함수 시작지점인 sfp를 만들고 ebp, esp를 해당 sfp로 MOV 명령을 통해 이동시켜준다.
그리고 위와 같이 test() 함수 내 존재하는 명령을 수행하며 변수 c에 대한 할당을 진행한다. 이 경우 main때와 마찬가지로 esp가 이동하며 변수를 할당한다.
그리고 함수가 리턴되면 esp는 ebp 위치로 이동한다. 즉 해당 함수의 시작 주소값이 있는 곳으로 MOV명령을 통해서 SP 레지스터를 옮겨준다.
그리고는 pop을 해주면서 ebp는 이전 함수의 시작지점으로 이동되고 esp의 경우 rip을 가리키게 된다. 그리고 rip을 읽어와서 다음으로 실행할 주소 값을 얻어온다.
그리고 이와 같이 stack이 정리된다. 이 후 변수 할당이 진행된다면 esp를 기준으로 값이 push되면서 현재 rip으로 되어있는 값에 덮어쓰기가 될 것이다.
여기서 마지막으로 함수를 실행하는 과정을 조금 더 자세히 살펴보면 아래와 같다.
함수를 실행한다는 것은 해당 함수에 해당하는 명령어(지시어)를 PC, IR 레지스터에 올리고 해당 명령을 실행하는 과정을 의미한다. 기본적으로 각 함수 명령어는 Code영역에 저장되어 있다 즉 함수 호출을 진행하게 되면 PC 레지스터에 Code영역에 존재하는 명령어의 주소 값을 넣어주고 IR은 해당 값을 읽어와 실행하게 되는 것이다. 그리고 해당 명령문을 실행하는 과정에서 발생하는 변수 할당 및 함수 리턴 후 실행해야되는 다음 명령문 등에 대한 정보(rip, sfp)를 모두 stack에 저장하는 것이다.
위와 같은 과정으로 프로그램이 동작하기 때문에 stack 영역을 프로그램 동작을 위해 사용하는 임시영역이라고 하는 것이다.
4) Heap
Heap은 동적할당이다. vmmap으로 프로그램의 메모리 구조를 살펴보면 동적으로 값이 할당 되었을 때 [heap]으로 중간에 갑자기 메모리가 할당된 것을 확인할 수 있다. 주소 값 중 처음도 끝도 아닌 중간인 이유는 heap의 할당 공간위치가 code, data와 stack 사이에 존재하기 때문이다. 즉 가장 처음인 0x00000000공간에는 code, data에 대한 값들이 메모리 할당된 것을 확인할 수 있고 0xFFFFFFFFF의 마지막 부분에는 stack에 대한 값들이 거꾸로 메모리를 할당하고 있음을 확인할 수 있다.
위와 같이 heap은 bss경계선에 해당하는 중간 부분에 위치하기 때문에 동적으로 할당된 값의 시작 주소 값을 보면 처음도 끝도 아닌 중간임을 확인할 수 있다.
이러한 heap은 실제적으로 Chunk 형태로 할당되고 해제되는데 heap에 대한 자세한 내용은 양이 많아 따로 정리가 필요할 것 같다..
'OS > Linux' 카테고리의 다른 글
[Linux Kernel] DT (디바이스 트리) (0) | 2021.01.09 |
---|---|
[Linux] 메모리 영역 (2) - Heap (0) | 2020.12.26 |
[Linux] IPC 통신(PIPE, Message, Shared, Memory Map, Socket, RPC) (0) | 2019.08.08 |
[Linux] Modify bit(Dirty bit) (0) | 2019.08.06 |
[Linux Kernel] 커널의 종류(마이크로 커널 & 모놀리식 커널) (0) | 2019.08.06 |