[운영체제] Memory - Address Space & Address Translation

전윤혁·2024년 9월 10일
0

OS

목록 보기
7/18

메모리 가상화 (Memory Virtualization)

이전 글까지 CPU Virtualization에 대한 내용들을 다뤘다. 이번 글부터는 Memory Virtualization, 즉 메모리 가상화에 대해 알아보자. 이전에 설명한 것과 같이, 메모리 가상화는 물리적 메모리와 독립적으로 각 프로세스가 자신의 독립된 주소 공간을 사용할 수 있도록 추상화하는 기술이다.


1. Address Space & Virtual Address (가상 주소)

먼저, 메모리 주소 공간을 시각적으로 살펴보며 메모리 가상화가 왜 필요한지, 이점이 무엇인지 알아보도록 하자.

1) Early System

image

초기 운영체제에서는 메모리에 단 하나의 프로세스만 적재할 수 있는 방식이었는데, 해당 방식은 메모리 자원을 비효율적으로 사용하는 문제를 가지고 있었다.

메모리는 운영체제 자체의 코드와 데이터뿐만 아니라 현재 실행 중인 프로그램의 코드와 데이터를 수용해야 하는데, 한 번에 하나의 프로그램만 실행할 수 있기 때문에 CPU와 메모리 자원의 활용도가 매우 낮았다.

2) Multiprogramming and Time Sharing

image

MultiprogrammingTime Sharing은 초기 시스템의 단일 프로세스 실행 문제를 해결하기 위해 도입된 개념이다.

  • Multiprogramming
    여러 프로세스를 동시에 메모리에 적재하여 CPU가 처리할 수 있도록 하는 방식이다. 이 방식에서는 각 프로세스가 메모리의 일부분을 차지하며, CPU는 각 프로세스를 번갈아 가며 실행한다.

  • Time Sharing
    CPU가 일정한 시간 단위(타임 슬라이스)로 각 프로세스에 할당되어 프로세스 간 전환이 빠르게 이루어지는 방식으로, 프로세스의 중단 시점의 상태를 저장한다. 이를 통해 사용자는 여러 작업이 동시에 진행되는 것처럼 느낄 수 있다.

그러나 이러한 방식에서 중요한 문제는 보호(protection)이다. 여러 프로세스가 같은 물리 메모리 공간을 공유하므로, 한 프로세스가 다른 프로세스의 메모리 공간에 잘못된 접근을 시도할 경우 시스템 오류나 보안 문제가 발생할 수 있다.

3) Address Space

image

위와 같은 문제를 해결하기 위해서는, 단순히 메모리를 하나의 긴 배열로 보는 시각을 바꿔야 했다. 이를 위해 고안된 것이 바로 Address Space 개념으로, 이는 운영체제가 물리 메모리를 추상화하여 각 프로세스마다 독립적인 메모리 공간을 제공하는 개념이다.

그림에서 메모리의 주소는 0KB ~ 16KB로 보이지만 이는 가상화된 주소로, 실제 Physical Memory (물리 주소) 주소는 이와 다르다.

이처럼 각 프로세스가 독립적인 주소 공간을 가지며 물리 주소와 상관없이 코드, 힙, 스택 등의 메모리 영역을 사용할 수 있게 하는 것이 바로 메모리 가상화(memory virtualization)인 것이다.


2. Address Translation

그렇다면 가상 주소에서 물리 주소로의 변환은 어떻게 일어나는걸까?

Address Translation(주소 변환)은 하드웨어가 가상 주소를 물리 주소로 변환하는 과정이다. 이전 CPU 가상화 파트에서, CPU를 프로세스 마음대로 쓰도록 내버려두지 않고 운영체제가 관리한다고 설명했던 부분을 기억하는가? 메모리도 이와 마찬가지로 운영체제에 의해 관리되므로, 운영체제는 주소 변환 과정에서 주요 역할을 수행한다.

가상 주소를 물리 주소로 변환해 주는 CPU의 구성 요소를 MMU(Memory Management Unit)이라고 하는데, 변환 정보는 논리적으로 저장되어 있는 것이 아니라 하드웨어에 저장되어 있다. 따라서 변환이 빠르게 진행될 수 있다.


3. Dynamic(Hardware-based) Relocation

OSTEP 책에서는 Dynamic(Hardware-based) Relocation 이라는 소제목으로 주소 변환을 설명한다. 직전에 변환 정보는 하드웨어에 저장되어 있다고 설명했는데, 왜 Dynamic일까?

Dynamic Relocation은 프로그램이 실행될 때, 운영체제(OS)가 해당 프로세스가 물리 메모리에서 어디에 로드되어야 할지를 결정하는 방식이다. 프로그램이 실행될 때, 운영체제는 실제 메모리의 어느 위치에 프로세스가 로드될 지 아래와 같이 결정한다.

physical address=virtual address+base\text{physical address} = \text{virtual address} + \text{base}

0virtual address<bounds0 \leq \text{virtual address} < \text{bounds}

위의 수식을 통해 base와 bounds가 각각 무엇을 의미하는지 감이 올 것이다. Base 레지스터는 프로세스가 로드된 물리 메모리의 시작 주소를 저장하고, Bounds 레지스터는 프로세스의 가상 주소 공간 크기를 정의한다.

따라서, 해당 방식을 Dynamic으로 표현하는 이유는 프로그램이 실행될 때 주소 변환이 발생하고, 프로그램이 실행 되는 중에도 현재 프로세스의 가상 주소를 다른 물리 주소로 변환(레지스터 값을 업데이트)할 수 있기 때문이다. 따라서 Hardware-based임과 동시에, Dynamic인 것이다.

