프로세스의 주소 공간 (Address Space)
한눈에 보기
프로세스마다 부여되는 독립된 가상 메모리 영역인 주소 공간을 다룬다. 코드와 데이터와 BSS와 힙과 스택의 역할, 변수별 배치 예시, 스택과 힙의 동작 차이, ASLR과 가상화의 이점까지 정리한다.
최종 업데이트 2026.09.17
프로세스의 주소 공간(Address Space)은 운영체제가 각 프로세스에게 부여하는 독립된 가상 메모리 영역으로, 코드, 데이터, 힙, 스택 영역으로 구성된다. 모든 프로세스는 자신이 메모리 전체를 혼자 쓰는 것처럼 보이는 가상 주소 위에서 동작하며, 실제 물리 메모리와의 대응은 페이지 테이블이 담당한다. 각 영역이 무엇을 담고 어느 방향으로 자라는지는 포인터, 재귀, 메모리 버그를 이해하는 기초가 된다.
주소 공간의 전체 구조
전형적인 프로세스 주소 공간을 낮은 주소부터 높은 주소로 그리면 다음과 같다.
높은 주소
+------------------+
| 커널 영역 | 사용자 코드가 접근 불가
+------------------+
| 스택 (Stack) | 아래(낮은 주소)로 성장
| | |
| v |
| |
| ^ |
| | |
| 힙 (Heap) | 위(높은 주소)로 성장
+------------------+
| BSS 영역 | 초기화 안 된 전역, 0으로 채워짐
+------------------+
| 데이터 (Data) | 초기화된 전역과 정적 변수
+------------------+
| 코드 (Text) | 기계어 명령, 읽기 전용
+------------------+
낮은 주소
힙과 스택이 서로를 향해 자라도록 배치해 중간의 빈 공간을 양쪽이 나눠 쓰게 한 설계다. 실제 주소 값은 보안을 위한 주소 공간 배치 난수화(ASLR, Address Space Layout Randomization) 때문에 실행할 때마다 달라진다. 공격자가 특정 함수나 버퍼의 주소를 예측해 악용하기 어렵게 만드는 방어 장치다. 리눅스에서는 /proc/프로세스ID/maps 파일을 읽으면 실행 중인 프로세스의 실제 영역 배치와 각 영역의 권한을 눈으로 확인할 수 있다.
영역별 역할
| 영역 | 담는 것 | 크기 결정 시점 | 권한 |
|---|---|---|---|
| 코드(Text) | 컴파일된 기계어 명령 | 컴파일 시 | 읽기와 실행, 쓰기 금지 |
| 데이터(Data) | 초기값이 있는 전역과 정적 변수 | 컴파일 시 | 읽기와 쓰기 |
| BSS | 초기값이 없는 전역과 정적 변수 | 컴파일 시(내용은 0으로 초기화) | 읽기와 쓰기 |
| 힙(Heap) | malloc이나 new로 할당한 동적 메모리 | 실행 중 | 읽기와 쓰기 |
| 스택(Stack) | 지역 변수, 함수 인자, 복귀 주소 | 실행 중(함수 호출마다) | 읽기와 쓰기 |
코드 영역이 읽기 전용인 덕분에 같은 프로그램을 여러 번 실행해도 물리 메모리의 코드 페이지를 안전하게 공유할 수 있다. BSS를 데이터와 분리하는 이유는 실행 파일 크기에 있다. 초기값이 전부 0이라면 값을 파일에 저장할 필요 없이 크기만 기록해 두고 적재 시 0으로 채우면 되기 때문이다.
변수는 어디에 놓이는가
C 코드로 각 영역을 짚어 보면 다음과 같다.
int g_init = 10; // 데이터 영역
int g_uninit; // BSS 영역
const char *msg = "hi"; // 포인터 msg는 데이터 영역,
// 문자열 리터럴 "hi"는 읽기 전용(코드 인접) 영역
void func(int arg) { // arg: 스택
int local = 1; // local: 스택
static int cnt = 0; // cnt: 데이터 영역 (수명은 프로그램 전체)
int *p = malloc(4); // 포인터 p는 스택, 할당된 4바이트는 힙
free(p);
}
스택 변수는 함수가 반환되면 사라지므로 지역 변수의 주소를 반환하면 안 되고, 힙 메모리는 명시적으로 해제해야 하므로 잊으면 메모리 누수(memory leak)가 된다. 두 규칙 모두 영역의 수명 차이에서 나온다.
스택과 힙의 동작 차이
스택은 함수 호출마다 스택 프레임(stack frame)을 쌓고 반환하면 통째로 걷어내는 구조라 할당과 해제가 포인터 이동 하나로 끝나고 매우 빠르다. 대신 크기가 제한적이어서(리눅스 기본 8MB 수준) 깊은 재귀나 큰 지역 배열은 스택 오버플로(stack overflow)를 일으킨다. 힙은 임의 시점에 임의 크기를 할당하고 해제할 수 있는 유연한 공간이지만, 할당자가 빈 블록을 찾고 관리하는 비용이 들고 할당과 해제가 뒤섞이며 단편화가 생길 수 있다. 힙 확장은 내부적으로 brk나 mmap 시스템 콜로 이루어지며, 큰 할당은 별도의 mmap 매핑으로 처리되는 것이 일반적이다. 참고로 스레드는 프로세스의 주소 공간을 공유하지만 스택만은 스레드마다 따로 가지므로, 힙의 데이터는 스레드 간에 공유되고 지역 변수는 공유되지 않는다.
가상 주소 공간이 주는 이점
주소 공간이 가상화되어 있기에 얻는 것이 많다. 프로세스끼리 완전히 격리되어 서로의 메모리를 침범할 수 없고, 모든 프로그램이 같은 논리적 배치(레이아웃)를 가정하고 컴파일될 수 있으며, 물리 메모리보다 큰 공간을 쓸 수 있다. 또한 공유 라이브러리를 여러 프로세스의 주소 공간에 각기 다른 가상 주소로 매핑하면서 물리 페이지는 하나만 두는 공유도 가능하다. fork의 쓰기 시 복사(copy-on-write), 파일을 메모리처럼 다루는 메모리 맵 파일(memory-mapped file)도 모두 이 가상 주소 공간 위에서 구현되는 기능이다.
면접에서 이렇게 물어본다
- 프로세스 주소 공간의 각 영역과 그곳에 저장되는 데이터를 설명해 보라.
- 스택과 힙의 성장 방향, 할당 속도, 수명 관리 방식을 비교해 보라.
- 데이터 영역과 BSS 영역을 나누는 이유는 무엇인가.
- 지역 변수의 주소를 반환하면 왜 위험한지 주소 공간 관점에서 설명해 보라.
- 스레드들이 주소 공간에서 공유하는 것과 공유하지 않는 것을 구분해 보라.
운영체제에서 같이 읽기
- CPU 스케줄링 (CPU Scheduling)운영체제가 준비 상태의 여러 프로세스 중 다음에 CPU를 쓸 프로세스를 고르는 CPU 스케줄링을 다룬다. 선점과 비선점, FCFS부터 라운드 로빈과 MLFQ까지 알고리즘 비교, 리눅스 CFS와 EEVDF까지 정리한다.
- 교착 상태 (Deadlock)둘 이상의 프로세스가 서로가 점유한 자원을 기다리며 영원히 멈추는 교착 상태를 다룬다. 네 가지 필요조건, 예방과 회피와 탐지 회복 전략, 기아와 라이브락의 구분, 데이터베이스 데드락 실무 사례까지 정리한다.
- 파일 시스템 (File System)저장 장치 위에 파일과 디렉터리라는 추상화를 만드는 파일 시스템을 다룬다. 슈퍼블록과 아이노드 구조, 연속과 연결과 색인 할당 비교, 저널링과 쓰기 시 복사, VFS 계층까지 정리한다.
- 인터럽트 (Interrupt)CPU가 하던 일을 멈추고 긴급한 사건을 먼저 처리하게 하는 인터럽트를 다룬다. 폴링과의 비교, 하드웨어 인터럽트와 예외와 트랩의 구분, 처리 절차와 상반부 하반부, 타이머 인터럽트와 멀티태스킹의 관계를 정리한다.
이 개념, 내 코드에서는 어떻게 물어볼까요
데브핏(DevFit)에 채용공고 URL을 붙여 넣고 레포지토리를 연결하면, 내가 쓴 커밋에서 예상 질문과 어필 포인트를 만듭니다. 하루 10회까지 무료입니다. 합불 판정이나 지원자 랭킹은 만들지 않고, 최종 판단은 사람이 합니다.
공고 넣고 시작하기