1. Early Systems
과거의 컴퓨터 시스템은 메모리 측면에서 보면 사용자에게 많은 추상화(abstraction)를 제공하지 않았다. 실제 메모리는 Figure 13.1에서 볼 수 있는 것과 같은 형태였다. OS는 라이브러리 형태로 구현되어 메모리에 위치했으며(이 예제에서는 물리적 주소 0에서 시작), 현재 실행 중인 프로그램(프로세스)은 물리적 메모리에 위치하여(이 예제에서는 물리적 주소 64k에서 시작) 나머지 메모리를 사용했다. 여기에는 거의 추상화가 없었으며, 사용자는 OS에서 많은 것을 기대하지 않았다. 그 시절의 OS 개발자들에게는 삶이 정말 쉬웠던 시기였다.
2. Multiprogramming and Time Sharing
기계가 비싸기 때문에, 사람들은 기계를 더 효율적으로 공유하기 시작했다. 이로 인해 여러 개의 프로세스가 한 번에 실행 가능한 멀티프로그래밍(multiprogramming) 시대가 시작되었다. 이 경우, OS는 한 프로세스가 I/O를 수행하려고 할 때(예: 디스크에서 읽기/쓰기), 다른 프로세스로 전환하여 CPU의 효율적인 활용을 높였다. 이러한 효율성 증가는 기계 한 대가 수백만 달러에서 수백만 달러에 이르는 시대에서 특히 중요했다.
하지만 사람들은 기계로 더 많은 것을 요구하기 시작하면서 타임 쉐어링(time sharing) 시대가 시작되었다. 이것은 여러 사용자가 동시에 기계를 사용할 수 있게 되면서 상호작용성(interactivity)이 중요해졌다. 이를 구현하는 방법 중 하나는 하나의 프로세스를 실행한 후 그 상태를 디스크에 저장하고, 다른 프로세스의 상태를 불러와 실행하는 것이었다. 하지만 이 방법은 메모리 용량이 커질수록 매우 느려지는 문제가 있었다. 그래서 프로세스들을 메모리에 유지하면서 전환하는 방식이 좋은 방법이었고, 이를 효율적으로 구현할 수 있게 되었다.
타임 쉐어링이 보편화되면서, 운영 체제에 새로운 요구가 생겼다. 특히, 여러 프로그램이 메모리에 동시에 상주할 수 있도록 허용하는 것은 보호 문제가 중요해졌다. 따라서 한 프로세스가 다른 프로세스의 메모리를 읽거나 쓰지 못하도록 보호해야 했다.
3. The Address Space
사용자들을 고려해야 하기 때문에, 운영 체제는 물리적 메모리를 쉽게 사용할 수 있는 추상화(abstraction)를 만들어야 한다. 이 추상화를 주소 공간(address space)이라고 부르며, 실행 중인 프로그램이 시스템에서 보는 메모리를 의미한다. 이 추상화를 이해하는 것은 메모리를 가상화하는 방법을 이해하는 데 필수적이다.
프로세스의 주소 공간에는 실행 중인 프로그램의 모든 메모리 상태가 포함된다. 예를 들어, 프로그램의 코드(명령어)는 메모리의 어딘가에 존재해야 하므로 주소 공간에 있다. 프로그램이 실행되는 동안 스택(stack)을 사용하여 함수 호출 체인을 추적하고 지역 변수를 할당하며 루틴에서 매개변수와 반환값을 전달한다. 마지막으로, 힙(heap)은 malloc() 함수(C언어)나 객체 지향 언어(C++ 또는 Java)에서의 new와 같이 동적으로 할당하고 사용자가 관리하는 메모리에 사용된다. 물론, 정적으로 초기화된 변수 등 다른 요소들도 있지만, 일단은 코드, 스택, 힙이라는 세 가지 요소만 고려하자.
Figure 13.3의 예에서는 매우 작은 주소 공간(16KB)이 있다. 프로그램 코드는 주소 공간의 맨 위에 위치하며(이 예제에서는 0에서 시작하여 주소 공간의 첫 1K를 차지한다), 상대적으로 쉽게 메모리에 배치할 수 있으므로 프로그램이 실행될 때 추가적인 메모리가 필요하지 않다.
그 다음, 프로그램이 실행 중에 크기가 변할 수 있는 두 개의 주소 공간 영역인 힙(맨 위)과 스택(맨 아래)이 있다. 이들은 모두 확장될 수 있도록 반대쪽 끝에 위치시킨다. 힙은 따라서 코드 바로 다음(1KB에서 시작)에 위치하며 하향으로 성장하고(사용자가 malloc()을 통해 더 많은 메모리를 요청할 때), 스택은 16KB에서 시작하여 상향으로 성장한다(사용자가 프로시저 호출을 할 때). 그러나 스택과 힙의 위치 배치는 단지 관례에 불과하며, 다른 방식으로 주소 공간을 배열할 수도 있다(후에 다룰 것이다).
주소 공간을 묘사할 때, 우리가 묘사하는 것은 운영 체제가 실행 중인 프로그램에 제공하는 추상화이다. 실제로 프로그램은 물리적 주소 0에서 16KB까지에 있지 않다. 그 대신에 어딘가 임의의 물리적 주소에 로드되어 있다. Figure 13.2의 프로세스 A, B, C를 살펴보면 각 프로세스가 다른 주소에서 메모리에 로드된 것을 볼 수 있다. 이것이 문제이다. 운영 체제가 이것을 할 때, 우리는 메모리를 가상화한다고 말한다. 실행 중인 프로그램은 특정 주소(예: 0)에서 메모리에 로드된 것으로 생각하지만 실제로는 매우 다르게 동작한다.
예를 들어 Figure 13.2에서 프로세스 A가 가상 주소 0에서 로드를 시도하면, 운영 체제와 일부 하드웨어 지원을 동시에 사용하여 로드가 실제로 물리적 주소 320KB(프로세스 A가 메모리에 로드된 곳)로 이동하도록 해야 한다. 이것이 메모리 가상화의 핵심이며, 이것이 세계의 모든 현대 컴퓨터 시스템의 기초이다.
5. Goals
이러한 노트에서 우리가 다루는 운영 체제의 주요 역할은 메모리 가상화이다. 그러나 운영 체제는 메모리 가상화뿐만 아니라, 스타일까지도 구현해야 한다. 이를 위해 몇 가지 목표가 필요하다. 이전에도 이 목표들을 보았고(소개를 생각해보자), 다시 볼 것이지만 반복해 보면 좋다.
메모리 가상화의 주요 목표 중 하나는 투명성이다. 운영 체제는 가상 메모리를 실행 중인 프로그램이 볼 수 없도록 구현해야 한다. 프로그램은 메모리가 가상화되었다는 사실을 인식할 필요가 없으며, 오히려 자신만의 개인적인 물리적 메모리가 있는 것처럼 동작해야 한다. 그 뒤로, 운영 체제(및 하드웨어)는 많은 다른 작업들 간에 메모리를 다중화하는 모든 작업을 수행하므로, 이것을 구현하는 환상을 만들어낸다.
또 다른 메모리 가상화의 목표는 효율성이다. 운영 체제는 가상화를 가능한 한 효율적으로 만들어야 한다. 시간적인 측면에서(즉, 프로그램을 훨씬 느리게 만들지 않는다는 의미)과 공간적인 측면에서(즉, 가상화를 지원하는 데 필요한 구조체를 너무 많이 사용하지 않는다는 의미) 모두에서 효율적이어야 한다. 시간적으로 효율적인 가상화를 구현하기 위해서는 하드웨어 지원, 즉 TLB와 같은 하드웨어 기능을 활용해야 한다.
마지막으로, 메모리 가상화의 세 번째 목표는 보호이다. 운영 체제는 프로세스 간에 서로를 보호하고 OS 자체도 프로세스로부터 보호해야 한다. 프로세스가 로드, 저장 또는 명령어를 가져올 때, 다른 프로세스나 OS 자체(즉, 주소 공간 외부의 모든 것)의 메모리 내용에 접근하거나 영향을 미치지 않도록 해야 한다. 보호는 각 프로세스가 자신만의 격리된 코쿤 안에서 실행되도록 보장함으로써 프로세스 간의
분리(isolation) 속성을 제공한다. 각 프로세스는 다른 결함이나 악성 프로세스로부터 안전한 자체 격리된 환경에서 실행되어야 한다.
다음 장에서는 하드웨어와 운영 체제 지원을 포함한 메모리 가상화에 필요한 기본 메커니즘을 중점적으로 살펴볼 것이다. 또한, 운영 체제에서 만나게 되는 관련 정책, 예를 들어 여유 공간을 관리하는 방법이나 공간이 부족할 때 어떤 페이지를 메모리에서 제거할 것인지 등도 살펴볼 것이다. 이를 통해 현대 가상 메모리 시스템이 실제로 어떻게 작동하는지에 대한 이해를 쌓아나갈 것이다.