
과거에는 메모리가 값비싼 저장장치였기에 개인용 컴퓨터에 큰 메모리를 사용할 수 없었습니다.
과거에는 작은 메모리로 큰 프로그램을 어떻게 작동할 것인가가 문제였습니다. 1MB 메모리의 컴퓨터에서 10MB의 프로그램을 어떻게 실행할 수 있을까요? 요리사가 도마의 크기보다 더 큰 고깃덩어리를 손질하는 경우를 생각해 봅시다. 결론은 간단합니다. 고깃덩어리를 잘라서 각 덩어리를 손질하면 됩니다.
프로그램의 크기가 실제 메모리(물리 메모리)보타 클 때 전체 프로그램을 메모리에 가져오는 대신 적당한 크기로 잘라서 가져오는 기법을 메모리 오버레이(memory overlay)라고 합니다. 메모리 오버레이는 하나의 메모리에 여러 프로그램을 겹겹이 쌓아놓고 실행하는 것을 말합니다.
메모리 오버레이는 프로그램을 몇 개의 모듈로 나누고 필요할 때마다 모듈을 메모리에 가져와 사용합니다.
메모리 오버레이에서 어떤 모듈을 가져오거나 내보낼지는 CPU 레지스터 중 하나인 프로그램 카운터(PC)가 결정합니다. 프로그램 카운터는 앞으로 실행할 명령어의 위치를 가리키는 레지스터로, 해당 모듈이 메모리에 없으면 메모리 관리자에게 요청하여 가져오게 합니다.
메모리 오버레이에는 다음과 같은 중요한 의미가 있습니다.
메모리에 모듈 B를 가져올 때 먼저 메모리에 올라온 모듈 A를 어딘가에 보관해야 합니다. 이처럼 메모리가 모자라서 쫓겨난 프로세스를 저장장치의 특별한 공간에 모아두는데 이러한 영역을 스왑 영역(swap area)이라고 합니다. 스왑 영역에서 메모리로 데이터를 가져오는 작업을 스왑인(swap in), 메모리에서 스왑 영역으로 데이터를 내보내는 작업을 스왑아웃(swap out)이라고 합니다.
스왑 영역은 메모리 관리자가 관리합니다. 원래 하드디스크 같은 저장장치는 저장장치 관리자가 관리하지만, 스왑 영역은 메모리에서 쫓겨났다가 다시 돌아가는 데이터가 머무는 곳이기 때문에 저장장치는 장소만 빌려주고 메모리 관리자가 관리합니다.
스왑 영역의 크기가 메모리의 크기로 인식됩니다. 사용자는 실제 메모리와 스왑 영역의 크기를 합쳐서 전체 메모리의 크기로 인식하고 사용할 수 있습니다.
메모리를 어떤 크기로 나눌 것인가는 메모리 배치 정책에 해당합니다. 메모리에 여러 개의 프로세스를 배치하는 방법은 크게 가변 분할 방식(variable-size partitioning)과 고정 분할 방식(fixed-size partitioning)으로 나뉩니다.
가변 분할 방식 : 프로세스의 크기에 따라 메모리를 나눕니다.
고정 분할 방식 : 프로세스의 크기와 상관없이 메모리를 같은 크기로 나눕니다.
가변 분할 방식의 구현
프로세스가 차지하는 메모리 영역의 크기가 40KB에서 17KB까지 다 다릅니다. 가변 분할 방식에서는 한 프로세스가 메모리의 연속된 공간에 배치되기 때문에 연속 메모리 할당(contiguous memory allocation)이라고 합니다. 물리 메모리에서 하나의 프로세스에 해당하는 주소 공간이 연속적으로 이어지기 때문입니다.
고정 분할 방식의 구현
프로세스의 크기에 상관없이 20KB의 같은 크기로 나뉩니다. 따라서 큰 프로세스가 메모리에 올라오면 여러 조각으로 나뉘어 배치됩니다. 30KB의 프로세스 C도 20KB의 프로세스 과 10KB의 프로세스 로 나뉘는데 물리 메모리 내에는 프로세스 만 있습니다. 물리 메모리가 부족하기 때문에 프로세스 는 스왑 영역에 있습니다. 고정 분할 방식에서는 하나의 프로세스가 여러 개로 나뉘어 배치되기 때문에 비연속 메모리 할당(noncontiguous memory allocation)이라고 합니다.
가변 분할 방식의 장단점
장점은 프로세스를 한 덩어리로 처리하여 하나의 프로세스를 연속된 공간에 배치할 수 있다는 점입니다. 그러나 메모리 관리가 복잡하다는 단점이 있습니다. 이처럼 가변 분할 방식은 메모리 통합 등의 부가적인 작업이 필요하므로 메모리 관리가 복잡합니다.
고정 분할 방식의 장단점
고정 분할 방식은 메모리를 일정한 크기로 나누기 때문에 메모리 관리가 편하다는 장점이 있습니다. 단점은 하나의 프로세스가 여러 곳으로 나뉠 수 있다는 점입니다. 따라서 고정 분할 방식은 가변 분할 방식보다 메모리 관리 측면에서 유리합니다. 이러한 이유로 현대 운영체제에서는 메모리 관리는 기본적으로 고정 분할 방식을 사용합니다.
가변 분할 방식은 프로세스의 크기에 맞춰 메모리를 할당하는 방법으로 세그먼테이션(segmentation)메모리 관리 기법이라고 합니다. 프로세스를 하나의 연속된 주소로 다룬다는 장점이 있습니다.
가변 분할 방식의 가장 큰 문제점은 빈 공간의 크기가 일정하지 않다는 것입니다. 이렇게 발생한 작은 빈 공간을 단편화(fragmentation)라고 합니다. 가변 분할 방식에서 발생하는 단편화는 조각이 프로세스의 바깥쪽에 위치하기 때문에 외부 단편화(external fragmentation)라고 합니다.
가변 분할 방식에서는 외부 단편화로 인한 문제를 해결하기 위해 메모리 배치 방식(memory placement strategy)이나 조각 모음(defragmentation)을 사용합니다. 메모리 배치 방식은 작은 조각이 발생하지 않도록 프로세스를 배치하는 것이며, 조각 모음은 조각이 발생했을 때 작은 조각들을 모아서 하나의 큰 덩어리로 만드는 작업입니다. 가변 분할 방식에서 메모리 배치 방식은 선처리에 해당하고 조각 모음은 후처리에 해당합니다.
가변 분할 방식의 외부 단편화 문제를 해결하기 위한 대표적인 메모리 배치 방식으로 최초 배치(first fit), 최적 배치(best fit), 최악 배치(worst fit)가 있습니다.
최초 배치
단편화를 고려하지 않는 방식으로 프로세스를 메모리의 빈 공간에 배치할 때 메모리에서 적재 가능한 공간을 순서대로 찾다가 첫 번째로 발견한 공간에 프로세스를 배치하는 방법입니다.
최적 배치
메모리의 빈 공간을 모두 확인한 후 크기가 가장 비슷한 곳에 프로세스를 배치하는 방법입니다.
최악 배치
빈 공간을 모두 확인한 후 가장 큰 공간에 프로세스를 배치하는 것으로 최적 배치와 정반대되는 방법입니다.
작은 프로세스가 작업을 마치고 메모리에서 나가면 그 공간이 조각으로 남아 쓸모없어질 가능성이 큽니다. 이렇게 단편화가 발생하면 이미 배치된 프로세스를 옆으로 옮겨 빈 공간들을 하나의 큰 덩어리로 만들어야 하는데 이 작업이 바로 조각 모음(defragmentation)입니다. 조각 모음은 서로 떨어져 있는 여러 개의 빈 공간을 합치는 작업으로, 메모리 통합이라 부르기도 합니다.
고정 분할 방식을 사용하여 물리 메모리를 나누는 방식을 페이징(paging) 메모리 관리 기법이라고 합니다. 한 책에서 모든 페이지의 크기가 같기 때문에 페이징이라고 부르는 것입니다. 가변 분할 방식과 달리 고정 분할 방식은 프로세스의 크기에 상관없이 메모리를 같은 크기로 나누기 때문에 관리하기가 편합니다. 그래서 현대의 메모리 관리는 페이징을 기본으로 합니다.
고정 분할 방식도 단점이 있습니다. 일정하게 나뉜 메모리의 크기보다 작은 프로세스가 배치될 경우 낭비되는 공간이 생긴다는 점입니다. 이처럼 각 메모리 조각에 프로세스를 배치하고 공간이 남는 것을 내부 단편화(internal fragmentation)라고 합니다
고정 분할 방식에서는 내부 단편화로 해결하기 위해 조각 모음을 할 수 없고 남는 공간을 다른 프로세스에 배정할 수도 없습니다. 따라서 고정 분할 방식에서는 내부 단편화를 줄이기 위해 메모리를 어떤 크기로 나눌지 신중하게 결정해야 합니다.
| 구분 | 가변 분할 방식 | 고정 분할 방식 |
|---|---|---|
| 메모리 관리 기법 | 세그먼테이션 | 페이징 |
| 특징 | 연속 메모리 할당 | 비연속 메모리 할당 |
| 장점 | 프로세스를 한 덩어리로 관리 가능 | 메모리 관리가 편리함 |
| 단점 | 빈 공간의 관리가 어려움 | 프로세스가 분할되어 처리됨 |
| 단편화 | 외부 단편화 | 내부 단편화 |
가변 분할 방식의 단점인 외부 단편화를 완화하는 방법으로는 버디 시스템이 있습니다. 버디 시스템은 가변 분할 방식과 고정 분할 방식의 중간 구조입니다.
버디 시스템의 작동 방식은 다음과 같습니다.
버디 시스템에서 메모리에 프로세스를 배치할 때는 프로세스의 크기에 맞게 메모리를 1/2 크기로 잘라나갑니다.
효율적인 공간 관리 측면에서 보면 고정 분할 방식과 버디 시스템은 비슷한 수준입니다. 그러나 메모리 관리 측면에서는 공간을 1/2로 나누어가며 메모리를 배분하는 버디 시스템보다 모든 공간을 똑같은 크기로 나누는 고정 분할 방식이 단순하기 때문에 버디 시스템보다 고정 분할 방식을 많이 사용합니다.
C나 자바 언어로 작성한 소스코드는 컴파일 과정을 거쳐 목적 코드가 됩니다. 컴파일러는 고급 언어로 작성된 소스코드를 기계어로 번역하면서 여러 가지 작업을 수행합니다. 오류가 있는지 점검하고 최적화를 통해 필요 없는 변수와 코드를 삭제합니다. 이렇게 만들어진 기계어 코드가 목적 코드입니다. 기계어 코드라고 하지 않고 목적 코드라고 부르는 이유는 아직 처리해야 할 단계가 남아 있기 때문입니다.
여러 개의 소스코드 파일을 사용하여 하나의 실행 파일을 만드는 것을 다중 소스(multiple source)코드라 하고, 여러 개의 소스코드를 각각 컴파일하여 하나의 실행 파일로 만드는 것을 분할 컴파일이라고 합니다.
컴파일러는 변수를 사용할 때마다 사용 범위를 넘는지 점검합니다. 한편, 주소를 보면 시작 주소만 나타나 있고 크기가 없습니다. 변수의 종류마다 크기가 정해져 있기 때문에 변수의 시작 주소만 명시하면 그 크기는 자동으로 정해집니다.
기억해야 할 점은 변수가 메모리 주소의 또 다른 이름이라는 점입니다. 사실 기계어 입장에서는 변수를 알 필요가 없습니다. 기계어는 메모리 주소만 필요한데도 변수를 사용하는 중요한 이유는 프로그래머가 주소 값만으로는 그것이 어떤 주소인지 기억하기 어렵기 때문입니다.
프로그램의 변수도 같은 역할을 합니다. 사용자는 결국 메모리 주소를 사용하지만 주소만으로는 그게 어떤 주소인지 기억하기가 어렵기 때문에 각 주소에 대응하는 변수를 사용합니다. 이렇게 프로그래머가 변수를 사용하여 프로그램을 만들면 컴파일러는 모든 변수를 메모리 주소로 바꾸어 기계어로 된 실행 파일을 만듭니다. 컴파일러는 프로그래머가 만든 변수를 적당한 크기의 메모리 주소로 변환하여 기계어로 바꿉니다.
컴파일러에 의해 만들어진 주소는 논리 주소입니다. 앞의 예에서 char str = 'a';는 0번지에 만들어진다고 했는데 사실 주소 0번지는 운영체제가 차지하고 있습니다.