세그멘테이션 방식
1. 개요
1. 개요
세그멘테이션 방식은 컴퓨터 메모리 관리 기법 중 하나로, 프로세스의 주소 공간을 논리적 의미 단위인 세그먼트로 나누어 관리하는 방식을 말한다. 이 기법은 프로그램을 코드, 데이터, 스택, 힙 등 기능과 용도가 다른 블록으로 분할하여, 각 블록이 독립적인 메모리 영역을 갖도록 한다. 세그멘테이션은 페이징 방식과 함께 가상 메모리 시스템을 구성하는 핵심 개념으로 자리 잡았다.
이 방식의 주요 목적은 프로그램의 논리적 구조를 메모리 관리에 반영하여 보안과 공유를 용이하게 하는 것이다. 예를 들어, 코드 세그먼트는 읽기 전용으로 설정하여 보호할 수 있고, 여러 프로세스가 동일한 라이브러리 코드를 하나의 세그먼트로 공유할 수 있다. 초기 멀티프로그래밍 시스템에서 메모리를 효율적으로 활용하고 프로세스 간 격리를 제공하기 위해 개발되었다.
세그멘테이션은 물리 메모리 상에서 각 세그먼트가 연속적으로 할당되어야 한다는 특징을 가진다. 이로 인해 외부 단편화가 발생할 수 있는 단점이 있지만, 프로그램의 자연스러운 구조를 따르기 때문에 프로그래머의 관점에서 이해하고 관리하기가 상대적으로 쉽다는 장점을 제공한다. 현대 운영체제에서는 순수 세그멘테이션보다는 세그멘테이션-페이징 혼합 방식을 더 흔히 사용한다.
2. 세그멘테이션의 기본 개념
2. 세그멘테이션의 기본 개념
세그멘테이션 방식은 운영체제의 메모리 관리 기법 중 하나로, 프로세스의 주소 공간을 논리적 의미 단위인 세그먼트로 나누어 관리하는 방식을 의미한다. 이 방식의 주요 목적은 프로그래머나 컴파일러가 인식하는 프로그램의 논리적 구조를 메모리 관리 시스템에 반영하여, 보다 효율적이고 안전한 메모리 할당 및 접근을 가능하게 하는 것이다.
세그멘테이션의 핵심은 하나의 프로세스 주소 공간을 코드, 데이터, 스택, 힙 등과 같은 논리적으로 독립된 블록으로 분할하는 데 있다. 각 세그먼트는 변수 집합, 함수, 객체 또는 특정 모듈과 같은 의미 있는 단위로 구성된다. 이는 물리적 메모리를 고정된 크기의 블록으로 나누는 페이징 방식과 근본적으로 다르다. 각 세그먼트는 연속적인 논리 주소 공간을 가지며, 크기가 서로 다를 수 있다.
주소 공간 관리 측면에서, 프로세스는 여러 개의 세그먼트로 구성되며 각 세그먼트는 독립적으로 메모리에 적재된다. 논리 주소는 일반적으로 세그먼트 번호와 그 세그먼트 내의 오프셋(변위)으로 구성된 2차원 주소 체계를 사용한다. 운영체제는 각 프로세스마다 세그먼트 테이블을 유지하며, 이 테이블은 각 세그먼트의 물리적 메모리 상의 시작 주소(베이스 레지스터 값)와 길이(한계 레지스터 값)를 저장한다. 이를 통해 메모리 관리 장치는 논리 주소를 물리 주소로 변환한다.
2.1. 정의와 목적
2.1. 정의와 목적
세그멘테이션 방식은 컴퓨터 메모리 관리 기법 중 하나로, 프로세스의 주소 공간을 논리적 의미 단위인 세그먼트로 나누어 관리하는 방식을 말한다. 각 세그먼트는 코드, 데이터, 스택, 힙 등과 같이 기능이나 용도가 동일한 정보의 집합으로 구성된다. 이 방식의 주요 목적은 프로그래머의 관점에서 자연스러운 메모리 관리를 제공하고, 효율적인 메모리 보호와 세그먼트 공유를 가능하게 하는 데 있다.
세그멘테이션의 근본적인 목표는 물리 메모리를 단순한 선형 배열로 보는 것이 아니라, 서로 다른 속성을 가진 논리 블록의 집합으로 취급하는 것이다. 이를 통해 운영체제는 각 세그먼트에 독립적인 접근 권한(읽기, 쓰기, 실행)을 부여할 수 있어, 한 세그먼트의 오류가 다른 세그먼트로 확산되는 것을 방지한다. 또한, 여러 프로세스가 공통으로 사용하는 라이브러리 코드 등을 하나의 세그먼트로 만들어 물리 메모리에 한 번만 적재하고 공유할 수 있어 메모리 사용 효율을 높인다.
이 방식은 페이징 방식과 대비되는 개념이다. 페이징이 고정된 크기의 물리적 프레임으로 메모리를 나누는 데 중점을 둔다면, 세그멘테이션은 가변 크기의 논리적 단위로 나누는 데 중점을 둔다. 따라서 세그멘테이션은 프로그램의 구조를 그대로 반영하여 관리할 수 있다는 장점을 가진다.
2.2. 주소 공간 관리
2.2. 주소 공간 관리
세그멘테이션 방식에서 주소 공간 관리는 프로세스의 논리적 주소 공간을 독립적인 세그먼트들로 나누어 관리하는 것을 의미한다. 각 세그먼트는 코드, 데이터, 스택, 힙 등과 같은 논리적 단위에 대응되며, 운영체제는 세그먼트 테이블을 통해 각 세그먼트의 물리 메모리 내 시작 위치(베이스)와 크기(한계) 정보를 관리한다.
주소 변환은 논리 주소가 <세그먼트 번호, 오프셋>의 형태로 구성된다는 점이 특징이다. 메모리 관리 장치(MMU)는 세그먼트 번호를 인덱스로 사용해 세그먼트 테이블에서 해당 세그먼트의 베이스 주소와 한계 길이를 조회한다. 이후 오프셋 값이 세그먼트의 한계 길이 내에 있는지 검사한 후, 베이스 주소에 오프셋을 더해 최종 물리 주소를 계산한다. 이 과정에서 오프셋이 한계 길이를 초과하면 세그멘테이션 폴트(segmentation fault)가 발생한다.
세그먼트별 독립적인 주소 공간 관리는 여러 이점을 제공한다. 각 세그먼트는 서로 다른 보호 속성(읽기, 쓰기, 실행)을 가질 수 있어, 코드 세그먼트는 읽기/실행만 허용하고 데이터 세그먼트는 읽기/쓰기를 허용하는 방식으로 메모리 보호를 강화할 수 있다. 또한, 공유 라이브러리와 같은 공통 코드는 동일한 세그먼트 번호를 가진 별도의 세그먼트로 구현되어 여러 프로세스 간에 물리 메모리에 한 번만 적재되고 공유될 수 있다[1].
그러나 이 방식은 물리 메모리에 세그먼트들이 연속적으로 할당되어야 하기 때문에 외부 단편화 문제가 발생할 수 있다. 크기가 각기 다른 세그먼트들이 메모리에 배치되고 반납되는 과정에서 메모리 공간 사이사이에 사용할 수 없는 작은 빈 공간들이 생겨나 메모리 이용 효율이 저하될 수 있다. 이 문제를 완화하기 위해 메모리 압축(compaction) 기법이 사용되기도 하지만, 이는 추가적인 시스템 오버헤드를 유발한다.
3. 세그멘테이션의 주요 특징
3. 세그멘테이션의 주요 특징
세그멘테이션은 프로그램을 논리적 의미를 가진 단위인 세그먼트로 나누어 관리하는 방식이다. 각 세그먼트는 코드, 데이터, 스택, 힙 등과 같이 기능이나 용도가 동일한 정보들의 집합으로 구성된다. 이는 단순히 물리적 주소 공간을 균일하게 자르는 페이징 방식과는 근본적으로 다른 접근법이다. 프로그램의 논리적 구조를 메모리 관리 체계에 직접 반영함으로써, 프로그래머의 관점에서 보다 직관적인 메모리 뷰를 제공하는 것이 핵심 특징이다.
이 방식은 강력한 보호와 공유 메커니즘을 구현하는 데 유리하다. 각 세그먼트는 독립적인 접근 권한 (읽기, 쓰기, 실행 등)을 부여받을 수 있다. 예를 들어, 코드 세그먼트는 실행만 가능하도록, 데이터 세그먼트는 읽기와 쓰기만 가능하도록 설정할 수 있다. 이는 한 프로세스가 잘못된 메모리 영역을 침범하는 것을 하드웨어 수준에서 방지하여 시스템의 안정성을 높인다. 또한, 여러 프로세스가 공통으로 사용하는 라이브러리 코드나 데이터를 하나의 세그먼트로 정의하여 물리 메모리에 단일 복사본만 유지한 뒤, 여러 프로세스의 세그먼트 테이블이 이를 가리키게 함으로써 메모리를 효율적으로 공유할 수 있다.
다음 표는 세그멘테이션의 주요 논리적 단위와 그 일반적인 특성을 정리한 것이다.
세그먼트 유형 | 일반적 내용 | 일반적 접근 권한 |
|---|---|---|
코드 (Text) | 실행 가능한 기계어 명령어 | 읽기, 실행 |
데이터 (Data) | 초기화된 전역/정적 변수 | 읽기, 쓰기 |
BSS | 초기화되지 않은 전역/정적 변수 | 읽기, 쓰기 |
힙 (Heap) | 동적 할당 메모리 영역 | 읽기, 쓰기 |
스택 (Stack) | 지역 변수, 함수 호출 정보 | 읽기, 쓰기 |
이러한 논리적 분할은 메모리 보호의 단위를 세밀하게 조정할 수 있게 하지만, 세그먼트의 크기가 가변적이라는 점에서 관리의 복잡성과 외부 단편화라는 문제를 동시에 야기한다.
3.1. 논리적 단위 분할
3.1. 논리적 단위 분할
세그멘테이션은 프로세스의 주소 공간을 논리적 의미 단위로 나누는 방식이다. 이는 페이징 방식이 물리적 메모리를 고정된 크기의 페이지로 나누는 것과 대조된다. 각 논리적 단위를 세그먼트라고 부르며, 일반적으로 코드, 데이터, 스택, 힙과 같은 프로그램의 기능적 구성 요소에 해당한다.
각 세그먼트는 독립적인 논리적 객체로 취급된다. 예를 들어, 코드 세그먼트는 실행 가능한 명령어들을 포함하고, 데이터 세그먼트는 전역 변수와 정적 변수를 담는다. 스택 세그먼트는 함수 호출과 지역 변수 정보를 관리하는 데 사용된다. 이렇게 분할되면 운영체제는 각 세그먼트에 대해 별도의 접근 권한(읽기, 쓰기, 실행)을 부여하여 보호를 강화할 수 있다.
세그먼트의 크기는 고정되지 않고, 그 안에 포함된 논리적 내용에 따라 가변적이다. 하나의 세그먼트는 연속적인 논리 주소 공간을 차지하지만, 물리 메모리에서는 다른 세그먼트와 연속적으로 위치하지 않아도 된다. 이는 프로그램의 구조를 메모리 관리 시스템이 인식하고 반영한다는 점에서 페이징과 구분되는 핵심 특징이다.
세그먼트 유형 | 일반적 내용 | 주요 특징 |
|---|---|---|
코드 세그먼트 | 실행 명령어(기계어) | 읽기 및 실행 권한, 공유 가능 |
데이터 세그먼트 | 전역/정적 변수 | 읽기 및 쓰기 권한 |
스택 세그먼트 | 지역 변수, 복귀 주소 | 읽기/쓰기 권한, 동적 확장 |
힙 세그먼트 | 동적 할당 메모리 | 읽기/쓰기 권한, 동적 확장 |
이러한 논리적 분할은 프로그래머의 관점과 일치하여 이해하기 쉽다. 또한, 관련 코드나 데이터를 하나의 단위로 묶어 효율적인 공유와 재배치를 가능하게 한다. 예를 들어, 여러 프로세스가 동일한 라이브러리 코드를 사용할 경우, 하나의 코드 세그먼트를 공유하여 메모리 사용량을 줄일 수 있다.
3.2. 보호와 공유
3.2. 보호와 공유
세그멘테이션은 메모리 보호와 메모리 공유를 구현하는 데 효과적인 메커니즘을 제공한다. 각 세그먼트는 독립적인 논리적 단위이므로, 운영체제는 세그먼트 테이블의 각 항목에 접근 권한 비트(예: 읽기, 쓰기, 실행)를 부여할 수 있다. 이를 통해 한 프로세스의 코드 세그먼트는 실행만 가능하게 하고, 데이터 세그먼트는 읽기와 쓰기만 허용하는 식으로 제어가 가능해진다. 한 프로세스가 다른 프로세스의 세그먼트에 무단으로 접근하려고 하면 하드웨어에 의해 메모리 보호 위반 오류가 발생하여 시스템의 안정성을 유지한다.
또한, 세그멘테이션은 코드와 데이터의 공유를 용이하게 한다. 여러 프로세스가 동일한 프로그램(예: 텍스트 에디터)을 실행할 경우, 각 프로세스의 코드 세그먼트가 물리 메모리의 동일한 영역을 가리키도록 설정할 수 있다. 이는 메모리 사용 효율을 높인다. 공유 가능한 세그먼트와 프로세스 전용 세그먼트를 명확히 구분하여 관리할 수 있다는 점도 장점이다.
보호와 공유의 수준은 일반적으로 세그먼트 단위로 적용된다. 다음 표는 세그먼트별로 다른 보호 및 공유 정책이 적용될 수 있음을 보여준다.
세그먼트 유형 | 일반적 접근 권한 | 공유 가능성 |
|---|---|---|
코드 (텍스트) | 읽기, 실행 | 높음 (다중 프로세스에 의해 공유 가능) |
데이터 | 읽기, 쓰기 | 낮음 (주로 프로세스 전용) |
스택 | 읽기, 쓰기 | 매우 낮음 (프로세스 전용) |
이러한 체계는 모듈화된 프로그램 구조와 잘 맞아, 각 모듈(세그먼트)에 대해 별도의 보호 및 공유 정책을 세울 수 있게 한다. 그러나 세그먼트 단위의 보호는 비교적 거친 단위의 제어에 해당하며, 더 세밀한 페이지 단위 보호를 제공하는 페이징 방식과 비교되는 특징이다.
4. 세그멘테이션 구현 방식
4. 세그멘테이션 구현 방식
세그멘테이션 방식을 구현하는 핵심 자료 구조는 세그먼트 테이블이다. 각 프로세스는 자신의 세그먼트 테이블을 가지며, 이 테이블은 메모리 관리 장치(MMU)에 의해 참조된다. 세그먼트 테이블의 각 항목은 하나의 세그먼트에 대한 정보를 담고 있으며, 일반적으로 세그먼트의 기준 주소(Base Address), 세그먼트의 길이(Limit), 그리고 접근 권한(읽기, 쓰기, 실행) 등의 속성을 포함한다[2].
주소 변환 과정은 다음과 같다. 프로세스가 생성하는 논리 주소는 일반적으로 <세그먼트 번호, 오프셋>의 쌍으로 구성된다. 먼저 세그먼트 번호를 인덱스로 사용하여 세그먼트 테이블에서 해당 항목을 찾는다. 그 후, 오프셋 값이 세그먼트 테이블 항목에 기록된 세그먼트 길이보다 작은지 검사하여 메모리 보호를 수행한다. 검사를 통과하면, 세그먼트 테이블 항목의 기준 주소에 오프셋을 더하여 실제 물리 메모리 주소를 계산한다. 이 변환 과정은 하드웨어에 의해 투명하게 처리된다.
구성 요소 | 설명 |
|---|---|
세그먼트 번호 (s) | 논리 주소의 상위 비트. 사용할 세그먼트를 지정한다. |
오프셋 (d) | 논리 주소의 하위 비트. 세그먼트 내의 상대 위치를 지정한다. |
기준 주소 (base) | 세그먼트 테이블 항목의 값. 세그먼트가 물리 메모리에 적재된 시작 주소다. |
세그먼트 길이 (limit) | 세그먼트 테이블 항목의 값. 오프셋이 이 값을 초과하면 트랩(예: 세그멘테이션 폴트)이 발생한다. |
물리 주소 | 최종 계산된 주소로, |
이 구현 방식은 세그먼트를 논리적 단위로 관리하기 때문에 공유 세그먼트와 재배치가 상대적으로 용이하다는 장점이 있다. 예를 들어, 여러 프로세스가 공유하는 코드 세그먼트는 각 프로세스의 세그먼트 테이블에서 동일한 물리 기준 주소를 가리키도록 설정할 수 있다. 그러나 세그먼트 테이블을 메모리에 유지하고 매번 주소 변환 시 접근해야 하므로, 변환 색인 버퍼(TLB)와 같은 고속 캐시 메커니즘이 성능 향상에 중요하게 작용한다.
4.1. 세그먼트 테이블
4.1. 세그먼트 테이블
세그먼트 테이블은 세그멘테이션 방식에서 각 세그먼트의 물리적 메모리 상 위치와 속성 정보를 관리하는 핵심 자료 구조이다. 운영체제는 각 프로세스마다 고유한 세그먼트 테이블을 유지하며, 이 테이블은 일반적으로 메모리 관리 장치(MMU)에 의해 참조된다. 각 테이블 항목은 하나의 세그먼트에 대한 정보를 담고 있으며, 이를 세그먼트 디스크립터(Segment Descriptor)라고 부른다.
세그먼트 테이블의 주요 구성 요소는 다음과 같다.
항목 | 설명 |
|---|---|
기준 주소 (Base Address) | 해당 세그먼트가 물리적 메모리에서 시작하는 주소이다. |
한계 길이 (Limit/Length) | 세그먼트의 크기(길이)를 바이트 또는 페이지 단위로 지정한다. |
접근 권한 (Access Rights) | 읽기, 쓰기, 실행 가능 여부 등의 보호 정보를 담는다. |
존재 비트 (Present Bit) | 해당 세그먼트가 현재 물리 메모리에 적재되어 있는지 여부를 나타낸다. |
주소 변환 시, 논리 주소의 세그먼트 번호는 세그먼트 테이블 내의 인덱스로 사용된다. 해당 항목에서 기준 주소를 읽어와 변위(오프셋)와 더함으로써 최종 물리 주소가 계산된다. 이 과정에서 변위 값이 세그먼트의 한계 길이를 초과하는지 검사하여 메모리 보호를 수행한다.
세그먼트 테이블 자체의 위치는 세그먼트 테이블 기준 레지스터(STR)에 저장된다. 현대 시스템에서는 성능 향상을 위해 자주 접근하는 세그먼트 디스크립터를 변환 색인 버퍼(TLB)에 캐싱한다.
4.2. 주소 변환 과정
4.2. 주소 변환 과정
세그먼트 테이블을 이용한 주소 변환 과정은 논리 주소를 물리 주소로 매핑하는 핵심 절차이다. 사용자 프로그램이 생성하는 논리 주소는 일반적으로 두 부분으로 구성된다. 첫 번째 부분은 세그먼트 번호(s)이며, 이는 어떤 세그먼트를 참조하는지를 지정한다. 두 번째 부분은 그 세그먼트 내의 오프셋(d)으로, 세그먼트 시작 지점으로부터의 상대 거리를 나타낸다.
변환 과정은 다음과 같은 단계로 진행된다.
1. CPU가 논리 주소 (s, d)를 생성하면, 운영체제는 세그먼트 번호(s)를 인덱스로 사용하여 세그먼트 테이블을 조회한다.
2. 테이블에서 해당 세그먼트의 베이스 주소(시작 물리 주소)와 세그먼트 한계(길이) 정보를 가져온다.
3. 오프셋(d)이 세그먼트 한계 값보다 작은지 검사한다. 만약 오프셋이 한계를 초과하면 세그멘테이션 폴트(segmentation fault)가 발생하여 접근이 차단된다. 이는 메모리 보호를 담당하는 중요한 단계이다.
4. 검사를 통과하면, 물리 주소는 베이스 주소 + 오프셋(d) 의 계산으로 얻어진다.
이 과정을 표로 정리하면 다음과 같다.
단계 | 수행 작업 | 설명 |
|---|---|---|
1 | 세그먼트 테이블 조회 | 논리 주소의 세그먼트 번호(s)로 테이블 항목 접근 |
2 | 정보 로드 | 해당 세그먼트의 베이스 주소와 한계(limit) 값을 읽음 |
3 | 오프셋 검증 | 오프셋(d)이 한계 값 미만인지 확인. 초과 시 폴트 발생 |
4 | 물리 주소 계산 |
|
주소 변환은 메모리 관리 장치(MMU)의 하드웨어 지원을 통해 효율적으로 수행된다. 그러나 매번 메모리 접근 시 테이블 조회와 덧셈 연산이 필요하므로, 자주 접근하는 세그먼트의 베이스 주소를 캐시하는 변환 색인 버퍼(TLB) 같은 기법을 함께 사용하여 성능 오버헤드를 줄인다.
5. 페이징과의 비교
5. 페이징과의 비교
페이징과 세그멘테이션은 모두 주기억장치를 관리하는 메모리 관리 기법이지만, 접근 방식과 특징에서 근본적인 차이를 보인다.
가장 큰 구조적 차이는 관리 단위의 성격에 있다. 페이징은 물리적 메모리를 고정된 크기의 블록(페이지)으로 나누고, 프로세스의 주소 공간도 동일한 크기의 페이지로 분할하여 매핑한다. 이는 사용자에게는 투명하게 작동하며, 메모리가 일련의 연속적인 가상 주소 공간처럼 보이게 만든다. 반면 세그멘테이션은 프로세스의 논리적 구조를 반영하여, 코드, 데이터, 스택, 힙 등 의미 있는 단위인 세그먼트로 나눈다. 각 세그먼트는 크기가 가변적이며 독립적인 논리 주소 공간을 형성한다.
두 방식의 장단점은 다음과 같이 비교된다.
비교 항목 | 페이징 | 세그멘테이션 |
|---|---|---|
관리 단위 | 고정 크기의 페이지 | 가변 크기의 세그먼트 |
분할 기준 | 물리적(크기 기준) | 논리적(의미 기준) |
주소 공간 | 일차원적 선형 공간 | 다차원적(세그먼트 번호 + 오프셋) |
단편화 | 내부 단편화 발생 | 외부 단편화 발생 |
공유/보호 | 페이지 단위 (덜 정교함) | 세그먼트 단위 (논리적 단위로 용이함) |
메모리 관리 | 간단 (크기가 균일함) | 복잡 (가변 크기 할당/회수 필요) |
페이징의 주요 장점은 메모리 할당과 관리가 단순하며, 외부 단편화가 발생하지 않는다는 점이다. 단, 페이지 내 남는 공간으로 인한 내부 단편화는 피하기 어렵다. 세그멘테이션은 논리적 구조에 맞춰 관리되므로 메모리 보호와 공유가 자연스럽고 효율적이다. 예를 들어, 코드 세그먼트는 읽기 전용으로, 데이터 세그먼트는 읽기-쓰기로 설정하기 쉽다. 그러나 가변 크기 세그먼트들의 할당과 해제가 반복되면 메모리 공간 사이에 사용할 수 없는 작은 홀들이 생겨 외부 단편화 문제가 발생하며, 이는 압축 작업을 통해 해결해야 하는 오버헤드를 유발한다.
5.1. 구조적 차이
5.1. 구조적 차이
세그멘테이션과 페이징은 모두 주기억장치 관리 기법이지만, 메모리를 분할하는 구조적 접근 방식에서 근본적인 차이를 보인다.
세그멘테이션은 프로그램의 논리적 구조에 따라 메모리를 가변 크기의 블록인 세그먼트로 나눈다. 각 세그먼트는 코드, 데이터, 스택, 힙 등 프로그램의 기능적 단위에 대응된다. 따라서 세그먼트의 크기는 그 내용에 따라 달라지며, 실행 중에 동적으로 변경될 수도 있다. 반면, 페이징은 물리적 메모리를 고정 크기의 블록인 페이지로, 프로그램의 주소 공간을 동일한 크기의 블록인 페이지 프레임으로 나눈다. 이 분할은 프로그램의 논리적 구조와 무관하며, 순전히 관리의 편의를 위한 것이다.
이 구조적 차이는 주소 변환과 메모리 할당 방식에 직접적인 영향을 미친다. 세그멘테이션에서 논리 주소는 세그먼트 번호와 세그먼트 내 오프셋으로 구성된다. 세그먼트 테이블은 각 세그먼트의 물리적 시작 주소(베이스)와 길이(리미트)를 저장한다. 페이징에서 논리 주소는 페이지 번호와 페이지 내 오프셋으로 구성되며, 페이지 테이블은 각 논리 페이지가 매핑된 물리적 페이지 프레임 번호를 저장한다.
다음 표는 두 방식의 핵심 구조적 특성을 비교한다.
특성 | 세그멘테이션 | 페이징 |
|---|---|---|
분할 단위 | 가변 크기의 논리적 세그먼트 | 고정 크기의 물리적 페이지/논리적 페이지 프레임 |
분할 기준 | 프로그램의 기능적 구조(코드, 데이터 등) | 관리적 편의성 |
주소 구성 | (세그먼트 번호, 오프셋) | (페이지 번호, 오프셋) |
매핑 정보 | 베이스(시작 주소)와 리미트(길이) | 페이지 프레임 번호 |
단편화 유형 | 주로 외부 단편화 |
결과적으로, 세그멘테이션은 프로그래머나 컴파일러가 인지하는 논리적 뷰와 일치하지만 메모리 공간 관리가 복잡해지는 반면, 페이징은 물리적 메모리를 균일하게 관리하기 쉬우나 주소 공간이 고정 크기 조각으로 강제로 나뉜다는 특징이 있다.
5.2. 장단점 비교
5.2. 장단점 비교
세그멘테이션은 논리적 단위로 메모리를 관리하기 때문에 프로그램의 관점에 부합한다는 장점이 있다. 각 세그먼트는 코드, 데이터, 스택 등 기능별로 독립되어 있어, 보호와 공유가 용이하다. 예를 들어, 코드 세그먼트는 읽기 전용으로, 데이터 세그먼트는 읽기-쓰기로 설정하여 메모리 보호를 강화할 수 있다. 또한, 여러 프로세스가 동일한 코드 세그먼트(예: 공유 라이브러리)를 공유함으로써 메모리 사용 효율을 높일 수 있다.
반면, 세그멘테이션의 가장 큰 단점은 외부 단편화 문제다. 세그먼트들의 크기가 가변적이기 때문에 메모리에 흩어져 있는 작은 빈 공간들이 누적되어, 총 여유 공간은 충분함에도 새로운 세그먼트를 할당할 수 없는 상황이 발생한다. 이 문제를 해결하기 위해 메모리 압축 작업이 필요하지만, 이는 상당한 시스템 오버헤드를 유발한다.
페이징과 비교했을 때의 장단점은 다음과 같이 정리할 수 있다.
비교 항목 | 세그멘테이션 | 페이징 |
|---|---|---|
관리 단위 | 가변 크기의 논리적 세그먼트 | 고정 크기의 물리적 프레임/논리적 페이지 |
단편화 | 외부 단편화 발생 | 내부 단편화 발생 |
보안/공유 | 논리적 단위 기반으로 용이 | 페이지 단위로는 복잡 |
주소 변환 | 세그먼트 테이블 기반 (베이스/한계 레지스터) | 페이지 테이블 기반 |
메모리 효율 | 외부 단편화로 인해 낮을 수 있음 | 내부 단편화 있으나 관리 효율적 |
요약하면, 세그멘테이션은 프로그램의 구조를 반영한 유연한 메모리 관리와 강력한 보호 기능을 제공하지만, 외부 단편화로 인한 메모리 활용도 저하와 관리 오버헤드가 주요 약점이다. 이 때문에 현대 운영체제에서는 순수 세그멘테이션보다는 페이징과 결합한 세그멘테이션-페이징 혼합 방식이 더 널리 사용된다.
6. 세그멘테이션의 한계와 문제점
6. 세그멘테이션의 한계와 문제점
세그멘테이션 방식의 가장 큰 문제점은 외부 단편화가 발생한다는 점이다. 각 세그먼트는 크기가 가변적이며, 프로세스의 종료와 함께 메모리에서 해제된다. 이로 인해 메모리 공간에 크기가 다양한 빈 공간(홀)이 산발적으로 생긴다. 새로운 세그먼트를 적재할 때, 이 빈 공간들의 크기가 요구 크기보다 작으면, 충분한 총 여유 공간이 있음에도 불구하고 할당에 실패하는 현상이 발생한다. 이 문제를 해결하기 위해 메모리 압축 작업이 필요하지만, 이는 상당한 성능 오버헤드를 유발한다.
또 다른 주요 한계는 성능 오버헤드이다. 주소 변환을 위해 세그먼트 테이블을 매번 참조해야 하며, 이는 메모리 접근 시간을 증가시킨다. 세그먼트 테이블 자체도 메모리에 상주하기 때문에, 하나의 논리 주소를 물리 주소로 변환하려면 최소 두 번의 메모리 접근이 필요하다[3]. 이는 변환 색인 버퍼(TLB)와 같은 하드웨어 지원이 없다면 시스템 성능에 큰 부담이 된다.
세그멘테이션은 메모리 공간의 효율적인 활용에도 어려움을 겪는다. 세그먼트는 반드시 연속된 물리 메모리에 위치해야 하므로, 큰 세그먼트를 할당하기가 어려워질 수 있다. 또한, 세그먼트 크기의 제한이나 세그먼트 테이블의 크기 제한으로 인해 프로세스가 사용할 수 있는 논리적 공간에 제약이 생길 수 있다. 이러한 문제들로 인해 현대 운영체제에서는 순수한 세그멘테이션만을 사용하기보다는 페이징 기법과 결합한 세그멘테이션-페이징 혼합 방식을 주로 채용한다.
6.1. 외부 단편화
6.1. 외부 단편화
세그멘테이션은 가변 크기의 논리적 단위인 세그먼트로 메모리를 관리하기 때문에, 물리 메모리에 세그먼트들이 불연속적으로 배치되고 해제될 때마다 사용되지 않는 작은 공간들이 산발적으로 발생하게 된다. 이 현상을 외부 단편화라고 한다. 외부 단편화는 전체 메모리의 여유 공간 총량은 요청을 수용하기에 충분하지만, 그 공간들이 연속적으로 존재하지 않아 메모리 할당에 실패하는 상황을 초래한다.
외부 단편화를 해결하기 위한 일반적인 방법은 메모리 압축이다. 이는 분산된 여러 개의 작은 여유 공간들을 하나의 큰 연속된 공간으로 모으는 작업이다. 그러나 메모리 압축은 모든 프로세스의 세그먼트를 재배치해야 하므로 상당한 오버헤드를 유발하며, 재배치 도중 시스템이 다른 작업을 수행하기 어려운 문제가 있다.
다음은 외부 단편화 발생과 메모리 압축 과정을 보여주는 간단한 도식이다.
단계 | 메모리 상태 (사용 중: [S], 여유: [ ]) | 설명 |
|---|---|---|
초기 | [S1][S2][S3][ ] | 세 개의 세그먼트가 연속적으로 배치됨. |
해제 후 | [S1][ ][S3][ ] | S2 세그먼트가 해제되어 중간에 여유 공간 생성. |
할당 실패 | [S1][ ][S3][ ] | 새 큰 세그먼트 S4를 할당하려 하지만, 단편화로 인해 실패[4]. |
압축 후 | [S1][S3][ ][ ] | S3를 이동시켜 여유 공간을 통합. |
할당 성공 | [S1][S3][S4][ ] | 통합된 여유 공간에 S4 할당 가능. |
이러한 단편화 문제는 세그멘테이션 방식의 근본적인 한계로, 메모리 사용 효율을 저하시키고 시스템 성능을 불안정하게 만드는 주요 원인이 된다. 이를 완화하기 위해 현대 운영체제는 대부분 세그멘테이션과 페이징을 결합한 세그멘테이션-페이징 혼합 방식을 채택한다.
6.2. 성능 오버헤드
6.2. 성능 오버헤드
세그멘테이션 방식은 주소 변환 과정에서 추가적인 메모리 접근이 필요하기 때문에 성능 오버헤드가 발생할 수 있다. 모든 메모리 참조는 세그먼트 테이블을 먼저 조회하여 논리 주소를 물리 주소로 변환해야 한다. 이 테이블은 일반적으로 주 메모리에 상주하므로, 각 메모리 접근마다 테이블 접근을 위한 추가적인 메모리 읽기 작업이 필요해진다. 이로 인해 순수한 계산 시간에 비해 실제 메모리 접근 시간이 길어져 전체 시스템 성능이 저하될 수 있다.
이러한 오버헤드를 완화하기 위해 변환 색인 버퍼(TLB)와 같은 고속 캐시 하드웨어가 자주 활용된다. TLB는 최근에 사용된 세그먼트 테이블 항목들을 캐싱하여, 매번 메인 메모리의 세그먼트 테이블에 접근할 필요를 줄여준다. 그러나 TLB 미스가 발생하면 여전히 전체 테이블 조회 과정을 수행해야 하며, 이는 성능 저하로 이어진다.
세그먼트 테이블의 관리와 유지보수 또한 오버헤드 요인이다. 운영체제는 새로운 프로세스 생성, 세그먼트 크기 변경, 세그먼트 삭제 시 테이블을 업데이트해야 한다. 특히 다중 프로그래밍 환경에서 많은 수의 프로세스가 동시에 실행될 경우, 이 관리 작업은 상당한 시스템 자원을 소모한다.
오버헤드 유형 | 설명 | 영향 |
|---|---|---|
주소 변환 오버헤드 | 모든 메모리 접근 시 세그먼트 테이블 조회 필요 | 메모리 접근 시간 증가 |
TLB 관리 오버헤드 | TLB 미스 처리 및 캐시 일관성 유지 | 캐시 효율성 저하 |
테이블 관리 오버헤드 | 세그먼트 생성/삭제/변경 시 테이블 업데이트 | 시스템 콜 처리 시간 증가 |
또한, 세그먼트의 크기가 가변적이기 때문에 메모리 보호와 접근 권한 검사가 더 복잡해질 수 있다. 각 접근마다 주소가 세그먼트 범위 내에 있는지, 그리고 요청된 연산(읽기, 쓰기, 실행)에 대한 권한이 있는지를 검사해야 한다. 이 추가적인 검사는 하드웨어나 마이크로코드 수준에서 처리되지만, 여전히 처리 사이클을 소모하는 요소로 작용한다.
7. 현대 시스템에서의 활용
7. 현대 시스템에서의 활용
세그멘테이션 방식은 순수한 형태로 현대 범용 운영 체제에 단독으로 사용되는 경우는 거의 없다. 그 주요 문제점인 외부 단편화와 복잡한 메모리 관리로 인해 성능 오버헤드가 크기 때문이다. 대신, 페이징 기법의 장점과 결합한 혼합 방식이 발전하여 활용된다.
가장 대표적인 혼합 방식은 세그멘테이션-페이징이다. 이 방식에서는 먼저 프로세스의 주소 공간을 코드, 데이터, 스택 등의 논리적 세그먼트로 나눈다. 그러나 각 세그먼트가 연속된 물리 메모리에 할당되는 대신, 다시 고정 크기의 페이지들로 나뉘어 페이지 테이블을 통해 관리된다. 따라서 사용자는 여전히 논리적 세그먼트 단위로 프로그램을 구성할 수 있는 이점을 가지면서, 시스템은 페이징을 통해 물리 메모리를 효율적으로 관리하고 외부 단편화 문제를 해결한다.
이러한 혼합 방식은 역사적으로 여러 컴퓨터 아키텍처에 구현되었다. 대표적으로 x86 아키텍처는 보호 모드에서 하드웨어 수준의 세그멘테이션을 지원한다. x86에서는 세그먼트 레지스터(CS, DS, SS 등)가 세그먼트 선택자 역할을 하며, 이는 글로벌 디스크립터 테이블이나 로컬 디스크립터 테이블을 참조하여 세그먼트의 베이스 주소와 한계 등을 얻는다. 그러나 현대 64비트 모드(x86-64)에서는 세그멘테이션의 사용이 극도로 제한되거나 사실상 무효화되고, 페이징이 주된 메모리 관리 기법으로 사용된다[5]. 이는 하드웨어적 복잡성을 줄이고 성능을 최적화하기 위한 것이다.
7.1. 혼합 방식 (세그멘테이션-페이징)
7.1. 혼합 방식 (세그멘테이션-페이징)
혼합 방식은 세그멘테이션과 페이징의 장점을 결합하여 메모리 관리의 효율성과 유연성을 높인다. 순수 세그멘테이션은 외부 단편화 문제가 있고, 순수 페이징은 사용자 관점의 논리적 구조를 반영하지 못하는 단점이 있다. 따라서 두 방식을 계층적으로 결합한 세그멘테이션-페이징 혼합 방식이 등장했다. 이 방식에서는 프로세스의 주소 공간이 먼저 논리적 단위인 세그먼트로 나뉜다. 그런 다음 각 세그먼트는 다시 고정 크기의 페이지들로 분할되어 물리 메모리에 적재된다.
구현 메커니즘은 각 프로세스마다 하나의 세그먼트 테이블을 유지하는 것으로 시작한다. 그러나 세그먼트 테이블의 각 항목은 해당 세그먼트의 기준 주소 대신, 그 세그먼트를 구성하는 페이지들의 정보를 담고 있는 페이지 테이블의 시작 주소를 가리킨다. 따라서 논리 주소는 세그먼트 번호, 페이지 번호, 페이지 내 변위(오프셋)의 세 부분으로 구성된다. 주소 변환은 두 단계를 거친다. 먼저 세그먼트 번호로 세그먼트 테이블을 참조해 페이지 테이블의 위치를 찾고, 다음으로 페이지 번호로 해당 페이지 테이블을 참조해 물리 메모리의 페이지 프레임 번호를 얻는다. 최종 물리 주소는 프레임 번호와 변위를 결합하여 생성된다.
이 방식의 주요 장점은 다음과 같다.
장점 | 설명 |
|---|---|
논리적 구조 유지 | |
외부 단편화 해결 | 세그먼트가 페이지들로 나뉘어 물리 메모리에 배치되므로 외부 단편화가 발생하지 않는다. |
효율적 메모리 활용 | 페이지 단위로 적재와 스왑이 이루어져 내부 단편화만 제한적으로 발생하며, 메모리 활용도가 높다. |
그러나 단점도 존재한다. 주소 변환이 두 번의 테이블 조회(세그먼트 테이블, 페이지 테이블)를 필요로 하므로 순수 페이징에 비해 오버헤드가 클 수 있다. 또한 메모리 내에 여러 수준의 테이블(세그먼트 테이블과 여러 개의 페이지 테이블)을 유지해야 해서 관리가 복잡해진다. 이러한 특성으로 인해 혼합 방식은 인텔 x86 아키텍처와 같은 일부 시스템에서 역사적으로 사용되었다.
7.2. x86 아키텍처의 예
7.2. x86 아키텍처의 예
x86 아키텍처는 역사적으로 세그멘테이션 메모리 관리 방식을 하드웨어 수준에서 광범위하게 지원하는 대표적인 예이다. 초기 16비트 모드(실제 모드)부터 32비트 보호 모드에 이르기까지 메모리 접근의 기본 단위로 세그먼트를 사용하였다. 이 아키텍처에서 코드, 데이터, 스택 등은 각각 별도의 세그먼트에 위치하며, 세그먼트 레지스터(CS, DS, SS, ES 등)가 현재 활성화된 세그먼트의 기준 주소를 가리킨다. 주소 지정은 '세그먼트:오프셋' 쌍으로 이루어지며, 이는 논리 주소를 형성한다.
32비트 보호 모드에서 세그멘테이션은 더욱 정교한 보호 메커니즘과 결합된다. 각 세그먼트는 세그먼트 디스크립터라는 데이터 구조로 정의되며, 이는 기준 주소(Base), 한계(Limit), 접근 권한(Type), 권한 수준(DPL) 등의 정보를 담고 있다. 모든 디스크립터는 글로벌 디스크립터 테이블(GDT) 또는 로컬 디스크립터 테이블(LDT)에 저장된다. 프로세서는 세그먼트 레지스터에 저장된 선택자(Selector)를 통해 해당 테이블에서 디스크립터를 로드하고, 이를 바탕으로 오프셋을 더해 선형 주소(Linear Address)로 변환한다. 이 과정에서 한계 검사와 권한 검사가 수행되어 메모리 보호를 제공한다.
구성 요소 | 설명 | 역할 |
|---|---|---|
세그먼트 레지스터(CS, DS 등) | 선택자(인덱스) 값을 보관 | 현재 사용 중인 세그먼트 지정 |
선택자(Selector) | 디스크립터 테이블 내 인덱스 | GDT/LDT 내 특정 디스크립터를 가리킴 |
디스크립터 테이블(GDT/LDT) | 세그먼트 디스크립터 배열 | 모든 세그먼트의 속성 정보 저장 |
세그먼트 디스크립터 | 기준 주소, 한계, 권한 등 | 단일 세그먼트의 물리적/보안 속성 정의 |
그러나 현대 운영체제(예: 리눅스, 윈도우)는 x86의 세그멘테이션 기능을 최소한으로만 사용하는 경향이 있다. 이는 순수 세그멘테이션의 외부 단편화 문제와 성능 오버헤드를 피하고, 페이징 방식이 더 효율적인 가상 메모리 관리를 제공하기 때문이다. 대부분의 32비트 및 64비트 운영체제는 '플랫(flat)' 메모리 모델을 채택한다. 즉, 모든 세그먼트의 기준 주소를 0으로, 한계를 최대값(4GB 등)으로 설정하여 전체 주소 공간을 하나의 연속된 세그먼트로 취급한다. 이렇게 하면 응용 프로그램 관점에서는 세그멘테이션이 투명해지고, 실제 메모리 관리의 주된 역할은 페이징 기법이 담당하게 된다. 따라서 x86의 세그멘테이션은 하드웨어적 호환성과 기본적인 보호 모드 진입을 위한 틀을 제공하는 역사적 유산으로 남아있다고 볼 수 있다.
