Memory Management in Operating Systems: Address Structure and Allocation Methods

1️⃣ Overview of Memory Management and Address Structure
2️⃣ Contiguous memory allocation method
3️⃣ Non-contiguous memory allocation method
1️⃣ Overview of Memory Management and Address Structure
메모리 관리 (Memory Management)
이번 주차에는 메모리 관리(Memory Management)에 대해 알아본다. 프로세스(Process)가 실행되기 위해서는 반드시 메모리(Memory)가 필요하다. 여러 프로세스가 동시에 실행될 때는 이 메모리를 효율적으로 나누고 보호하는 것이 중요하다. 따라서 이번 시간에는 운영체제(Operating System)가 메모리를 어떻게 관리하는지 공부한다.
이번 주차의 목표는 먼저 메모리 관리의 필요성과 메모리가 수행하는 역할을 이해하는 것이다. 또한 논리 주소(Logical Address)와 물리 주소(Physical Address)의 차이를 설명할 수 있어야 한다.
이어서 연속 메모리 할당 방식(Contiguous Memory Allocation)의 구조를 이해하고, 분할 방식(Partitioning)의 특징을 살펴본다. 또한 메모리 배치 전략(Memory Placement Strategy)과 단편화(Fragmentation) 문제가 무엇인지 설명할 수 있어야 한다.
마지막으로 비연속 메모리 할당 방식(Non-Contiguous Memory Allocation)의 개념을 이해한다. 특히 페이징(Paging)과 세그멘테이션(Segmentation)의 구조, 그리고 주소 변환(Address Translation) 과정이 어떻게 이루어지는지 살펴본다.
즉, 이번 주차에서는 메모리의 기본 개념을 이해하고, 다양한 메모리 할당 방식을 학습한 뒤, 주소 변환이 이루어지는 과정까지 차례대로 살펴볼 예정이다.
❓메모리 관리의 필요성 (Need for Memory Management)
메모리 관리(Memory Management)의 필요성에 대해 생각해보자. 한 컴퓨터에서 여러 프로그램이 동시에 실행된다고 가정하면, 각 프로그램이 실행되기 위해서는 메모리에 적재되어야 한다.
그런데 운영체제(Operating System)가 메모리를 따로 관리하지 않고 프로그램들이 자유롭게 사용한다면 어떤 일이 발생할까? 서로 다른 프로그램이 같은 메모리 영역(Memory Area)을 사용하게 되면 데이터(Data)가 덮어써지거나 다른 프로그램의 영역을 침범하는 문제가 발생할 수 있다.
이로 인해 프로그램이 비정상적으로 동작하거나, 심한 경우에는 시스템 전체에 오류가 발생할 수 있다. 즉, 이 질문은 메모리를 안전하게 보호하고 관리해야 할 필요성을 이해하게 해주는 질문이다.
❓메모리 주소와 주소 연결 (Memory Address and Address Mapping)
다음 질문을 살펴보자. 프로그램이 실행될 때는 메모리 주소(Memory Address)를 이용해서 데이터를 읽고 저장한다. 그렇다면 프로그램이 사용하는 주소가 실제 메모리의 물리적인 위치와 항상 동일할까?
여러 프로그램이 동시에 실행되는 환경에서는 각 프로그램이 사용하는 주소를 그대로 사용할 수 없다. 따라서 물리 주소(Physical Address)와 논리 주소(Logical Address)를 구분할 필요가 있으며, 이 둘을 연결해주는 과정이 필요하다.
운영체제(Operating System)는 프로그램이 사용하는 주소를 실제 메모리의 위치와 연결해주어야 한다. 즉, 프로그램이 바라보는 주소와 실제 메모리상의 위치가 어떻게 대응되는지를 관리해야 한다.
정리하면, 첫 번째 질문은 “왜 메모리 관리(Memory Management)가 필요한가?”에 대한 질문이다. 두 번째 질문은 “주소를 어떻게 연결할 것인가?”에 대한 문제이다. 오늘은 이 두 가지를 중심으로 공부할 예정이다.
메모리 관리의 복잡성과 이중성 (Complexity and Duality of Memory Management)
이번 차시에서는 메모리 관리(Memory Management)의 필요성과 기본 개념을 이해하기 위해, 먼저 메모리가 수행하는 역할과 주소 구조(Address Structure)에 대해 살펴본다.
초기 일괄 처리 시스템(Batch Processing System)을 떠올려보면, 당시에는 한 번에 하나의 작업(Job)만 실행되는 구조였다. 따라서 하나의 프로그램(Program)만 메모리에 올라갔고, 다른 프로그램과 메모리 영역이 겹칠 일이 없었다. 이 때문에 메모리 관리는 비교적 어렵지 않았다.
하지만 현재의 시스템에서는 여러 프로그램이 동시에 실행되며, 이 프로그램들이 함께 메모리에 올라오게 된다. 이렇게 되면 하나의 메모리를 여러 프로그램이 공유하게 되고, 서로 간섭하지 않도록 관리해야 하는 문제가 생긴다. 결국 여러 프로그램이 하나의 메모리를 함께 사용하게 되면서 메모리 관리는 점점 더 복잡해진다.
메모리 관리에는 이중성(Duality)도 존재한다. 각 프로세스(Process)의 입장에서 보면, 자신에게 할당된 메모리를 독립적으로 사용하고 싶어 한다. 즉, 다른 프로세스의 영향을 받지 않고 자기 영역만 안전하게 사용하기를 원한다.
반면 운영체제(Operating System)의 입장에서는 하나의 메모리를 여러 프로세스가 사용할 수 있도록 효율적으로 관리해야 한다. 이 과정에서 각 프로세스는 자신의 메모리가 침범되지 않도록 보호(Protection)를 요구하게 된다.
결국 운영체제는 메모리를 효율적으로 활용해야 하는 동시에, 각 프로세스의 메모리 보호도 함께 보장해야 한다. 메모리 관리는 이러한 두 가지 요구를 동시에 만족시켜야 하는 중요한 역할을 수행한다.
메모리 관리의 이중성 (Duality of Memory Management)
메모리 관리(Memory Management)의 이중성은 메모리를 바라보는 관점이 서로 다르다는 점에서 이해할 수 있다.
먼저 프로세스(Process) 입장에서의 메모리(Memory)는 자신만의 메모리 공간이 따로 존재하는 것처럼 보인다. 각 프로세스는 다른 프로세스와 메모리를 전혀 공유하지 않고, 독립적으로 사용하는 것처럼 인식한다.
반면 메모리 관리자(Memory Manager) 입장에서의 메모리는 여러 프로세스가 나누어 사용하는 구조이다. 운영체제(Operating System)는 메모리를 구분해서 각 프로세스에 할당하고, 필요에 따라 재배치하며 관리한다.
결국 메모리 관리의 핵심은 프로세스에게는 메모리가 독립적으로 보이게 하면서, 실제로는 하나의 메모리를 여러 프로세스가 공유하도록 만드는 것이다.
메모리 관리 작업 (Memory Management Operations)
메모리 관리(Memory Management)에서 실제로 어떤 작업이 이루어지는지 살펴보자.
먼저 실행할 프로그램과 데이터를 메모리로 가져오는 과정이 필요하다. 그리고 메모리에 올라온 프로세스를 어느 위치에 배치할 것인지 결정해야 한다. 또한 메모리가 부족해지면 어떤 프로세스를 내보낼지도 결정해야 한다.
결국 메모리 관리는 가져오기(Fetch), 배치(Placement), 재배치(Replacement)의 과정으로 이루어지며, 이 세 가지를 중심으로 동작한다고 볼 수 있다.
그림을 보면 메모리 관리의 흐름을 한눈에 확인할 수 있다. 먼저 실행할 프로세스는 하드 디스크(Hard Disk)와 같은 보조기억장치(Secondary Storage)에 저장되어 있다가, 필요할 때 메모리로 가져오게 된다. 이렇게 메모리로 가져오는 과정을 가져오기(Fetch)라고 한다.
그다음 메모리로 올라온 프로세스를 어느 위치에 둘 것인지 결정하는 과정을 배치(Placement)라고 한다. 만약 메모리가 부족한 경우에는 이미 메모리에 올라와 있는 프로세스 중 일부를 내보내고, 새로운 프로세스를 넣어야 한다. 이러한 과정을 재배치(Replacement)라고 한다.
이처럼 메모리 관리는 가져오기(Fetch), 배치(Placement), 재배치(Replacement)의 흐름 속에서 이루어진다고 볼 수 있다.
메모리 주소와 주소 공간 (Memory Address and Address Space)
메모리 주소(Memory Address)와 주소 공간(Address Space)의 개념을 조금 더 구체적으로 살펴보자.
메모리(Memory)는 하나의 연속된 주소 공간으로 이루어져 있다. 각 메모리 위치는 서로 다른 고유한 주소(Address)를 가지며, CPU는 이 주소를 이용해 데이터를 읽거나 명령어(Instruction)를 가져온다.
즉, 프로그램(Program)이 실행되는 과정에서는 특정 위치를 직접 지정하기보다, 주소를 기준으로 메모리에 접근한다고 볼 수 있다.
따라서 메모리 주소는 데이터(Data)가 어디에 있는지 구분하고, 해당 위치에 정확하게 접근하기 위한 기준으로 이해할 수 있다.
주소 공간 (Address Space)
프로그램(Program)이 실행될 때 각 프로그램은 자신이 사용할 수 있는 메모리 범위를 가진다. 이 범위 안에서 명령어(Instruction)와 데이터(Data)를 저장하고 접근하게 된다.
여기서 중요한 점은 각 프로세스(Process)가 서로 독립적인 주소 공간(Address Space)을 가진다는 것이다. 즉, 한 프로세스가 사용하는 주소와 다른 프로세스가 사용하는 주소는 서로 분리되어 있다.
이렇게 주소 공간이 분리되어 있기 때문에 여러 프로그램이 동시에 실행되더라도 서로 간섭하지 않고 동작할 수 있다.
논리 주소와 물리 주소 (Logical Address and Physical Address)
앞에서 각 프로세스(Process)가 독립적인 주소 공간(Address Space)을 가진다고 했다. 그렇다면 여기서 한 가지 의문이 생긴다. 프로그램(Program)이 사용하는 주소와 실제 메모리(Memory)의 위치는 과연 같을까?
먼저 프로그램이 사용하는 주소를 논리 주소(Logical Address)라고 한다. 이 주소는 프로그램이 실행되는 과정에서 CPU가 생성하며, 프로그램이 직접 사용하는 주소이다. 즉, 프로그램의 입장에서 사용하는 주소라고 이해할 수 있다.
반면 실제 메모리에 데이터(Data)가 저장되는 물리적인 위치도 따로 존재한다. 이를 물리 주소(Physical Address)라고 한다. 물리 주소는 실제 메모리 하드웨어(Memory Hardware)에서 사용하는 주소이며, 운영체제(Operating System)가 관리하는 대상이라고 볼 수 있다.
결국 프로그램이 사용하는 논리 주소와 실제 메모리의 물리 주소는 서로 다르다. 따라서 이 두 주소를 구분해서 이해하는 것이 중요하다.
논리 주소와 물리 주소의 차이는 그림을 통해 직관적으로 이해할 수 있다. 왼쪽을 보면 각 프로세스가 자신만의 주소 공간을 독립적으로 가지고 있다. 그래서 각 프로세스는 자신의 주소가 연속적으로 구성되어 있는 것처럼 인식한다.
하지만 실제 물리 메모리(Physical Memory)를 보면, 하나의 메모리 공간 안에 여러 프로세스가 섞여 배치되어 있다. 실제 메모리에서는 각 프로세스의 데이터가 반드시 처음부터 끝까지 연속적으로 존재하지 않을 수도 있다.
이때 중요한 점은 논리 주소와 물리 주소가 반드시 1:1로 대응되는 것이 아니라, 서로 다른 위치로 변환되어 연결된다는 것이다. 결국 프로세스 입장에서는 연속된 주소 공간을 사용하는 것처럼 보이지만, 실제로는 운영체제가 주소를 적절히 변환하여 물리 메모리에 배치하고 있다고 이해하면 된다.
주소 변환의 필요성 (Need for Address Translation)
앞서 본 그림처럼 프로그램(Program)은 논리 주소(Logical Address)를 기준으로 메모리(Memory)에 접근하지만, 실제 메모리는 물리 주소(Physical Address)를 기준으로 동작한다.
즉, 프로그램이 사용하는 주소와 실제 메모리가 사용하는 주소가 서로 다르기 때문에, 이 둘을 연결해주는 과정이 필요하다. 이 과정을 주소 변환(Address Translation)이라고 볼 수 있다.
또한 중요한 문제는 이 주소를 언제 실제 물리적 위치와 연결할 것인지이다. 논리 주소와 물리 주소를 연결하는 것뿐만 아니라, 그 연결이 이루어지는 시점도 함께 결정해야 한다.
결국 주소 변환에서 중요한 핵심은 논리 주소와 물리 주소를 연결하는 것, 그리고 그 연결 시점을 결정하는 것이라고 할 수 있다.
주소 바인딩 방식 (Address Binding Methods)
다음은 주소를 실제 메모리 위치에 언제 연결할 것인지에 따라 주소 바인딩(Address Binding) 방식을 나누어 살펴본다.
주소 바인딩은 크게 세 가지로 구분된다. 첫 번째는 컴파일 시간 바인딩(Compile-Time Binding)이다. 이는 프로그램을 컴파일(Compile)하는 시점에 이미 물리 주소(Physical Address)가 결정되는 방식이다.
두 번째는 적재 시간 바인딩(Load-Time Binding)이다. 이는 프로그램이 메모리(Memory)에 적재될 때 주소가 결정되는 방식이다.
세 번째는 실행 시간 바인딩(Execution-Time Binding)이다. 이는 프로그램이 실행되는 동안 주소 변환(Address Translation)이 이루어지는 방식이다. 이처럼 주소를 연결하는 시점에 따라 각기 다른 바인딩 방식이 존재한다.
그림을 보면 주소 바인딩이 실제로 어떻게 이루어지는지 이해할 수 있다. 먼저 컴파일 단계(Compile Stage)에서는 원시 코드(Source Code)가 컴파일러(Compiler)를 거쳐 목적 파일(Object File)로 변환된다. 이 단계에서는 프로그램이 사용할 주소가 어느 정도 결정되는 과정이 이루어진다.
다음 적재 단계(Load Stage)에서는 여러 라이브러리(Library)와 목적 파일(Object File)이 링커(Linker)를 통해 하나로 연결된다. 이후 로더(Loader)에 의해 메모리에 적재되며, 이 과정에서 실제 메모리에 올라갈 위치가 결정된다.
마지막 실행 단계(Execution Stage)에서는 프로그램이 메모리에 올라간 이후에도 필요에 따라 주소 변환이 계속 이루어진다. 즉, 실행 중에도 주소가 동적으로 연결되는 방식이라고 볼 수 있다.
이처럼 주소 바인딩은 컴파일(Compile), 적재(Load), 실행(Execution)의 흐름 속에서 단계적으로 이루어진다고 이해할 수 있다.
실행 시간 주소 바인딩 (Execution-Time Address Binding)
주소 바인딩(Address Binding)은 컴파일 시점(Compile Time), 적재 시점(Load Time), 실행 시점(Execution Time)으로 나누어 이루어진다. 이 중에서 특히 중요한 것이 실행 시간 바인딩(Execution-Time Binding)이다.
앞에서 프로그램(Program)이 실행되는 동안에도 주소 변환(Address Translation)이 계속 이루어진다고 했다. 실행 시간 바인딩 방식에서는 프로그램이 메모리(Memory)의 고정된 위치에 묶여 있지 않아도 실행될 수 있다. 즉, 메모리의 어느 위치에 올라가더라도 문제없이 동작할 수 있다.
또한 실행 중에도 필요에 따라 프로세스(Process)의 위치를 다른 곳으로 이동시킬 수 있다. 따라서 실행 시간 바인딩은 메모리를 보다 유연하게 사용할 수 있도록 해주는 방식이라고 볼 수 있다.
MMU (Memory Management Unit)
지금까지 주소 바인딩(Address Binding)이 이루어지는 과정을 살펴보았다. 이제 이렇게 결정된 주소가 실제 실행 과정에서 어떻게 변환되는지 생각해볼 필요가 있다. 이 역할을 수행하는 장치가 바로 MMU(Memory Management Unit)이다.
MMU는 논리 주소(Logical Address)를 물리 주소(Physical Address)로 변환해주는 하드웨어 장치이다. CPU가 생성한 논리 주소를 입력으로 받아, 실제 메모리(Memory)에서 사용되는 물리 주소로 바꿔준다.
이 장치는 CPU 내부 또는 CPU 패키지(CPU Package)에 포함되어 있으며, 프로그램(Program)이 메모리를 사용할 때마다 주소 변환(Address Translation)을 담당한다.
따라서 MMU는 논리 주소를 물리 주소로 변환해주면서, 각 프로세스(Process)가 독립적인 메모리를 사용하는 것처럼 보이도록 만들어주는 역할을 한다.
MMU의 역할 (Role of MMU)
이 그림은 MMU(Memory Management Unit)가 어떤 역할을 하는지 보여준다. 프로세스(Process)에서 생성된 논리 주소(Logical Address)가 MMU로 전달되면, MMU는 이 주소를 변환하여 물리 주소(Physical Address)로 바꿔준다.
그다음 변환된 물리 주소를 기준으로 실제 메모리(Memory)에 대한 접근이 이루어진다. 즉, 프로세스가 사용하는 논리 주소가 바로 메모리에 접근하는 것이 아니라, MMU를 거쳐 물리 주소로 변환된 뒤 실제 메모리에 접근하게 된다.
이 과정에서 MMU는 고정 분할(Fixed Partitioning), 동적 분할(Dynamic Partitioning), 페이징(Paging), 세그멘테이션(Segmentation)과 같은 다양한 메모리 관리 기법(Memory Management Techniques)을 기반으로 주소 변환(Address Translation)을 수행한다.
결국 중요한 점은 이러한 기법들이 서로 다른 방식으로 구현될 뿐, MMU가 수행하는 핵심 역할은 논리 주소(Logical Address)를 물리 주소(Physical Address)로 변환하는 것이라는 점이다.
메모리 보호의 필요성 (Need for Memory Protection)
여러 프로세스(Process)가 하나의 물리 메모리(Physical Memory)를 함께 사용하는 환경을 떠올려보자. 이때 잘못된 주소(Address)로 접근하게 되면 다른 프로세스의 데이터(Data)를 침범할 수 있다.
또한 사용자 프로그램(User Program)이 운영체제(Operating System)의 영역까지 접근하게 되면, 시스템(System) 자체가 손상될 위험도 존재한다. 이러한 오류는 단순히 하나의 프로그램에서 끝나는 것이 아니라, 시스템 전체의 오류로 확산될 수도 있다.
따라서 운영체제는 이러한 문제를 방지하기 위해 메모리 보호(Memory Protection) 기능을 반드시 제공해야 한다.
메모리 보호와 기준 레지스터 (Memory Protection and Base Register)
메모리 보호(Memory Protection)를 구현하기 위한 방법 중 하나가 기준 레지스터(Base Register)이다. 기준 레지스터는 베이스 레지스터(Base Register)라고도 하며, 프로세스(Process)가 사용할 수 있는 메모리(Memory)의 시작 주소를 저장하는 역할을 한다.
프로그램(Program)이 사용하는 논리 주소(Logical Address)에 기준 레지스터의 값, 즉 베이스 값(Base Value)을 더하면 물리 주소(Physical Address)가 만들어진다. 각 프로세스마다 서로 다른 베이스 값을 가지도록 설정하기 때문에, 각 프로세스는 서로 다른 메모리 영역(Memory Area)을 사용하게 된다.
결국 기준 레지스터는 논리 주소를 물리 주소로 변환할 때 기준이 되는 값을 제공하는 역할을 한다고 볼 수 있다.
그림을 통해 기준 레지스터를 이용한 주소 변환(Address Translation) 과정을 확인할 수 있다. 예를 들어 프로세스에서 생성된 논리 주소가 346이고, 기준 레지스터에 저장된 값이 1400이라면 두 값을 더해 1746이라는 물리 주소가 만들어진다.
이렇게 생성된 물리 주소를 기준으로 실제 메모리에 접근하게 된다. 이처럼 논리 주소에 기준값을 더해 물리 주소를 생성하는 방식이 기준 레지스터 방식이다.
경계 레지스터를 이용한 메모리 보호 (Memory Protection with Limit Register)
기준 레지스터(Base Register)와 함께 사용되는 것이 경계 레지스터(Limit Register)이다. 경계 레지스터는 프로세스(Process)가 사용할 수 있는 메모리(Memory)의 크기, 즉 허용된 범위(Limit)를 저장한다.
이때 CPU가 생성한 논리 주소(Logical Address)가 허용된 범위를 초과하는지 MMU(Memory Management Unit)와 같은 하드웨어가 검사한다. 만약 프로세스가 허용된 범위를 벗어난 주소로 접근하려고 하면, 해당 접근은 차단된다.
이렇게 해서 프로세스가 자신에게 할당된 메모리 영역(Memory Area)을 넘어가지 못하도록 보호할 수 있다.
그림을 통해 경계 레지스터가 어떻게 메모리를 보호하는지 살펴볼 수 있다. 경계 레지스터에는 프로세스가 접근할 수 있는 메모리의 범위가 저장되어 있다. 그러면 CPU가 생성한 논리 주소가 이 범위를 벗어나는지 MMU가 검사하게 된다.
그림에서 운영체제 영역(Operating System Area)은 상위 주소 영역에 위치하고, 사용자 프로그램 영역(User Program Area)은 아래쪽 영역만 사용하도록 제한되어 있다. 따라서 프로그램이 허용된 범위를 벗어나 운영체제 영역에 접근하려고 하면, 해당 접근은 차단된다.
즉, 경계 레지스터는 사용자 프로그램(User Program)이 운영체제 영역을 침범하지 못하도록 보호하는 역할을 한다.
메모리 보호 동작 과정 (Memory Protection Operation)
메모리 보호(Memory Protection)가 실제로 동작하는 흐름을 살펴보자.
먼저 CPU가 생성한 논리 주소(Logical Address)가 MMU(Memory Management Unit)로 전달된다. MMU는 이 논리 주소가 허용된 범위, 즉 경계값(Limit Value) 안에 있는지 검사한다.
만약 논리 주소가 허용된 범위 안에 있다면, 기준값(Base Value)을 더해 물리 주소(Physical Address)를 생성한다. 반대로 논리 주소가 허용된 범위를 초과하면 예외(Exception)가 발생하고, 이때 운영체제(Operating System)가 개입하게 된다.
즉, 모든 메모리 접근 과정에서는 하드웨어 수준에서 보호 검사(Protection Check)가 수행된다고 이해할 수 있다.
그림을 통해 메모리 보호가 실제로 어떻게 이루어지는지 구체적으로 살펴보자. 먼저 각 프로세스(Process)마다 기준 레지스터(Base Register)와 경계 레지스터(Limit Register) 값이 설정되어 있다. 기준 레지스터는 시작 주소(Start Address)를 의미하고, 경계 레지스터는 사용할 수 있는 범위(Limit)를 의미한다.
이를 바탕으로 실제 동작 순서를 보면, 프로세스가 생성한 논리 주소가 먼저 허용된 범위 안에 있는지 검사된다. 즉, 이 주소가 경계 레지스터 값보다 작은지를 MMU가 먼저 확인한다.
이 조건을 만족하는 경우에만 그다음 기준 레지스터 값을 더해 물리 주소를 생성한다. 따라서 메모리 보호 과정은 먼저 범위를 검사하고, 그다음 주소 변환(Address Translation)을 수행하는 순서로 이루어진다고 볼 수 있다.
반대로 논리 주소가 허용된 범위를 벗어나면 트랩(Trap)이 발생한다. 이 경우 주소 지정 오류(Addressing Error)로 처리되며, 운영체제가 개입하게 된다.
이처럼 기준 레지스터와 경계 레지스터를 이용하면, 각 프로세스가 자신에게 허용된 범위 안에서만 메모리를 사용하도록 제어할 수 있다.
메모리 보호 예제 (Memory Protection Example)
하나의 예제를 통해 메모리 보호(Memory Protection)가 어떻게 동작하는지 확인해보자.
어떤 프로세스(Process)의 기준값(Base Value)이 1000이고, 경계값(Limit Value)이 500이라고 가정한다. 여기서 중요한 점은 경계값이 마지막 주소값이 아니라, 프로세스가 사용할 수 있는 메모리의 크기(Memory Size)를 의미한다는 것이다.
따라서 해당 프로세스가 실제로 접근할 수 있는 물리 주소(Physical Address)의 범위는 1000부터 1499까지가 된다.
이때 논리 주소(Logical Address)가 200이라면, 이 값은 경계값의 범위 안에 있다. 따라서 기준 레지스터(Base Register)의 값이 더해져 물리 주소 1200으로 변환되고, 정상적으로 메모리에 접근할 수 있다.
반대로 논리 주소가 600이라면, 이 값은 경계값을 초과한다. 따라서 접근이 차단되고 예외(Exception)가 발생한다. 이 경우 프로그램(Program)이 강제로 종료되거나, 운영체제(Operating System)가 개입하여 처리하게 된다.
지금까지 메모리 관리(Memory Management)의 기본 개념과 주소 변환(Address Translation), 그리고 메모리 보호 방법까지 살펴보았다. 다음에는 이러한 개념을 바탕으로 실제 메모리 할당 방식(Memory Allocation Methods)에 대해 살펴볼 예정이다.
2️⃣ Contiguous memory allocation method
연속 메모리 할당 방식 (Contiguous Memory Allocation)
이번에는 메모리(Memory)를 실제로 어떤 방식으로 나누어 사용하는지 살펴보기 위해, 연속 메모리 할당 방식(Contiguous Memory Allocation)에 대해 알아본다.
앞에서 프로세스(Process)가 실행되기 위해서는 반드시 메모리에 적재되어야 한다고 배웠다. 메모리를 할당하는 방식은 크게 두 가지로 나눌 수 있는데, 연속 메모리 할당(Contiguous Memory Allocation)과 불연속 메모리 할당(Non-Contiguous Memory Allocation)이다.
먼저 살펴볼 연속 메모리 할당 방식은 프로세스를 하나의 연속된 공간에 적재하는 방식이다.
그림을 통해 메모리 할당 방식의 전체 구조를 정리해보자. 메모리 할당 방식은 크게 연속 메모리 할당 방식과 불연속 메모리 할당 방식으로 나뉜다.
연속 메모리 할당 방식은 하나의 프로세스를 메모리의 연속된 공간에 배치하는 방식이다. 반면 불연속 메모리 할당 방식은 하나의 프로세스를 여러 개의 분산된 공간에 나누어 배치하는 방식이다.
이 중에서 지금부터는 연속 메모리 할당 방식에 해당하는 방법들을 살펴본다.
단일 프로그래밍 방식 (Single Programming)
먼저 가장 단순한 형태인 단일 프로그래밍(Single Programming) 방식에 대해 살펴본다. 단일 프로그래밍 방식은 한 번에 하나의 프로그램(Program)만 메모리(Memory)에 적재해서 실행하는 방식이다. 따라서 메모리는 운영체제(Operating System)와 사용자 프로그램(User Program)이 나누어 사용하는 구조를 가진다.
하지만 이 방식에서는 프로그램이 입출력(I/O) 작업을 수행하는 동안 CPU가 아무 작업도 하지 못하고 대기 상태(Waiting State)가 될 수 있다. 따라서 단일 프로그래밍 구조는 매우 단순하지만, 전체 자원 활용(Resource Utilization) 효율은 낮은 방식이라고 볼 수 있다.
다음으로 단일 프로그램의 메모리 구조(Memory Structure)를 살펴보면, 메모리가 어떻게 나뉘어 사용되는지 확인할 수 있다. 메모리는 크게 운영체제 영역(Operating System Area)과 사용자 영역(User Area)으로 구분된다.
운영체제 영역에는 시스템 코드(System Code)와 핵심 기능이 저장되고, 사용자 영역에는 실제 실행 중인 프로그램이 올라오게 된다. 여기서 중요한 점은 사용자 프로그램이 운영체제 영역에 접근하지 못하도록 보호된다는 것이다.
이러한 구조를 통해 단일 프로그램 환경에서는 하나의 프로그램이 메모리를 독점적으로 사용하면서 안정적으로 실행될 수 있게 된다.
단일 프로그램의 메모리 적재 과정 (Memory Loading in Single Programming)
단일 프로그램(Single Program) 환경에서 프로그램(Program)이 메모리(Memory)에 올라오는 과정을 살펴보자.
사용자 프로그램(User Program)은 메모리의 사용자 영역(User Area)에 적재된다. 프로그램은 실행될 때마다 디스크(Disk)와 같은 보조기억장치(Secondary Storage)에서 메모리로 올라오게 된다.
새로운 프로그램을 실행하면 기존에 실행되던 프로그램은 메모리에서 제거되고, 새로운 프로그램이 다시 적재된다. 즉, 단일 프로그램 환경은 한 번에 하나의 프로그램만 메모리를 사용하는 구조라고 볼 수 있다.
다중 프로그래밍의 필요성 (Need for Multiprogramming)
연속 메모리 할당 방식(Contiguous Memory Allocation)에서 다중 프로그래밍(Multiprogramming)이 왜 필요한지 살펴보자.
단일 프로그램 환경(Single Program Environment)에서는 한 번에 하나의 프로그램(Program)만 실행된다. 따라서 프로그램이 입출력(I/O) 작업을 수행하는 동안에는 CPU가 아무 일도 하지 않고 대기하게 된다.
이로 인해 CPU와 메모리(Memory) 자원이 비효율적으로 사용된다. 따라서 여러 프로그램을 동시에 실행하여 **자원 활용률(Resource Utilization)**을 높일 필요가 있다.
이러한 요구를 해결하기 위해 고정 분할 방식(Fixed Partitioning)과 같은 메모리 할당 방식(Memory Allocation Method)이 사용된다.
고정 분할 방식 (Fixed Partitioning)
고정 분할 방식(Fixed Partitioning)은 운영체제(Operating System)가 메모리(Memory)를 여러 개의 고정된 크기 영역으로 나누어 관리하는 방식이다.
각 영역에는 하나의 프로그램(Program)만 적재되며, 이를 통해 여러 프로그램이 동시에 실행될 수 있는 구조가 만들어진다. 이때 분할 영역(Partition)의 크기는 미리 고정되어 있기 때문에 실행 중에는 변경되지 않는다.
따라서 고정 분할 방식은 구조가 단순하다는 장점이 있지만, 메모리를 유연하게 활용하기에는 한계가 있다.
그림을 통해 고정 분할 방식이 어떻게 동작하는지 살펴보자. 운영체제는 메모리를 미리 여러 개의 고정된 영역으로 나누고, 각 영역에 하나의 프로그램을 할당한다.
그림에서 A, B, C 와 같은 각 영역에 프로그램이 하나씩 올라와 있는 것을 볼 수 있다. 각 영역은 독립적으로 사용되기 때문에 여러 프로그램을 동시에 실행할 수 있다.
하지만 각 영역의 크기가 고정되어 있으므로, 프로그램의 크기에 따라 사용되지 않는 남는 공간이 발생할 수 있다는 점도 함께 고려해야 한다.
내부 단편화 (Internal Fragmentation)
분할 내부에 사용되지 않고 남는 공간을 내부 단편화(Internal Fragmentation)라고 한다. 내부 단편화는 프로세스(Process)의 크기가 분할된 영역(Partition)의 크기보다 작을 때 발생한다.
이때 남은 공간은 다른 프로세스가 사용할 수 없기 때문에, 결과적으로 메모리(Memory) 낭비가 발생하게 된다.
그림을 통해 내부 단편화가 실제로 어떻게 나타나는지 살펴보자. 오른쪽을 보면 메모리가 모두 20KB 크기의 고정된 영역으로 나뉘어 있다. 각 영역에는 하나의 프로세스가 할당된다.
그런데 왼쪽의 프로세스들을 보면 각각의 크기가 40KB, 18KB, 30KB, 17KB처럼 분할 크기와 정확히 맞지 않는다. 예를 들어 크기가 18KB인 프로세스 B(Process B)는 20KB짜리 하나의 영역에 적재되지만, 2KB의 공간이 남게 된다. 17KB인 프로세스 D(Process D)도 20KB짜리 하나의 영역을 사용하면서 3KB의 공간이 사용되지 않고 남게 된다.
이처럼 고정된 크기의 분할에 맞추어 프로세스를 적재하다 보면, 각 분할 내부에 사용되지 않는 공간이 발생한다. 이 남는 공간을 내부 단편화라고 이해할 수 있다.
가변 분할 방식과 외부 단편화 (Variable Partitioning and External Fragmentation)
앞서 고정 분할 방식(Fixed Partitioning)에서는 분할 크기(Partition Size)가 고정되어 있기 때문에 내부 단편화(Internal Fragmentation)가 발생한다고 배웠다. 이러한 문제를 해결하기 위한 방식이 가변 분할 방식(Variable Partitioning)이다.
가변 분할 방식에서는 프로세스(Process)의 크기에 맞게 메모리(Memory)를 연속된 공간으로 동적으로 할당한다. 즉, 메모리를 미리 고정된 크기로 나누는 것이 아니라, 필요할 때마다 적절한 크기로 나누어 사용한다. 따라서 필요한 만큼만 메모리를 할당할 수 있고, 그만큼 메모리 낭비를 줄일 수 있다.
가변 분할 방식에서는 프로세스 크기에 맞는 연속된 빈 공간을 찾아 그 위치에 프로세스를 적재한다. 고정된 크기로 나누어놓은 공간을 사용하는 것이 아니라, 필요한 크기만큼 공간을 할당하는 방식이다. 실행 중이던 프로세스가 종료되면 해당 위치에는 다시 빈 공간이 생긴다. 이렇게 메모리 안에 빈 공간이 생성되면, 또 다른 프로세스가 그 공간을 사용할 수 있다.
그림을 통해 가변 분할 방식에서 메모리가 어떻게 변화하는지 살펴보자. 프로세스들은 각자의 크기에 맞게 연속된 공간에 적재된다. 그런데 일부 프로세스가 종료되면 그 위치에는 빈 공간이 생긴다.
이때 빈 공간들이 메모리 중간중간에 끊어진 형태로 남게 된다. 이처럼 연속되지 않는 작은 빈 공간이 여러 개로 나뉘어 발생하는 현상을 외부 단편화(External Fragmentation)라고 한다.
즉, 전체적으로는 여유 공간이 충분하더라도 연속된 공간이 부족하면 새로운 프로세스를 적재하지 못하는 문제가 발생할 수 있다.
외부 단편화와 메모리 압축 (External Fragmentation and Memory Compaction)
앞서 그림에서 확인했듯이, 프로세스(Process)의 생성과 종료가 반복되면 빈 공간(Free Space)이 메모리(Memory)의 여러 위치에 분산되어 생기게 된다.
이렇게 연속되지 않는 빈 공간들이 많아지면, 전체 여유 공간은 충분하더라도 연속된 공간이 부족해서 새로운 프로세스를 적재하기 어려워질 수 있다. 이러한 현상을 외부 단편화(External Fragmentation)라고 한다.
결과적으로 외부 단편화가 발생하면 메모리 활용 효율(Memory Utilization)이 저하된다. 이 문제를 해결하기 위해 메모리 재배치(Memory Relocation), 즉 압축(Compaction)이 필요할 수 있다.
메모리 압축 (Memory Compaction)
메모리 압축(Memory Compaction)은 분산되어 있는 빈 공간(Free Space)을 하나의 연속된 공간으로 합치는 방식이다. 이를 위해 프로세스(Process)들을 한쪽으로 이동시켜 흩어져 있는 빈 공간을 하나로 모은다.
이렇게 하면 외부 단편화(External Fragmentation) 문제를 완화할 수 있다. 하지만 프로세스를 이동시키는 과정에서 추가적인 오버헤드(Overhead)가 발생할 수 있다.
그림을 통해 메모리 압축이 어떻게 이루어지는지 살펴보자. 왼쪽을 보면 프로세스들 사이에 빈 공간이 여러 군데로 나뉘어 있는 것을 확인할 수 있다. 이 상태에서는 전체 여유 공간이 충분하더라도 연속된 공간이 부족하기 때문에 새로운 프로세스를 적재하기 어려울 수 있다.
이러한 문제를 해결하기 위해 압축을 수행한다. 프로세스들을 한쪽으로 이동시켜 흩어져 있던 빈 공간을 하나의 연속된 공간으로 모으는 것이다.
오른쪽 그림을 보면 압축이 수행된 후 하나의 큰 빈 공간이 만들어진 것을 확인할 수 있다. 이처럼 메모리 압축을 통해 외부 단편화 문제를 해결할 수 있지만, 프로세스를 이동하는 과정에서 오버헤드가 발생할 수 있다는 점도 함께 고려해야 한다.
메모리 통합 (Memory Coalescing)
연속 메모리 할당 방식(Contiguous Memory Allocation)에서 외부 단편화(External Fragmentation)를 해결하는 또 다른 방법으로 메모리 통합(Memory Coalescing)이 있다.
메모리 통합은 서로 인접해 있는 빈 공간(Free Space)들을 하나로 합치는 방법이다. 이 방식은 프로세스(Process)를 이동시키지 않고, 빈 공간끼리만 병합(Merge)하기 때문에 압축(Compaction) 방식에 비해 오버헤드(Overhead)가 적다는 특징이 있다.
하지만 메모리 통합은 인접한 빈 공간이 있을 때만 수행될 수 있다. 따라서 외부 단편화를 완전히 해결하기보다는 부분적으로 완화하는 방식이라고 볼 수 있다.
메모리 배치 전략의 필요성
(Need for Memory Placement Strategy)
지금까지 살펴본 것처럼 가변 분할 방식(Variable Partitioning)에서는 프로세스(Process)의 생성과 종료가 반복되면서 빈 공간(Free Space)이 계속 생긴다.
이때 빈 공간을 어떻게 활용하느냐에 따라 메모리 사용 효율(Memory Utilization)이 달라진다. 즉, 프로세스를 어떤 공간에 배치할 것인가에 따라 결과가 달라질 수 있다.
따라서 가변 분할 방식에서는 프로세스를 어떤 기준으로 배치할 것인지가 중요한 문제가 된다.
메모리 배치 전략과 단편화 (Memory Placement Strategy and Fragmentation)
가변 분할 방식(Variable Partitioning)에서는 여러 개의 빈 공간(Free Space) 중에서 프로세스(Process)를 어디에 배치할 것인지 선택해야 한다. 이때 어떤 기준으로 공간을 선택하느냐에 따라 메모리 사용 효율(Memory Utilization)이나 성능(Performance)이 달라질 수 있다.
그래서 다양한 메모리 배치 전략(Memory Placement Strategy)이 존재한다. 대표적인 방식으로는 최초 적합(First Fit), 최적 적합(Best Fit), 최악 적합(Worst Fit)이 있다.
먼저 최초 적합(First Fit) 방식은 메모리의 앞쪽부터 순차적으로 빈 공간을 탐색하다가, 조건을 만족하는 첫 번째 공간에 프로세스를 바로 배치하는 방식이다. 이 방식은 필요한 공간을 찾는 즉시 할당하기 때문에 탐색을 빠르게 끝낼 수 있고, 할당 속도(Allocation Speed)가 빠르다는 특징이 있다.
다만 뒤쪽에 더 적절한 공간이 있더라도 그 공간은 고려하지 않는다. 앞쪽에서 조건을 만족하는 공간을 발견하면 바로 배치하기 때문에, 메모리 앞부분에 작은 빈 공간들이 남을 수 있다는 단점이 있다.
그림을 통해 최초 적합 방식이 어떻게 동작하는지 살펴보자. 현재 13KB 크기의 프로세스가 들어온 상황이라고 가정한다. 이때 메모리의 앞쪽부터 빈 공간을 탐색한다.
가장 먼저 발견된 빈 공간이 16KB라면, 이 공간은 요구 크기인 13KB를 만족한다. 따라서 프로세스는 이 위치에 바로 배치된다. 그 결과 16KB 공간 중 일부만 사용되고, 나머지 공간은 그대로 남게 된다.
이처럼 최초 적합 방식은 조건을 만족하는 첫 번째 공간에 바로 할당하는 방식이다. 따라서 탐색 속도는 빠르지만, 메모리 앞쪽에 작은 빈 공간이 계속 남을 수 있다는 특징을 가진다.
최적 적합 방식 (Best Fit)
이번에는 최적 적합(Best Fit) 방식에 대해 살펴본다. 최적 적합 방식은 메모리(Memory)에 있는 모든 빈 공간(Free Space)을 탐색한 뒤, 요구 크기에 가장 근접한 공간을 선택하는 방식이다. 즉, 프로세스(Process)가 들어갈 수 있는 공간 중에서 가장 작은 적합 공간을 선택한다.
이 방식은 불필요하게 큰 공간을 사용하는 것을 줄일 수 있기 때문에, 공간 낭비를 줄일 수 있다는 특징이 있다.
예를 들어 13KB 크기의 프로세스가 들어온다고 가정해보자. 이때 16KB 공간이 있더라도, 더 근접한 14KB 공간이 있다면 14KB 공간이 선택된다.
다만 최적 적합 방식은 모든 빈 공간을 탐색해야 하기 때문에 탐색 비용(Search Cost)이 크다. 또한 결과적으로 작은 크기의 빈 공간들이 많이 남게 되는 문제가 발생할 수 있다.
그림에서는 현재 13KB 크기의 프로세스가 들어온 상황을 보여준다. 이때 메모리에 존재하는 모든 빈 공간을 확인한다. 그 결과 16KB, 14KB, 5KB, 30KB와 같은 공간들 중에서 요구 크기와 가장 근접한 14KB 공간이 선택된다.
따라서 최초 적합(First Fit) 방식처럼 16KB 공간을 사용하는 것이 아니라, 더 작은 14KB 공간에 배치가 이루어진다.
이처럼 최적 적합 방식은 가장 알맞은 크기의 공간을 선택하기 때문에 공간 낭비를 줄일 수 있다. 하지만 모든 빈 공간을 탐색해야 하므로 탐색 비용이 크고, 작은 크기의 빈 공간이 점점 남게 되는 문제가 발생할 수 있다.
최악 적합 방식 (Worst Fit)
이번에는 최악 적합(Worst Fit) 방식에 대해 살펴본다. 최악 적합 방식은 메모리(Memory)에 있는 빈 공간(Free Space) 중에서 가장 큰 공간을 선택해 프로세스(Process)를 배치하는 방식이다.
예를 들어 13KB 크기의 프로세스가 들어온다고 가정해보자. 이때 14KB나 16KB 공간이 있더라도 해당 공간을 선택하지 않고, 가장 큰 30KB 공간에 프로세스를 배치한다.
이 방식은 큰 공간을 사용한 뒤에도 비교적 큰 여유 공간을 남길 수 있다는 특징이 있다. 하지만 큰 공간이 계속 분할되면서 결과적으로 작은 빈 공간들이 많이 생성될 수 있다는 문제도 있다.
그림에서는 현재 13KB 크기의 프로세스가 들어온 상황을 보여준다. 이때 메모리에 있는 빈 공간들 중 가장 큰 30KB 공간을 선택한다. 16KB나 14KB처럼 더 적절해 보이는 공간이 있더라도 사용하지 않고, 가장 큰 공간에 배치가 이루어진다.
이처럼 최악 적합 방식은 가장 큰 빈 공간을 먼저 사용하면서 상대적으로 큰 여유 공간을 유지하려는 특징이 있다. 그러나 그 큰 공간이 분할되면서 결과적으로 작은 크기의 빈 공간들이 많이 생성될 수 있다.
연속 메모리 할당 방식의 한계 (Limitations of Contiguous Memory Allocation)
지금까지 살펴본 것처럼 연속 메모리 할당 방식(Contiguous Memory Allocation)에서는 메모리(Memory)를 사용하는 과정에서 여러 개의 작은 공간으로 나뉘는 단편화(Fragmentation)가 발생할 수 있다.
이렇게 공간이 나뉘어 있으면 전체적으로는 여유 공간이 충분하더라도, 연속된 공간이 부족해서 메모리를 사용할 수 없는 경우가 생긴다. 그 결과 메모리 활용 효율(Memory Utilization)이 떨어지게 된다.
연속 메모리 할당 방식에서 단편화는 두 가지 형태로 나타난다. 먼저 내부 단편화(Internal Fragmentation)는 할당된 공간 내부에서 사용되지 않고 남는 공간이 발생하는 경우이다. 반면 외부 단편화(External Fragmentation)는 빈 공간이 여러 조각으로 나뉘어 흩어져 존재하는 경우이다.
이처럼 내부 단편화와 외부 단편화가 발생하면, 사용할 수 있는 메모리 공간이 충분하더라도 연속된 공간을 확보하기 어려워질 수 있다. 따라서 경우에 따라서는 메모리를 다시 정리해주는 작업이 필요하다. 하지만 이 과정에서도 추가적인 비용(Cost)이 발생한다.
결국 이러한 구조적인 한계를 해결하기 위해서는 연속된 공간에만 의존하지 않는 방식이 필요하다. 다음 시간에는 비연속 메모리 할당 방식(Non-Contiguous Memory Allocation)에 대해 알아볼 예정이다.
3️⃣ Non-contiguous memory allocation method
비연속 메모리 할당 방식 (Non-Contiguous Memory Allocation)
지금부터는 비연속 메모리 할당 방식(Non-Contiguous Memory Allocation)이 어떻게 이루어지는지 살펴본다.
앞서 연속 메모리 할당 방식(Contiguous Memory Allocation)의 구조와 한계를 공부했다면, 이번에는 그 문제를 해결하기 위한 비연속 메모리 할당 방식이 어떻게 작동하는지 알아볼 예정이다.
비연속 메모리 할당 방식은 프로세스(Process)를 하나의 연속된 공간에 배치하는 것이 아니라, 여러 개의 떨어진 공간에 나누어 배치하는 방식이다. 그리고 이렇게 분산된 공간을 하나의 연속된 공간처럼 사용할 수 있도록 관리한다.
결국 비연속 메모리 할당 방식은 연속 메모리 할당 방식에서 발생했던 단편화 문제(Fragmentation Problem)를 해결하기 위해 도입된 방식이라고 볼 수 있다.
페이징 (Paging)
그럼 비연속 메모리 할당 방식(Non-Contiguous Memory Allocation)이 어떤 방식으로 구현되는지 살펴보자. 먼저 페이징(Paging)의 개념에 대해 알아본다.
페이징은 프로세스(Process)를 하나의 연속된 공간으로 사용하는 것이 아니라, 동일한 크기의 페이지(Page) 단위로 나누어 관리하는 방식이다. 메모리(Memory) 역시 동일한 크기의 프레임(Frame) 단위로 나누어 사용한다.
이렇게 나누어진 페이지들은 서로 다른 프레임에 자유롭게 배치될 수 있다. 따라서 프로세스가 실제 메모리 안에서 여러 위치에 분산되어 있더라도, 하나의 연속된 공간처럼 사용할 수 있게 된다.
그림을 통해 페이징 방식이 어떻게 동작하는지 살펴보자. 프로세스는 페이지 단위로 나뉘고, 각 페이지는 메모리의 서로 다른 위치에 있는 프레임에 적재된다. 즉, 하나의 프로세스가 메모리 안에서 연속된 공간에 존재하는 것이 아니라 여러 위치에 나누어 배치되는 구조이다.
이때 MMU(Memory Management Unit)가 논리 주소(Logical Address)를 물리 주소(Physical Address)로 변환하면서 각 페이지가 어느 프레임에 있는지 연결해준다. 그래서 실제로는 프로세스가 메모리 곳곳에 분산되어 있더라도, 프로그램(Program)의 입장에서는 하나의 연속된 공간처럼 사용할 수 있다.
페이징에서의 주소 변환 (Address Translation in Paging)
페이징(Paging)에서 주소 변환(Address Translation)이 어떻게 이루어지는지 살펴보자.
논리 주소(Logical Address)는 페이지 번호(Page Number, p)와 변위(Offset, d)로 구성된다. 이때 페이지 번호를 기준으로 페이지 테이블(Page Table)을 확인하고, 해당 페이지가 저장된 프레임 번호(Frame Number, f)를 찾는다.
그다음 찾은 프레임 번호와 기존의 변위를 결합하여 최종적인 물리 주소(Physical Address)를 만든다. 즉, 논리 주소의 페이지 번호와 변위는 물리 주소의 프레임 번호와 변위로 변환되는 구조라고 이해할 수 있다.
이 과정에서 페이지 테이블은 주소 변환의 핵심 역할을 수행한다.
그림을 보면, 프로세스(Process)가 생성한 논리 주소는 페이지 번호(p)와 변위(d)로 나뉘어 MMU(Memory Management Unit)로 전달된다. MMU는 페이지 번호(p)를 이용해 페이지 테이블에서 해당 페이지가 위치한 프레임 번호(f)를 찾는다.
이후 찾은 프레임 번호(f)에 기존의 변위(d)를 그대로 결합하여 물리 주소를 생성한다. 이렇게 만들어진 물리 주소를 기준으로 실제 메모리(Physical Memory)의 해당 프레임 위치에 접근하게 된다.
즉, 페이지 번호는 프레임 번호로 변환되고, 변위는 그대로 유지된다. 이를 통해 최종적으로 실제 메모리 위치가 결정된다.
페이지 테이블 구조 (Page Table Structure)
이제 페이지 테이블(Page Table)이 어떤 구조로 되어 있는지 살펴보자. 페이지 테이블은 각 페이지(Page)가 어느 프레임(Frame)에 저장되어 있는지를 기록해두는 자료구조(Data Structure)이다.
이때 페이지 번호(Page Number)를 인덱스(Index)로 사용하여, 해당 페이지가 위치한 프레임 번호(Frame Number)를 빠르게 찾을 수 있다. 즉, 페이지 테이블의 각 항목은 페이지 번호에 대응하는 프레임 번호로 구성되어 있다고 볼 수 있다.
결국 페이지 테이블은 논리 주소(Logical Address)와 물리 주소(Physical Address)를 연결해주는 핵심적인 역할을 한다.
그림을 통해 페이지 테이블이 실제로 어떻게 매핑(Mapping)되는지 확인해보자. 왼쪽에는 논리 주소 공간(Logical Address Space)이 있고, 각 페이지 번호가 페이지 테이블의 인덱스로 사용된다.
예를 들어 논리 주소의 페이지 번호가 2라고 하면, 페이지 테이블의 2번 인덱스를 조회한다. 그 결과 해당 페이지가 프레임 번호 4에 저장되어 있다는 것을 확인할 수 있다. 따라서 이 데이터는 오른쪽의 물리 메모리(Physical Memory)에서 4번 프레임 위치에 존재하게 된다.
이처럼 페이지 번호를 기준으로 페이지 테이블을 조회하면, 해당 데이터가 저장된 실제 위치를 찾아 접근할 수 있다. 즉, 페이지 번호를 알고 있다면 해당 데이터가 저장된 물리 메모리 위치를 빠르게 찾을 수 있는 구조라고 이해하면 된다.
페이징의 장점과 한계 (Advantages and Limitations of Paging)
이제 페이징(Paging)을 왜 사용하는지 생각해보자. 페이징의 가장 큰 특징은 연속된 메모리 공간(Contiguous Memory Space)이 없어도 프로세스(Process)를 실행할 수 있다는 점이다.
즉, 메모리(Memory)의 여러 위치에 흩어져 있는 공간을 묶어서 사용하는 것이 가능하다. 따라서 연속 메모리 할당 방식(Contiguous Memory Allocation)에서 문제가 되었던 외부 단편화(External Fragmentation)는 사실상 발생하지 않는다.
그 결과 메모리를 더 유연하게 사용할 수 있고, 전체적인 메모리 활용 효율(Memory Utilization)도 좋아진다. 또한 메모리를 동일한 크기의 단위로 나누어 관리하기 때문에 구조가 단순해지고 관리도 쉬워진다는 특징이 있다.
지금까지 페이징의 장점을 살펴보았다면, 이번에는 한계를 살펴보자. 페이징 방식에서도 메모리를 페이지(Page) 단위로 나누어 관리하기 때문에, 페이지 크기보다 작은 데이터가 들어가면 일부 공간이 남을 수 있다. 따라서 단편화 문제가 완전히 사라지는 것은 아니며, 내부 단편화(Internal Fragmentation)는 여전히 발생할 수 있다.
또한 페이지 테이블(Page Table)을 따로 관리해야 하므로 추가적인 메모리 공간이 필요하다. 주소 변환(Address Translation) 과정에서도 페이지 테이블을 한 번 더 거쳐야 하기 때문에 추가적인 연산이 발생한다.
그 결과 메모리 접근 속도(Memory Access Speed)가 다소 느려질 수 있다는 한계도 존재한다.
세그멘테이션 (Segmentation)
이번에는 세그멘테이션(Segmentation) 기법을 살펴본다. 페이징(Paging)이 메모리를 일정한 크기로 나누는 방식이었다면, 세그멘테이션은 프로그램(Program)을 의미 단위로 나누는 방식이다.
예를 들어 코드 영역(Code Segment), 데이터 영역(Data Segment), 스택 영역(Stack Segment)처럼 프로그램의 논리적인 구조(Logical Structure)를 기준으로 나눈다. 따라서 각 세그먼트(Segment)는 서로 다른 크기를 가질 수 있다. 이처럼 프로그램의 구조를 그대로 반영한다는 점이 세그멘테이션의 특징이다.
그림을 보면 세그멘테이션은 페이징과 접근 방식이 다르다는 것을 확인할 수 있다. 페이징은 동일한 크기로 나누어 배치하는 방식이지만, 세그멘테이션에서는 각 영역의 크기가 서로 다르게 유지된 상태로 메모리에 배치된다.
즉, 프로그램을 나누는 기준이 크기(Size)가 아니라 의미(Meaning)라는 점이 핵심이다. 코드, 데이터, 스택처럼 서로 다른 역할을 하는 영역들이 각각 독립적으로 메모리에 배치된다.
이때도 실제 메모리(Physical Memory)에서는 세그먼트들이 분산되어 저장될 수 있지만, 프로그램 입장에서는 하나의 논리적인 구조로 이어져 있는 것처럼 보이게 된다.
세그멘테이션에서의 주소 변환 (Address Translation in Segmentation)
이제 세그멘테이션(Segmentation)에서 주소가 어떻게 변환되는지 살펴본다. 세그멘테이션은 페이징(Paging)과 구조는 비슷하지만, 주소를 나누는 기준이 다르다는 점이 중요하다.
세그멘테이션에서 논리 주소(Logical Address)는 세그먼트 번호(Segment Number)와 변위(Offset)로 구성된다. 먼저 세그먼트 번호를 이용해 세그먼트 테이블(Segment Table)에서 해당 세그먼트의 시작 위치(Base)와 길이(Limit)를 확인한다.
이때 변위가 허용된 범위, 즉 세그먼트의 길이를 넘지 않았는지 검사한다. 문제가 없다면 세그먼트의 시작 주소(Base Address)에 변위(Offset)를 더해 최종적인 물리 주소(Physical Address)를 생성한다.
즉, 페이징이 페이지 번호(Page Number)를 프레임 번호(Frame Number)로 변환하는 것이 중심이었다면, 세그멘테이션은 먼저 범위 검사(Limit Check)를 수행한 뒤 실제 위치를 계산하는 방식이라고 이해할 수 있다.
그림을 보면 세그멘테이션에서 주소 변환이 어떤 순서로 이루어지는지 더 명확하게 확인할 수 있다. 왼쪽 상단의 가상 주소(Virtual Address), 즉 논리 주소는 세그먼트 번호(Segment)와 변위(Offset)로 구성되어 있다.
MMU(Memory Management Unit)는 세그먼트 번호를 이용해 세그먼트 테이블에서 해당 세그먼트의 정보를 찾는다. 이때 중요한 점은 변위가 허용된 범위 안에 있는지 Yes/No로 검사한다는 것이다.
즉, 변위 값은 경계값(Limit Value)보다 작아야 정상적인 접근이 가능하다. 만약 조건을 만족하지 못하면 트랩(Trap)이 발생하고, 주소 오류(Address Error)로 처리된다.
반대로 조건을 만족하면 세그먼트의 시작 주소(address)에 변위(Offset)를 더해 최종적인 물리 주소를 생성한다. 정리하면, 세그멘테이션에서는 MMU가 먼저 변위가 허용 범위 안에 있는지 검사하고, 그다음 기준 주소(Base Address)에 변위를 더해 물리 주소를 구성한다.
세그멘테이션의 특징 (Characteristics of Segmentation)
세그멘테이션(Segmentation)의 특징을 정리해보면, 가장 큰 특징은 프로그램(Program)의 논리 구조(Logical Structure)를 그대로 반영해서 메모리(Memory)를 관리할 수 있다는 점이다.
따라서 코드(Code), 데이터(Data), 스택(Stack)과 같은 단위를 각각 따로 관리할 수 있다. 이를 통해 메모리 보호(Memory Protection)나 공유(Sharing)가 더 유연하게 이루어질 수 있다.
또한 세그먼트(Segment)마다 크기가 다르기 때문에, 프로그램의 구조에 맞게 메모리를 사용할 수 있다.
다만 각 세그먼트는 메모리의 연속된 공간에 배치되어야 한다. 따라서 세그멘테이션 방식에서도 외부 단편화(External Fragmentation)가 발생할 수 있다는 한계가 있다.
페이징과 세그멘테이션 비교 (Paging vs Segmentation)
이제 페이징(Paging)과 세그멘테이션(Segmentation)을 함께 놓고 보면, 두 방식의 차이가 더 명확하게 보인다.
먼저 페이징은 메모리(Memory)를 일정한 크기로 나누어 단순하게 관리하는 방식이다. 반면 세그멘테이션은 프로그램(Program)의 의미 단위로 나누어, 프로그램의 구조를 그대로 반영하는 방식이다.
페이징은 구조가 단순하고 관리가 쉽다는 장점이 있다. 하지만 페이지(Page) 단위로 메모리를 나누기 때문에, 일부 공간이 사용되지 않고 남는 내부 단편화(Internal Fragmentation)가 발생할 수 있다.
세그멘테이션은 프로그램의 논리 구조(Logical Structure)를 잘 반영하고, 보호(Protection)나 공유(Sharing)를 유연하게 처리할 수 있다는 장점이 있다. 하지만 각 세그먼트(Segment)가 연속된 공간에 배치되어야 하기 때문에 외부 단편화(External Fragmentation)가 발생할 수 있다.
주소 변환(Address Translation) 방식에서도 차이가 있다. 페이징은 페이지 번호(Page Number)를 프레임 번호(Frame Number)로 변환하는 방식이라면, 세그멘테이션은 세그먼트의 시작 위치(Base)에 변위(Offset)를 더해 실제 위치를 계산하는 방식이다.
이처럼 두 방식을 비교해보면, 각각의 장단점이 분명하게 드러난다.



