본문 바로가기
독서/운영체제 아주 쉬운 세 가지 이야기

[OSTEP] 메모리 가상화 : 주소 공간의 개념

by 도리언 옐로우 2025. 3. 11.

1. 초기의 시스템에서 멀티프로그래밍까지

초기에는 사용자가 많은 것을 기대하지 않았기 때문에, 초기의 컴퓨터는 많은 개념을 사용자에게 제공하지 않았다. 메모리에는 운영체제가 상주하고 있고, 하나의 프로세스가 나머지 메모리를 사용하였다. 그러나 사용자들은 점점 많은 것들을 기대하기 시작하는데, 바로 사용자 편이, 고성능, 신뢰성 등을 요구하기 시작한 것이다. 이러한 기대에 부응하기 위해 여러 프로세스들을 전환하면서 실행하는 멀티프로그래밍과, 하나의 프로세스를 짧은 시간 실행하고 중단시켜 그 상태를 저장하고 다른 프로세스 상태를 탑재하여 또 짧은 시간 실행시키는 시분할의 시대가 도래한다.

이때 시분할 구현과 관련하여, 중단시킨 프로세스 상태를 메모리에서 디스크로 옮기는 것은 매우 느리기 때문에, 프로세스 전환 시 프로세스를 메모리에 그대로 유지하면서 운영체제가 시분할 시스템을 효율적으로 구현하도록 하는 것이 목표이다. 그렇다면 그림에서처럼 프로그램이 메모리에 동시에 존재해야 하기 때문에, 한 프로세스가 다른 프로세스의 메모리에 간섭하는 현상을 막아야한다는 추가적인 요구가 발생한다.

2. 주소 공간

주소 공간(address space)이란 프로그램이 실행될 때 자신만의 메모리 공간이 있는 것 처럼 보이게 해주는 가상의 모습이다. 사용자의 잘못된 행동을 방지하기 위해 만든 사용하기 쉬운 메모리 개념으로 이해하면 된다. 이 주소 공간에는 실행 프로그램의 모든 메모리 상태가 들어 있게 된다.

주소 공간 위쪽은 코드 영역으로 프로그램 코드가 위치한다. 프로그램 코드는 정적이기 때문에 메모리에 저장하기 쉽고, 또 프로그램이 실행되면서 추가 메모리를 필요로 하지 않는다. 이와 달리 힙과 스택 영역은 확장, 축소가 가능해야 한다. 프로그램이 실행중에 메모리를 동적으로 요청할 때 사용하는 영역이고, 스택 함수를 호출할 때 필요한 정보들(지역변수, 인자, 함수 호출 순서 등)이 저장되는 공간이기 때문이다. 따라서 각각 메모리의 상단과 하단에 배치하고 서로를 향해 확장할 수 있게 한다.

 

참고로, 위 그림에서는 프로그램이 물리 주소 0에서 16KB 사이에 존재하는 것처럼 보여주지만 실제로 프로그램이 여기에 존재하는 것은 아니고 임의의 물리 주소에 탑재된다. 앞으로는 우리가 보는 모든 주소는 가상 주소임을 인지하자. 명령어와 데이터가 탑재되어 있는 물리 메모리 주소를 알 수 있는 것은 오직 운영체제 뿐이다.

3. 가상 메모리 시스템의 목표

가상 메모리 시스템은 다음과 같은 주요 목표를 갖는다.

  • 투명성 : 실행중인 프로그램이 가상 메모리의 존재를 인지하지 못하도록 가상 메모리 시스템을 구현해야 한다.
  • 효율성 : 가상화는 시간과 공간 측면에서 효율적이어야 한다.
  • 보호 : 운영체제는 프로세스를 다른 프로세스로부터 보호해야 하고 운영체제 자신도 프로세스로부터 보호해야 한다.

운영체제가 메모리를 가상화한다는 것은 물리 메모리를 공유하는 다수의 프로세스에게 각자만의 커다란 메모리 공간이 있는 것 처럼 속이는 것이다. 여기서 핵심 질문은 '운영체제가 어떻게 이러한 개념을 제공할 수 있는 것인가'가 된다.

4. 메모리 관리 API

(1) 메모리 공간의 종류 : 스택 vs 힙 메모리

C 프로그램이 실행되면 크게 두 가지 메모리 공간이 할당된다.

  • 스택 (=자동 메모리) : 함수 호출 시 자동으로 할당되고 함수가 끝나면 자동으로 해제된다.
  • 힙 (=명시적 메모리) : malloc()을 사용해 프로그래머가 직접 요청해야 하고 free()로 직접 해제해야 한다.

스택에서의 메모리 할당과 반환과정을 살펴보자. func()라는 함수 내부에 int x;라는 코드는 정수형 변수 x를 사용하겠다는 선언이다. 함수 func()이 호출되면 운영체제는 함수 실행을 위한 스택 프레임을 생성하고, 컴파일러가 선언한 변수들에 맞춰 실제 메모리 공간을 확보하게 된다. 그리고 함수가 끝나면 컴파일러는 메모리를 반환한다.

 

따라서 함수 리턴 이후에도 유지되어야 하는 정보와 같이 오랫동안 유지되어야 할 변수를 위해 힙 메모리라는 다른 유형의 메모리가 필요하게 된다. 모든 할당과 반환이 프로그래머에 의해 명시적으로 처리되는데, 그렇기에 (미숙하면) 많은 버그의 원인이 되기도 한다.

(2) malloc()과 free()함수

int *x = (int *) malloc(sizeof(int));

free(x);

위 코드의 첫 줄은 int 형 하나를 저장할 수 있는 공간을 힙 메모리에 요청하는 것이다. 이때 성공하면 malloc() 함수는 해당 공간의 주소를 void*로 반환하고 실패하면 NULL을 반환한다. 그리고 이 메모리를 다 썼다면 반드시 free()함수를 통해 해제해야 한다. 해제하지 않으면 메모리 누수가 발생하는데, 서버와 같이 장시간 실행되는 프로그램에서는 치명적일 수 있다.