세그먼트
리눅스에서 프로세스의 메모리를 크게 5가지의 세그먼트(Segment)로 구분합니다.
세그먼트란 적재되는 데이터의 용도별로 메모리의 구획을 나눈 것이다.
코드 세그먼트, 데이터세그먼트, BSS 세그먼트, 힙 세그먼트, 스택 세그먼트로 구분한다.
메모리를 용도별로 나누는 이유는, 각 용도에 맞게 적잘한 권한을 부여할 수 있기 때문이다.
권한에는 읽기, 쓰기, 실행이 존재하며, CPU는 메모리에 대해 권한이 부여된 행위만 할 수 있다.
코드 세그먼트 | Code Segment
실행 가능한 기계 코드가 위치하는 영역으로 텍스트 세그먼트(Text Segment)라고 불린다.
권한 : 읽기, 실행
*쓰기 권한이 있으면 공격자가 악의적인 코드를 삽입하기 쉬워지므로, 대부분의 현대 운영체제는 이 세그먼트에 쓰기 권한을 제거한다.
#예제
int main(){ return 31337; }
정수 31337을 반환하는 main함수가 컴파일되면 554889e5b8697a00005dc3라는 기계 코드로 변환된다.
이 기계 코드가 코드 세그먼트에 위치하게 된다.
데이터 세그먼트 | Data Segment
컴파일 시점에서 값이 정해진 전역 변수 및 전역 상수들이 위치한다.
권한 : 읽기 전용, (읽기, 쓰기)
*CPU가 이 세그먼트의 데이터를 읽을 수 있어야 하므로, 읽기 권한이 부여된다.
쓰기가 가능한 데이터 세그먼트(data segment)
쓰기가 가능한 세그먼트는 전역 변수와 같이 프로그램이 실행되면서 값이 변할 수 있는 데이터들이 위치한다.
쓰기가 불가능한 데이터 세그먼트(rodata) | read-only data
쓰기가 불가능한 세그먼트에는 프로그램이 실행되면서 값이 변하면 안되는 데이터들이 위치한다.
전역으로 선언되니 상수가 여기에 포함된다.
#예제
int data_num = 31337;
// data
char data_rwstr[] = "writable_data";
// data
const char data_rostr[] = "readonly_data"; // rodata
char *str_ptr = "readonly"; // str_ptr은 data, 문자열은 rodata
int main() { ... }
주의깊게 살펴봐야 하는 변수는 str_ptr이다.
"readonly"라는 문자열을 가리키고 있는데, 이 문자열은 상수 문자열로 취급되어 rodata에 위치하며, 이를 가리키는 str_ptr은 전역 변수로서 data에 위치한다.
BSS 세그먼트 (BSS Segment) | Block Started By Symbol Segment
컴파일 시점에 값이 정해지지 않은 전역 변수가 위치하는 메모리 영역이다.
선언만 하고 초기하화하지 않은 전역변수 등이 포함된다.
권한 : 읽기, 쓰기
*이 세그먼트의 메모리 영역은 프로그램이 시작될 때, 모두 0으로 값이 초기화된다. (즉, 초기화 되지 않은 전역 변수의 값은 0이 된다.)
#예제
int bss_data;
int main() {
printf("%d\n", bss_data); // 0
return 0;
}
위 코드에서 초기화되지 않은 전역 변수인 bss_data가 BSS 세그먼트에 위치하게 된다.
스택 세그먼트(Stack Segment)
프로세스의 스택이 위치하는 영역이다.
함수의 인자나 지역 변수와 같은 임시 변수들이 실행중에 여기에 저장된다.
스택 프레임은 함수가 호출될 때 생성되고, 반환할 때 해제된다.
권한 : 읽기, 쓰기
*코드에서 유저가 입력한 값에 따라서 호출되는 함수가 다르다.
**운영체제는 프로세스를 시작할 때 작은 크기의 스택 세그먼트를 먼저 할당해주고, 부족해 질 때마다 이를 확장해줍니다.
***스택에 대해서 '아래로 자란다'라는 표현을 종종 사용하는데, 이는 스택이 확장될 때, 기존 주소보다 낮은 주소로 확장되기 때문이다.
#예제
void func() {
int choice = 0;
scanf("%d", &choice);
if (choice)
call_true();
else
call_false();
return 0;
}
위 코드에서 지역변수 choice가 스텍에 저장되게 된다.
힙 세그먼트 (Heap Segment)
힙 데이터가 위치하는 세그먼트이다.
스택과 마찬가지로 실행중에 동적으로 할당될 수 있으며, 리눅스에서는 스택 세그먼트와 반대 방향으로 자란다.
C언어에서 malloc(), calloc()등을 호출해서 할당받는 메모리가 이 세그먼트에 위치하게 된다.
권한 : 읽기, 쓰기
#예제
int main() {
int *heap_data_ptr =
malloc(sizeof(*heap_data_ptr)); // 동적 할당한 힙 영역의 주소를 가리킴
*heap_data_ptr = 31337; // 힙 영역에 값을 씀
printf("%d\n", *heap_data_ptr); // 힙 영역의 값을 사용함
return 0;
}
위 예제 코드는 heap_data_ptr에 malloc()으로 동적 할당한 영역의 주소를 대입하고, 이 영역에 값을 씁니다. heap_data_ptr은 지역변수이므로 스택에 위치하며, malloc으로 할당받은 힙 세그먼트의 주소를 가리킵니다.
세그먼트 요약
세그먼트 | 역할 | 일반적인 권한 | 사용 예 |
코드 세그먼트 | 실행 가능한 코드가 저장된 영역 |
읽기, 실행 | main() 등의 함수 코드 |
데이터 세그먼트 | 초기화된 전역 변수 또는 상수가 위치하는 영역 | 읽기와 쓰기 또는 읽기 전용 | 초기화된 전역 변수, 전역 상수 |
BSS 세그먼트 | 초기화되지 않은 데이터가 위치하는 영역 | 읽기, 쓰기 | 초기화되지 않은 전역 변수 |
스택세그먼트 | 임시 변수가 저장되는 영역 | 읽기, 쓰기 | 지역 변수, 함수의 인자 등 |
힙 세그먼트 | 실행중에 동적으로 사용되는 영역 | 읽기, 쓰기 | malloc(), calloc()등으로 할당 받은 메모리 |