리눅스 운영체제에서 각각의 프로세스에 할당된 메모리는 데이터의 효율적인 관리를 위해서세그먼트(Segment) 라는 영역으로 나뉘어져 있습니다.
각각의 세그먼트에는 보안상의 이유 때문에
데이터의 용도별로 필요한 권한이 지정되어 있습니다.
코드 세그먼트에는 실행할 수 있는 코드 데이터를 저장합니다.실행을 위해 코드를 읽어야하므로 읽기, 실행 권한이 부여됩니다.
예시 사진에서 "void func() { ··· }", "int main() { ... }" 에 해당하는 부분이
코드 세그먼트에 저장됩니다.
데이터 세그먼트에는 초기화된 전역변수, 정적 변수가 저장됩니다.
기본적으로 읽기, 쓰기 권한이 부여되지만 보안을 위해서 정적 변수는
"read-only data"라는 세그먼트로 구분되어 저장됩니다.
read-only data 세그먼트에서는 오직 읽기 권한만 부여됩니다.
예시 사진에서 "int a = 5;", "static int c = 21;" 에 해당하는 부분은
코드 세그먼트에 저장됩니다.
"const int b = 4;" 에 해당하는 부분은 read-only data 세그먼트에 저장되며
권한은 읽기 전용으로 설정됩니다.
BSS 세그먼트에는 초기화되지 않은 전역변수가 저장됩니다.
데이터 세그먼트와 마찬가지로 읽기, 쓰기 권한이 부여되며
상수 변수는 ro-data 영역에 저장됩니다.
예시 사진에서 "int a;", "static int c;" 에 해당하는 부분이 BSS 세그먼트에 저장됩니다.
"const int b;"는 데이터 세그먼트에서 서술했던
read-only data 세그먼트에 저장되는 부분을 나타냅니다.
힙 세그먼트에는 동적으로 할당된 메모리 영역이 저장되어 있습니다.
따라서 힙 세그먼트는 그 크기가 가변적으로 변화할 수 있습니다.
힙이 확장될때는 주소가 커지는 방향으로 확장됩니다.
이는 스택 세그먼트의 영역이 확장될때와 반대의 방향입니다.
예시 사진에서 "int *a = malloc(4);", "int *ptr = malloc(6);" 에 해당하는 부분이
힙 세그먼트에 저장되는 영역을 나타냅니다.
스택 세그먼트에는 프로그램에서 임시적으로 저장하는 데이터들이 저장됩니다.
함수의 호출 여부에 따라서 할당해줘야하는 크기가 달라지기 때문에 크기가 가변적입니다.
스택 세그먼트의 영역은 가장 높은 주소에서 시작하며, 확장될때는
주소가 낮아지는 방향으로 확장됩니다.
힙 세그먼트의 영역이 확장될때와 반대의 방향입니다.
예시 사진에서 "int a;", "int b = 5;", "func()" 에 해당하는 부분이
스택 세그먼트에 저장되는 데이터를 나타내는 부분입니다.
힙과 스택이 다른 방향으로 확장되는 이유
스택은 함수의 매개변수등을 저장해야하고,
함수가 종료될때 이를 역순으로 정리하여야 합니다.
따라서 스택 최상단의 위치를 추적하는
SP(스택 포인터)를 관리하기 쉽게 하기 위해
스택이 세그먼트중 가장 높은 주소를 가지는것이 일반적입니다.
따라서 스택은 프로세스에 배정된 메모리 영역을 넘어가지 않기 위해서
낮은 주소로 확장되며,
힙은 더 낮은 주소에 다른 세그먼트의 데이터들이 저장되어 있기 때문에
높은 주소로 확장됩니다.
힙과 스택의 영역이 충돌한다면?
힙과 스택간의 주소 차이는 매우 크기 때문에
일반적으로 두 영역은 충돌하지 않습니다.
하지만 둘 중 하나의 영역이 매우 크게 확장되어서
서로의 영역을 침범하면 어떻게 될까요?
스택이 확장되어서 힙의 영역을 침범하게 되는 경우,
스택의 데이터는 힙의 데이터를 덮어쓰게 됩니다.
이 경우 힙에 할당된 데이터가 손상될 수 있고,
이로 인해서 메모리 공간이 부족해지거나
프로그램이 오류로 종료될 수 있습니다.
힙이 확장되어서 스택의 영역을 침범하게 되는 경우,
스택 프레임이 손상되어 실행 흐름이 바뀔 수도 있습니다.
그렇다면 이러한 문제를 방지하기 위해서는 어떻게 해야할까요?
리눅스에는 이미 자체적으로 이러한 문제를 방지하기 위한
메모리 할당 기법이 존재합니다.
가상 주소 공간을 만들어서 각각의 영역으로 나누고,
확장된 힙과 스택의 영역을 매핑합니다.
그리고 CPU가 메모리에서 데이터를 가져올때는
MMU라는 알고리즘을 통해서 논리주소를 가지고 메모리에서 데이터를 가져오게 됩니다.
이러한 방식을 통해 리눅스에서는 메모리 영역간의 충돌을 방지하고 있습니다.