프로세스를 메모리에 적재하는 방식은 운영체제의 성능과 확장성을 결정하는 핵심 요소이다. 초기 운영체제는 프로세스를 하나의 연속된 물리 메모리 공간에 배치하는 방식을 사용했다. 이 방식은 단순하지만 여러 구조적 한계를 내포한다. 이러한 한계를 해결하는 과정에서 스와핑, 메모리 배치 전략, 가상 메모리, 페이징 기법이 등장했다. 본 글은 그 흐름을 단계적으로 정리한다.
프로세스에 연속적인 물리 메모리 공간을 할당하는 방식을 연속 메모리 할당 방식이라 한다. 하나의 프로세스는 메모리 상에서 끊기지 않은 하나의 영역을 차지한다. 구현이 단순하고 주소 변환이 직관적이라는 장점이 존재한다.
다만 메모리에 적재된 모든 프로세스가 항상 실행 상태인 것은 아니다. 실행되지 않는 프로세스는 메모리 자원을 점유한 채 대기 상태로 남게 된다.
메모리에 적재된 프로세스 중 실행되지 않는 프로세스를 보조기억장치의 일부 영역으로 옮기고, 확보된 메모리 공간에 다른 프로세스를 적재하여 실행하는 방식이 스와핑이다. 이때 사용되는 보조기억장치의 영역을 스왑 영역이라 한다.
스왑 아웃되었던 프로세스는 스왑 인 시 이전과 다른 물리 주소에 적재될 수 있다. 따라서 프로세스는 특정 물리 주소에 종속되지 않는 구조를 전제로 설계된다.
프로세스는 반드시 메모리 내의 빈 공간에 적재되어야 한다. 빈 공간이 여러 개 존재할 경우, 어느 위치에 배치할 것인지 결정하는 정책이 필요하다. 대표적인 메모리 배치 전략은 다음과 같다.
메모리의 시작부터 탐색하여 프로세스를 수용할 수 있는 첫 번째 빈 공간에 할당하는 방식이다. 탐색 비용이 낮고 구현이 단순하다. 다만 메모리 앞부분에 작은 빈 공간들이 다수 생성되는 경향이 있다.
프로세스를 수용할 수 있는 빈 공간 중 가장 작은 공간에 할당하는 방식이다. 이론적으로는 메모리 낭비를 최소화한다. 그러나 모든 빈 공간을 탐색해야 하므로 오버헤드가 크며, 결과적으로 매우 작은 빈 공간들이 다수 남는다.
가장 큰 빈 공간에 프로세스를 할당하는 방식이다. 큰 공간을 쪼개 사용함으로써 이후 할당 가능성을 높이려는 접근이다. 실제로는 활용도가 낮은 전략으로 평가된다.
연속 메모리 할당 방식은 외부 단편화 문제를 내포한다. 외부 단편화란 전체적으로는 충분한 빈 메모리가 존재하지만, 각각의 빈 공간이 너무 작아 새로운 프로세스를 할당할 수 없는 상태를 의미한다. 메모리의 총량이 아니라 배치 형태로 인해 자원이 낭비되는 현상이다.
일부 언어 런타임에서 제공하는 gc.collect()는 메모리 정리라는 점에서 외부 단편화와 유사해 보일 수 있다. 그러나 개념적으로는 다르다.
다만 압축형 가비지 컬렉션(compacting GC)은 살아 있는 객체들을 한쪽으로 이동시켜 힙 공간을 연속적으로 만드는 과정을 포함한다. 이 과정은 개념적으로 메모리 압축과 유사하다. 즉, GC 자체가 외부 단편화를 해결하는 것은 아니지만, 일부 GC 구현은 단편화 완화 효과를 가진다.
외부 단편화를 해결하는 대표적인 방법 중 하나는 메모리 압축이다. 흩어져 있는 빈 공간들을 하나로 모아 큰 연속 공간을 만드는 방식이다.
이 방식은 다음과 같은 비용을 수반한다.
따라서 실제 시스템에서는 제한적으로 사용된다.
연속 메모리 할당 방식은 두 가지 구조적 한계를 가진다.
이 한계를 해결하기 위해 가상 메모리 개념이 도입된다.
가상 메모리는 실행 중인 프로그램의 일부만 메모리에 적재하여, 실제 물리 메모리 크기보다 더 큰 프로세스를 실행할 수 있도록 하는 기술이다. 이를 통해 메모리 사용 효율과 시스템 확장성이 크게 향상된다.
대표적인 가상 메모리 관리 기법은 페이징과 세그먼테이션이다.
페이징은 물리 주소 공간을 프레임 단위로 나누고, 프로세스의 논리 주소 공간을 페이지 단위로 나누어 각 페이지를 프레임에 매핑하는 방식이다. 페이지와 프레임의 크기는 동일하다.
페이징 환경에서도 스와핑이 사용된다. 이때는 프로세스 단위가 아니라 페이지 단위로 이루어진다.
실행에 필요한 페이지만 메모리에 적재함으로써 물리 메모리보다 큰 프로세스 실행이 가능해진다.
페이징 환경에서는 프로세스가 물리 메모리 상에 불연속적으로 배치된다. CPU가 이를 연속적으로 실행할 수 있도록 하기 위해 논리 주소와 물리 주소를 분리한다.
이 변환을 담당하는 구조가 페이지 테이블이다. 페이지 테이블은 페이지 번호와 프레임 번호를 짝지어 주는 일종의 이정표 역할을 한다.
각 프로세스는 자신만의 페이지 테이블을 가지며, 해당 테이블은 메모리에 적재된다. CPU 내부의 페이지 테이블 베이스 레지스터는 현재 실행 중인 프로세스의 페이지 테이블 시작 주소를 가리킨다.
페이징은 외부 단편화 문제를 해결하지만 내부 단편화를 야기할 수 있다. 내부 단편화란 페이지 크기보다 작은 데이터가 페이지에 적재되면서 발생하는 사용되지 않는 공간을 의미한다.
페이지 크기를 줄이면 내부 단편화는 감소하지만, 페이지 테이블의 크기가 커져 관리 비용이 증가한다. 페이지 크기 선택은 메모리 효율과 관리 오버헤드 사이의 절충 문제이다.
각 프로세스의 페이지 테이블 정보는 해당 프로세스의 PCB에 기록된다. PCB는 프로세스의 상태를 관리하기 위한 커널 자료구조이며, 페이지 테이블의 시작 주소 역시 이 내부에 포함된다. 이 구조를 통해 운영체제는 프로세스 전환 시 올바른 페이지 테이블을 참조할 수 있다.
페이지 테이블을 메모리에 두는 구조는 명확한 문제를 가진다. 하나의 메모리 접근을 위해 두 번의 메모리 접근이 필요하다.
이로 인해 메모리 접근 시간이 이론적으로 두 배 증가한다. 이 문제는 주소 변환 과정에서의 구조적 병목이다.
이 문제를 완화하기 위해 CPU 근처에 TLB(Translation Lookaside Buffer)를 둔다. TLB는 최근 사용된 페이지 번호와 프레임 번호의 매핑 정보를 저장하는 캐시 메모리이다.
TLB 히트 시 페이지 테이블을 거치지 않고 즉시 주소 변환이 가능하다. TLB 미스 시 메모리에 있는 페이지 테이블을 참조한 뒤 TLB를 갱신한다. 전체 성능은 TLB 히트율에 크게 의존한다.
하나의 페이지 또는 프레임은 여러 개의 주소를 포함한다. 특정 주소에 접근하기 위해서는 두 가지 정보가 필요하다.
이 때문에 페이징 시스템의 논리 주소는 기본적으로 페이지 번호와 변위로 구성된다. 변위는 페이지 내부에서의 상대 위치를 의미한다.
논리 주소의 변위 값과 물리 주소의 변위 값은 동일하다. 주소 변환 과정에서는 페이지 번호만 변환되며, 변위는 그대로 유지된다. 변위 값은 페이지 크기보다 작은 값이어야 하며, 이는 페이지 내부 주소 범위를 벗어나지 않기 위한 필수 조건이다.
페이지 테이블의 각 행을 페이지 테이블 엔트리라 한다. 엔트리는 단순한 매핑 정보 이상의 의미를 가진다. 일반적으로 다음과 같은 필드를 포함한다.
페이지 번호
논리 주소 공간에서의 페이지 식별자
프레임 번호
물리 메모리에서 해당 페이지가 적재된 프레임 식별자
유효 비트
해당 페이지가 현재 메모리에 적재되어 있는지 여부
유효하지 않은 경우 접근 시 페이지 폴트 발생
보호 비트
읽기, 쓰기, 실행 권한을 제어하기 위한 비트
메모리 보호와 프로세스 격리에 사용
참조 비트
최근에 해당 페이지가 접근되었는지를 나타내는 비트
페이지 교체 알고리즘에서 활용
수정 비트
페이지가 메모리에 적재된 이후 수정되었는지 여부
페이지 교체 시 보조기억장치에 다시 기록해야 하는지 판단 기준
쓰기 시 복사(Copy-on-Write)는 여러 프로세스가 동일한 페이지를 공유하다가, 쓰기 연산이 발생하는 시점에만 실제 복사를 수행하는 기법이다.
초기에는 동일한 물리 페이지를 공유하며 읽기 전용으로 접근한다. 이후 특정 프로세스가 해당 페이지에 쓰기를 시도하면, 그 순간 새로운 페이지를 할당하고 내용을 복사한 뒤 수정한다. 이를 통해 메모리 사용량과 프로세스 생성 비용을 절감한다.
프로세스의 주소 공간이 커질수록 페이지 테이블의 크기도 커진다. 모든 페이지 테이블을 단일 구조로 유지하는 것은 비효율적이다.
계층적 페이징은 페이지 테이블을 여러 단계로 나누는 방식이다. 상위 페이지 테이블은 하위 페이지 테이블의 위치를 가리킨다. 실제로 사용되는 주소 공간에 대해서만 하위 페이지 테이블이 생성된다. 이를 통해 페이지 테이블 자체의 메모리 사용량을 줄인다.
가상 메모리를 통해 큰 프로세스를 실행할 수 있지만, 물리 메모리는 여전히 한정되어 있다. 요구 페이징은 실제로 접근이 발생한 페이지에 대해서만 메모리에 적재하는 방식이다.
순수 요구 페이징에서는 프로세스 시작 시 어떤 페이지도 메모리에 적재되지 않는다. 최초 접근 시 페이지 폴트가 발생하며, 그때 페이지를 적재한다.
페이지 폴트는 접근하려는 페이지가 메모리에 존재하지 않을 때 발생한다. 운영체제는 페이지 폴트 처리 루틴을 통해 페이지를 메모리에 적재하고 실행을 재개한다. 페이지 폴트 횟수는 전체 시스템 성능에 직접적인 영향을 미친다.
물리 메모리가 가득 찬 상태에서 새로운 페이지를 적재해야 할 경우, 기존 페이지 중 하나를 제거해야 한다. 이 과정을 페이지 교체라 한다. 이때 어떤 페이지를 제거할 것인지 결정하는 정책이 페이지 교체 알고리즘이다.
페이지 교체 알고리즘은 페이지 폴트를 가장 적게 발생시키는 방향으로 평가된다. 알고리즘 비교는 동일한 페이지 참조 문자열에 대해 발생하는 페이지 폴트 횟수를 기준으로 이루어진다.
가장 먼저 메모리에 들어온 페이지를 교체하는 방식이다. 구현이 단순하다. 다만 오래되었지만 자주 사용되는 페이지도 제거될 수 있다. 벨라디의 역설이 발생할 수 있다.
FIFO 방식에 참조 비트를 결합한 방식이다. 교체 대상 페이지의 참조 비트가 1이면 비트를 0으로 초기화하고 기회를 한 번 더 준다. 실제로는 최근에 사용된 페이지를 보호하는 효과를 가진다.
앞으로 가장 오랫동안 사용되지 않을 페이지를 교체하는 방식이다. 이론적으로 가장 낮은 페이지 폴트 횟수를 보장한다. 그러나 미래의 페이지 접근을 예측할 수 없기 때문에 실제 구현은 불가능하다. 알고리즘 성능 비교를 위한 기준점으로 사용된다.
가장 오랫동안 사용되지 않은 페이지를 교체하는 방식이다. 과거의 사용 이력을 기반으로 한다. 실제 접근 패턴과 잘 부합하지만, 정확한 구현에는 높은 오버헤드가 필요하다. 실제 시스템에서는 근사 기법을 사용한다.
스레싱은 프로세스가 실행보다 페이지 교체에 더 많은 시간을 소비하는 상태이다. 페이지 폴트가 과도하게 발생하며 CPU는 대부분 대기 상태에 머무른다. 이 현상은 CPU 성능과 무관하게 메모리 크기와 프레임 할당 상태에 의해 발생한다.
물리 메모리가 충분하지 않으면 CPU 성능이 좋아도 시스템 전체 성능은 저하된다.
멀티프로그래밍의 정도는 동시에 메모리에 적재된 프로세스 수를 의미한다. 프로세스나 스레드 수가 과도하게 많아지면 각 프로세스에 할당되는 프레임 수가 감소한다. 이는 페이지 폴트 증가와 스레싱으로 이어질 수 있다. 적정 수준의 균형이 필요하다.
모든 프로세스에 동일한 수의 프레임을 할당하는 방식이다. 구현이 단순하다. 프로세스 특성을 고려하지 않는다.
프로세스의 크기에 비례하여 프레임을 할당하는 방식이다. 상대적으로 합리적인 분배를 제공한다.
작업 집합은 특정 시간 구간 동안 프로세스가 실제로 참조한 페이지들의 집합이다. 운영체제는 작업 집합 크기를 기준으로 적절한 프레임 수를 할당한다. 작업 집합이 메모리에 유지되면 페이지 폴트가 급격히 감소한다.
페이지 폴트 빈도는 단위 시간당 발생하는 페이지 폴트 수를 의미한다. 운영체제는 이 값을 기준으로 프레임을 회수하거나 추가 할당한다. 페이지 폴트 빈도가 일정 범위를 벗어나면 스레싱 위험 신호로 판단한다.
연속 메모리 할당은 단순한 구조를 가지지만, 외부 단편화와 확장성 한계를 가진다. 스와핑과 메모리 배치 전략은 이를 부분적으로 완화하지만 근본적인 해결책은 아니다. 가상 메모리와 페이징 기법은 메모리 관리 문제를 구조적으로 재해석한 결과이며, 현대 운영체제 메모리 관리의 핵심 기반이다.
가상 메모리의 핵심은 제한된 물리 메모리 위에서 실행 흐름을 유지하는 구조적 설계에 있다. 페이지 테이블, TLB, 요구 페이징, 페이지 교체 알고리즘은 모두 이 목표를 달성하기 위한 장치이다. 성능 문제는 단일 요소가 아니라 이 구성 요소들의 상호작용에서 발생한다.