코드(Code) 영역
: 프로그램의 실행 코드가 저장되는 영역입니다. 이 영역은 프로그램의 명령어들이 메모리에 로드되고 실행되는 곳입니다.
데이터(Data) 영역
: 전역 변수(Global variables)와 정적 변수(Static variables)가 저장되는 영역입니다. 이 영역은 프로그램이 시작될 때 초기화되며, 프로그램의 수명 동안 유지됩니다.
힙(Heap) 영역
: 동적으로 할당되는 메모리가 저장되는 영역입니다. 힙 영역은 프로그래머가 직접 제어할 수 있는 메모리 공간으로, 메모리가 자동 해제되지 않아 직접 메모리 해제를 하거나 GC의 도움을 받아 메모리 해제를 해주어야 한다.(ex-object type (String))
스택(Stack) 영역
: 지역 변수(Local variables)와 함수의 매개변수(Parameter)가 저장되는 영역입니다. (원시 타입이 저장되는 영역, Heap 영역에 생성된 object type에 데이터의 참조값이 저장)
함수가 호출될 때마다 스택에 새로운 프레임(Frame)이 생성되며, 함수가 종료되면 해당 프레임이 제거됩니다. 스택은 후입선출(LIFO, Last-In-First-Out) 구조를 가지고 있습니다.
메모리 구조에서 CPU에서 가까운 순서로는 다음과 같은 순서로 나열할 수 있습니다:
레지스터(Register)
: 레지스터는 CPU 내부에 위치하며, 가장 빠른 접근 시간을 가지는 메모리입니다. 레지스터는 CPU 명령어를 실행하거나 데이터를 일시적으로 저장하는 데 사용됩니다.
캐시(Cache)
: 캐시는 CPU와 주 메모리(메인 메모리) 사이에 위치하며, CPU가 자주 사용하는 데이터나 명령어를 저장하는 데 사용됩니다. 캐시는 주 메모리에 비해 더 빠른 접근 시간을 가지므로 CPU의 성능을 향상시키는 역할을 합니다.
주 메모리(Main Memory)
: 주 메모리는 프로세스에 할당되는 메모리로서, 코드, 데이터, 힙, 스택 등의 영역을 포함합니다. 주 메모리는 CPU가 직접 접근할 수 있는 메모리이지만, 레지스터와 캐시에 비해 상대적으로 느린 접근 시간을 가집니다.
보조 저장장치(Secondary Storage)
: 보조 저장장치는 하드 디스크 드라이브(Hard Disk Drive, HDD)나 솔리드 스테이트 드라이브(Solid State Drive, SSD)와 같은 장치를 의미합니다. 보조 저장장치는 주 메모리보다 용량은 크지만, 상대적으로 접근 시간이 더 느립니다. 데이터의 영구 저장과 프로그램 실행 파일의 보관에 사용됩니다.
이렇게 CPU에서 가까운 순서로 메모리 구조를 나열하면, 접근 시간이 점점 느려지지만 저장 용량은 늘어남에 따라 데이터의 지속성이 보장됩니다.
CPU-RAM 사이의 상호 동작을 저수준에서 설명하면 다음과 같습니다:
프로그램이 CPU로부터 실행되면, CPU는 해당 명령어를 가져와서 실행합니다.
CPU는 배열의 첫 번째 원소인 정수를 메모리 주소로부터 RAM으로부터 요청합니다.
메모리 컨트롤러는 CPU의 요청을 받고, 메모리 계층 구조에서 가장 빠른 계층에서 데이터를 찾습니다.
만약 해당 데이터가 CPU 캐시에 이미 존재한다면, CPU는 캐시로부터 데이터를 읽어옵니다. 그렇지 않다면, 메모리 컨트롤러는 RAM에서 해당 데이터를 읽어와 CPU로 전송합니다.
CPU는 읽어온 데이터를 레지스터에 저장합니다.
CPU는 다음 배열 원소를 읽기 위해 반복문을 계속 실행하고, 이 과정을 배열의 모든 원소에 대해 반복합니다.
모든 배열 원소에 대한 작업이 완료되면, CPU는 결과를 저장할 레지스터 또는 메모리 위치에 저장합니다.
결과를 저장한 레지스터나 메모리 위치로부터 CPU는 결과를 읽어올 수 있습니다.
이러한 과정에서 CPU와 RAM 사이에서는 데이터의 읽기와 쓰기가 발생하며, 메모리 계층 구조에서 데이터의 캐싱과 전송이 일어납니다. 이를 통해 CPU는 RAM에 저장된 배열의 각 정수를 읽어와 연산을 수행하고, 최종적인 합계를 구합니다.
페이지(Page)와 세그멘테이션(Segmentation)은 가상 메모리(Virtual Memory) 시스템에서 사용되는 메모리 관리 기법입니다.
페이지(Page):
페이지는 일정한 크기로 분할된 고정 크기의 블록으로 메모리를 관리하는 기법입니다.
가상 주소 공간과 물리적인 메모리 공간을 동일한 크기의 페이지로 나누어 매핑하기 때문에 내부 단편화가 일어날 수 있습니다. 예를 들어, 프로세스의 크기가 10 페이지이고 페이지 크기가 4KB인 경우, 마지막 페이지에는 4KB보다 작은 내용만 포함될 수 있으며 이로 인해 내부 단편화가 발생합니다.
세그멘테이션(Segmentation):
세그멘테이션은 프로세스를 논리적인 단위인 세그먼트(Segment)로 분할하여 관리하는 기법입니다.
세그먼트는 프로세스의 논리적인 단위로서 코드, 데이터, 스택 등과 같은 논리적인 부분으로 구성됩니다.
각 세그먼트는 서로 다른 크기를 가질 수 있기 때문에 외부 단편화가 발생할 수 있지만 메모리를 효율적으로 관리하고 가상 메모리를 지원하는 데 매우 유용합니다.
외부 단편화(External Fragmentation)와 내부 단편화(Internal Fragmentation)는 메모리 관리에서 발생하는 두 가지 현상을 나타냅니다.
외부 단편화(External Fragmentation):
외부 단편화는 메모리 공간 중에 프로세스들이 사용할 수 없는 작은 조각들이 흩어져 있는 상태를 말합니다. 주로 동적 메모리 할당이 반복되거나 메모리 공간의 할당과 해제가 빈번하게 일어날 때 발생합니다.
외부 단편화는 가용한 메모리 공간이 충분하지만 연속된 큰 블록으로 사용할 수 없어서 실제로는 메모리가 낭비되는 상황입니다.
외부 단편화를 해소하기 위해 메모리 관리 기법 중 하나인 메모리 압축(Memory Compaction)이 사용될 수 있습니다. 메모리 압축은 프로세스들을 한쪽으로 모으거나 조각들을 합치는 등의 방법을 사용하여 외부 단편화를 해소합니다.
내부 단편화(Internal Fragmentation):
내부 단편화는 프로세스가 할당받은 메모리 공간보다 실제로 필요한 공간이 더 작은 상황을 말합니다. 주로 고정 크기의 메모리 블록을 사용하는 메모리 관리 기법에서 발생합니다.
내부 단편화는 프로세스가 사용할 수 있는 메모리 공간이 일부 낭비되는 상황입니다.
내부 단편화를 해소하기 위해 가변 크기 할당 방식을 사용할 수 있습니다. 가변 크기 할당 방식은 프로세스의 실제 크기에 맞게 메모리를 할당하므로 내부 단편화가 발생하지 않습니다.
First Fit, Best Fit, Worst Fit은 메모리 할당 알고리즘 중에서 자주 사용되는 세 가지 방식입니다. 이 알고리즘들은 프로세스가 메모리에 할당될 때 사용 가능한 공간을 찾는 방법에 차이가 있습니다.
First Fit
: First Fit은 메모리에서 가장 먼저 발견된 충분한 크기의 공간에 프로세스를 할당하는 방식입니다.
메모리를 처음부터 순차적으로 검색하면서, 처음으로 발견된 충분한 크기의 공간에 프로세스를 할당합니다.
First Fit은 빠르게 실행되며, 메모리 내의 공간을 최대한 빨리 활용할 수 있습니다.
하지만 후에 발생할 수 있는 외부 단편화 문제가 있을 수 있습니다.
Best Fit
: Best Fit은 가장 적합한 크기의 공간을 찾아서 프로세스를 할당하는 방식입니다.
메모리를 순차적으로 검색하면서, 가장 작은 크기의 충분한 공간을 찾습니다.
찾은 공간 중에서 가장 작은 크기의 공간을 사용하여 프로세스를 할당합니다.
Best Fit은 외부 단편화를 최소화하는 장점이 있지만, 검색 과정이 좀 더 오래 걸릴 수 있습니다.
Worst Fit
: Worst Fit은 가장 큰 공간을 찾아서 프로세스를 할당하는 방식입니다.
메모리를 순차적으로 검색하면서, 가장 큰 크기의 충분한 공간을 찾습니다.
찾은 공간 중에서 가장 큰 크기의 공간을 사용하여 프로세스를 할당합니다.
Worst Fit은 외부 단편화를 최소화하는 장점이 있으며, 큰 프로세스를 처리하기에 유리합니다.
하지만 작은 공간이 남게되어 내부 단편화가 발생할 수 있습니다.
각 알고리즘은 자신의 장단점을 가지고 있으며, 메모리 할당 시 어떤 알고리즘을 사용할지는 메모리 사용 패턴이나 프로세스의 크기와 다른 요구사항에 따라 달라집니다. 성능과 메모리 활용의 균형을 고려하여 적절한 알고리즘을 선택해야 합니다.
FIFO (First-In, First-Out)
:FIFO 알고리즘은 가장 간단한 페이지 교체 알고리즘으로, 가장 먼저 들어온 페이지를 교체하는 방식입니다.
페이지의 도착 순서를 기준으로 페이지를 큐에 유지하고, 가장 먼저 도착한 페이지를 교체합니다.
FIFO 알고리즘은 구현이 간단하지만, 페이지의 접근 패턴에 따라 페이지 부재가 더 자주 발생하거나 교체 비용이 높아질 수 있습니다.
(FIFO 알고리즘은 페이지 참조 패턴이 지역성이 없는 경우에 사용될 수 있습니다. 모든 페이지를 동일한 확률로 접근하는 경우에는 FIFO 알고리즘이 적합합니다.)
LRU (Least Recently Used)
: LRU 알고리즘은 페이지 중 가장 오랫동안 참조되지 않은 페이지를 교체하는 방식입니다.
페이지의 참조 시간을 기록하고, 가장 오래 전에 참조된 페이지를 교체합니다.
LRU 알고리즘은 페이지 교체의 최적해에 가까우며, 최근에 사용되지 않은 페이지를 교체하여 지역성(Locality)을 고려합니다.
하지만 구현이 복잡하고, 각 페이지에 대한 참조 시간을 추적해야 하므로 추가적인 비용이 발생할 수 있습니다.
(LRU 알고리즘은 페이지 참조 패턴에 기반하여 페이지를 교체하므로, 최근에 사용되지 않은 페이지를 교체하는데 효과적입니다. 캐시 메모리와 같이 자주 사용되는 데이터를 관리하는데 적합합니다.)
LFU (Least Frequently Used)
: LFU 알고리즘은 페이지 중 가장 적게 참조된 페이지를 교체하는 방식입니다.
페이지의 참조 횟수를 기록하고, 가장 적게 참조된 페이지를 교체합니다.
LFU 알고리즘은 특정 페이지가 처음에 자주 참조되다가 후에 더 이상 사용되지 않을 경우에 효과적입니다.
하지만 페이지 참조 횟수를 추적하기 위해 추가적인 비용이 발생하고, 페이지 교체 정책이 지나치게 보수적일 수 있습니다.
(LFU 알고리즘은 특정 페이지가 초기에 자주 참조되다가 후에 더 이상 사용되지 않을 경우에 효과적입니다. 예를 들어, 네트워크 패킷 처리에서 사용되는 경우 특정 패킷이 일시적으로 많은 참조를 받다가 이후에 참조되지 않는 경우에 적용할 수 있습니다.)
Optimal
: Optimal 알고리즘은 가장 이상적인 페이지 교체 알고리즘으로, 앞으로 가장 오랫동안 사용되지 않을 페이지를 교체합니다.
모든 페이지에 대한 앞으로의 참조 패턴을 미리 알고 있어야 하며, 가장 최적의 페이지를 교체합니다.
(Optimal 알고리즘은 모든 페이지 참조 패턴을 미리 알고 있어야하므로, 시뮬레이션 및 성능 평가에 주로 사용됩니다. 이론적으로는 최적의 페이지 교체를 제공하지만, 실제로는 구현하기 어렵고 비용이 많이 듭니다.)