✅ Base and Bounds Register

앞에서 설명한 Base 레지스터와 Bounds 레지스터를 사용한 주소 변환을 구체적인 예시와 함께 살펴보자.

위 그림에서 Base 레지스터는 물리 메모리의 32KB 지점을 가리키고 있고, Bounds 레지스터는 16KB 만큼의 주소 공간 크기를 정의하고 있다.

이 때, 가상 주소 128에 있는 명령어를 가져온다고 해보자.

image

32896=128+32KB(𝑏𝑎𝑠𝑒)32896 = 128 + 32KB(𝑏𝑎𝑠𝑒)

위와 같이 가상 주소를 물리 주소로 변환하여, 실제로 CPU는 물리 주소의 32896 위치에서 명령어를 가져와서 실행하게 되는 것이다.


4. OS Issues & Internal Fragmentation

Base와 Bound 방식을 구현하는 과정에서, 운영체제는 다음 세 가지 중요한 시점에 올바른 작업을 수행해야 한다.

  • 프로세스가 시작될 때
    물리 메모리에서 해당 프로세스의 주소 공간을 찾는 작업.

  • 프로세스가 종료될 때
    사용했던 메모리를 반환하여 다른 작업에 사용할 수 있도록 하는 작업.

  • Context Switch가 발생할 때
    현재 프로세스의 Base와 Bound 값을 (PCB에) 저장하고, 새로운 프로세스의 값을 불러오는 작업.

✅ Internal Fragmentation

프로세스에 할당되는 메모리 블록은 일반적으로 고정된 크기로 나누어진다. 프로세스가 요청한 메모리보다 큰 블록이 할당되면, 실제로 사용하지 않는 여분의 공간이 남게 되는데, 이것을 Internal Fragmentation (내부 단편화) 문제라고 한다.

image

위의 그림에서 볼 수 있듯이, 운영체제는 프로세스에게 16KB의 고정된 크기의 메모리를 할당했지만, 실제로 가상 주소의 6KB에서 14KB는 사용되지 않고 있고, 이는 고스란히 실제 메모리의 낭비로 이어진다. 이것이 Internal Fragmentation 문제인 것이다.

우리는 Base and Bounds 기법의 비효율성을 알게 되었다. 그렇다면 이를 해결할 수 있는 다른 방법이 있을까?


5. Segmentation

Segmentation은 메모리 관리 기법 중 하나로, 프로그램의 주소 공간을 논리적으로 구분된 여러 개의 세그먼트로 나누어 관리하는 방식이다.

Segmentation 기법을 통해 위의 내부 단편화 문제를 해결할 수 있다. 여기서 Segment는 단순히 주소 공간의 연속적인 부분을 의미하고, 논리적으로 구분된 영역을 나타낸다. 맞다! 코드, 힙, 스택, 데이터 부분으로 나누어진 각각의 메모리 영역이 바로 Segment이다.

Segmentation의 요점은, 각 Segment를 물리 메모리의 다른 위치에 배치하여 메모리의 낭비를 줄인다는 것이다. 아래 그림을 살펴보자.

각각의 Segment에 Base와 Bound를 설정한 결과, 위와 같이 Internal Fragmentation 문제가 해결된 것을 볼 수 있다. 만약 특정 Segment가 자신의 영역을 벗어난 메모리를 참조할 경우, Segmentation Fault가 발생한다.

그러면 메모리에서 Segment를 참조하는 과정은 어떻게 이루어질까? 아래의 그림을 살펴보자

가상 주소 01000001101000 번지의 예시이다. 해당 주소에서, 상위 2비트가 주소 공간 안에서 Segment를 식별하는 부분이다. 또한, Offset은 Segment Base로부터 얼마나 떨어져 있는지 표시하는 비트이다.

다시 설명하면, 위와 같이 Code, Stack, Heap 3개의 Segment로 메모리 공간을 나누는 경우, 최소 2개의 비트(22=42^2=4)가 필요하므로 2비트를 통해 Segment를 식별하고, Offset을 통해 해당 Segment의 Base로부터 얼마나 떨어진 위치의 주소와 매핑되어야 하는지 알 수 있다.

✅ External Fragmentation

Segmentation을 통해 Internal Fragmentation 문제를 해결하였지만, External fragmentation 문제가 발생할 수 있다. 아래 그림을 살펴보자.

Internal fragmentation은 연속적으로 할당된 메모리 내부에서 낭비가 발생하는 문제인 반면, External fragmentation은 할당된 메모리 외부에서 메모리 낭비가 발생하는 문제이다.

위의 Not Compacted 예시에서 16KB ~ 24KB, 32KB ~ 40KB, 48KB ~ 56KB 모두 할당되지 않은 메모리 영역이다. 이 경우, 만약 새로운 프로세스가 20KB의 메모리를 공간을 요구한다면 어떻게 해야 할까? 현재 비어있는 주소 공간의 총합은 20KB 이상이지만, 공간이 연속적이지 않으므로 메모리 할당이 어려워진다. 이것이 바로 할당된 메모리 외부에서 메모리 낭비가 발생하는, External fragmentation 문제이다.

따라서 External fragmentation을 방지하기 위해, Compacted 예시와 같이, 흩어져 있는 메모리를 모아 낭비 없이 할당하는 OS의 기능이 필요하다.


마치며

메모리 가상화와 주소 변환, Internal/External Fragmentation 문제에 대해 알아봤다. 다음 글에서는 Free Space Management에 대한 내용을 알아보며 External Fragmentation 문제를 해결해보자.

profile
전공/개발 지식 정리

0개의 댓글