Operating Systems : Three Easy Pieces를 보고 번역 및 정리한 내용들입니다.
CPU를 가상화 할 때와 마찬가지로, 메모리를 가상화할 때도 효율성과 제어권을 얻으면서 가상화를 제공해야한다. 효율성이란 하드웨어의 지원을 이용하는 것이고, 제어는 프로그램이 자신에게 허용된 메모리 외에는 접근할 수 없도록 OS가 보장해야한다는 것이다.
여기에 추가적으로 가상 메모리 시스템에서는 융통성(flexibility) 또한 신경 써줘야 하는데, 프로그램은 주th 공간을 원하는만큼 사용할 수 있어야 한다는 것이다.
어떻게 효율적인 가상화를 설계할 수 있을까?
프로그램에 대한 융통성은 어떻게 제공할 수 있을까?
메모리 위치에 대한 제어는 어떻게 유지할 수 있을까?
일반적인 테크닉은 하드웨어 기반의 주소 변환(hardware-based address translation) 이다. 이는 간단히 주소 변환이라고도 불리는데, 주소 변환 하드웨어는 메모리 접근이 일어날 때 명령어의 가상 주소를 실제 정보가 담겨있는 물리 주소로 변환한다. 모든 각 메모리 참조의 주소 변환은 프로그램의 메모리 참조를 메모리의 실제 위치로 리다이렉트하기 위해 하드웨어에 의해 이루어진다.
물론 하드웨어만으로는 메모리를 가상화할 수 없다. 메모리의 어디가 쓸 수 있고 어디는 사용 중인지를 아는 것 등, 메모리 관리를 위해서는 OS가 필요하다.
지금은 다음의 간단한 가정들을 사용하도록 한다.
- 유저의 주소 공간은 물리 메모리에 연속적으로 위치한다.
- 주소 공간의 크기가 너무 크지는 않다고 가정한다.
- 구체적으로, 이 사이즈는 물리 메모리의 사이즈보다는 작다고 가정한다.
- 각 주소 공간들은 정확히 같은 사이즈를 가지고 있다.
왼쪽은 프로세스와 그 주소 공간, 오른쪽은 이 프로세스가 할당되어 있는 물리 메모리이다. 프로그램의 입장에서, 주소 공간은 주소 0으로부터 시작해 최대 16KB까지를 차지한다. 이 프로세스가 만드는 모든 메모리 참조들은 이 범위 내에 있어야 한다.
메모리를 가상화하기 위해 OS는 이 프로세스를 물리 메모리의 어딘가에 위치시켜야 한다. 그 위치의 주소는 꼭 0이 아니어도 된다. 그렇다면 어떻게 프로세스에게 자신이 재배치되고 있다는 사실을 알려주지 않으면서 프로세스를 재배치할 수 있을까?
1950년대 후반의 첫 시분할 컴퓨터에 도입된 간단한 아이디어가 있다. 이는 베이스-바운드 기법(base-and-bounds) 으로, 동적 재배치(dynamic relocation) 라고도 불린다.
이는 구체적으로 CPU에 베이스(base) 와 바운드(bound) 라 불리는 두 레지스터를 사용한다. 이 베이스-바운드 쌍은 주소 공간을 원하는 물리 메모리의 어느 위치에든 위치시킬 수 있게 함으로써 프로세스가 자기 자신만의 주소 공간에만 접근할 수 있도록 보장한다.
여기에서 각 프로그램들은 주소 0번에 로드된 것처럼 쓰이고 컴파일된다. 하지만 프로그램이 실행되기 시작할 때, OS는 이 프로그램이 물리 메모리의 어디에 탑재되어야할지를 결정하고 베이스 레지스터에 해당 값을 쓴다. 프로세스에 어떤 메모리 참조가 생기면, 이것은 프로세서에 의해 다음과 같은 방식으로 변환된다.
physical address = virtual address + base
프로세스에 의해 만들어진 각 메모리 참조는 가상 주소이다. 하드웨어는 베이스 레지스터의 내용을 그 주소에 추가하고 결과는 메모리 시스템에서 실제 데이터에 접근하기 위해서 사용하는 물리 주소가 된다.
명령어 실행 예시로 무슨 일이 일어나는지를 보자.
128 : movl 0x0(%ebx), %eax
PC는 지금 128로 설정되어 있다. 하드웨어는 이 명령어를 가져올 때, 먼저 이 값을 베이스 레지스터의 값에 더해 물리 주소를 얻는다. 하드웨어가 이 명령어를 물리 메모리로부터 해당 주소로부터 가져오면 프로세서는 명령어를 실행한다. 어떤 시점에 프로세스가 가상 주소 15KB에 있는 데이터를 가져와야 한다고 하자. 프로세서는 15KB의 가상 주소를 베이스 레지스터에 담긴 32KB에 더해 물리 주소를 47KB를 얻고 원하던 내용을 가져올 것이다.
가상 주소를 물리 주소로 변환하는 것이 주소 변환이다. 하드웨어는 프로세스의 가상 주소를 받아서 실제 데이터가 들어있는 물리 주소로 변환하는데, 이러한 재배치는 런타임에 일어나고, 프로세스가 실행되기 시작한 후에는 언제든 주소 공간을 이동시킬 수 있기 때문에, 이 테크닉은 동적 재배치라고도 불린다.
그렇다면 바운드 레지스터는 왜 쓰는 걸까? 바운드 레지스터는 보호(protection) 를 돕기 위해 사용된다. 프로세서는 먼저 메모리 참조가 바운드 내에 있는지를 확인함으로써 참조가 제대로 된 것인지를 판단한다.
베이스, 바운드 레지스터들은 CPU에 있는 하드웨어라는 점을 잊지 말자. 이들은 CPU에서 주소 변환을 돕는 부분이라는 점에서 MMU(memory manage unit) 라고도 부른다.
바운드 레지스터가 메모리 보호를 구현하는 방법에는 두 가지가 있다. 첫 번째는 바운드 레지스터가 주소 공간의 사이즈를 가지게 해, 하드웨어가 베이스에 가상 주소를 더하기 전에 이를 먼저 확인하게 하는 방법이다. 다른 방법은 바운드에 주소 공간 끝에 해당하는 물리 주소를 갖게 하는 방법이다. 하드웨어는 먼저 베이스에 가상 주소를 더한 후, 해당 주소가 바운드 확인한다. 두 방법은 논리적으로 동치다.
하드웨어로부터의 지원이 필요한 것들이 뭐가 있는지 보자.
하드웨어가 동적 재배치 기능을 제공함에 따라, OS도 처리해야 할 새로운 이슈들을 가진다.
가장 먼저 프로세스가 만들어질 때 해야하는 행동이 있다. 그것은 바로 메모리에서 프로세스의 주소 공간으로 사용하기 위한 공간을 찾는 것이다. 다행스럽게도 각 주소 공간의 크기(의 합)가 물리 메모리의 크기보다 작고 서로 같은 크기를 가진다는 가정 아래 이 작업은 어려운 게 아니다. 물리 메모리를 슬롯들의 배열로 보고, 각 슬롯이 가용 상태인지 사용 중인 상태인지를 보면 그만이기 때문이다. 프로세스가 만들어졌을 때, OS는 새로운 주소 공간을 위한 여유 공간을 찾기 위해 가용 리스트(free list) 라 불리는 자료 구조를 탐색하고, 사용된 공간에는 사용됐다고 마크한다.
다만 이 문제는 여러 사이즈의 주소 공간을 사용하는 경우 더 어려운 문제가 되는데, 이는 다음 챕터들에서 다루게 될 것이다.
위의 예시를 보자. 물리 메모리의 첫 번째 슬롯은 OS, 32KB-48KB의 슬롯은 프로세스가 사용하고 있고, 나머지 두 슬롯들은 비어있다. 그러므로 가용 리스트는 이 두 슬롯을 엔트리로 포함해야 한다.
두 번째는 프로세스가 종료됐을 때 해야 하는 작업들이다. OS는 사용 중이던 메모리를 다른 프로세스들이나 OS에서 사용할 수 있도록 회수해야 한다. 프로세스가 끝나면 OS는 이 메모리를 다시 가용 리스트로 보내고, 관련된 자료 구조들을 청소해야 한다.
세 번째는 문맥 전환이 일어났을 때 해야하는 작업들이다. OS는 문맥 전환이 일어나면 몇 가지 추가적인 작업 단계들을 수행해야 한다. CPU에는 단 한 쌍의 베이스-바운드 레지스터 쌍이 있고, 그 값은 프로세스마다 다르다. 프로세스들은 메모리의 서로 다른 물리 주소에 올라가있기 때문이다. 그러므로 OS는 문맥 전환을 할 때 베이스-바운드 쌍을 저장하고 복원해야 한다.
구체적으로는 OS가 프로세스 실행을 멈추려고 결정할 때, OS는 현재 실행 중인 프로세스의 베이스-바운드 값을 process structure, 또는 PCB(process control block) 와 같은 프로세스 별 구조로 메모리에 저장해야 한다. 비슷하게 실행 중인 프로세스를 나중에 재개할 때에는 CPU에 해당 프로세스의 베이스-바운드 값들을 다시 돌려 줘야한다.
마지막으로 OS는 예외 핸들러를 제공해야 한다. 예를 들어 프로세스가 자신의 바운드를 넘어서는 메모리에 접근하려 할 때 CPU는 예외를 일으켜야 하는데, OS는 그런 예외가 일어났을 때 어떤 행동을 취할지를 미리 준비해야 한다. OS의 일반적인 반응은 그런 프로세스를 종료하는 것이다.
주소 변환을 통해 OS는 프로세스들의 메모리 접근이 주소 공간의 바운드 내에 있다는 걸 보장하면서 각 프로세스로부터의 모든 메모리 접근을 제어할 수 있다.
이 테크닉의 효율성은 가상 주소의 물리 주소로의 변환을 빠르게 하는 하드웨어의 지원을 통해 이루어진다. 이것들은 모두 프로세스에게는 투명하게 수행되는데, 프로세스는 자신의 주소가 변환되고 있다는 것을 모른다는 것이다.
동적 재배치는 베이스 레지스터를 가상 주소에 더하고 해당 주소가 바운드에 있는지를 확인하는 하드웨어 로직만을 필요로 한다는 점에서 효율적이다. 베이스-바운드 기법은 OS와 하드웨어가 어떠한 프로세스도 자신의 주소 공간 밖의 메모리를 참조할 수 없음을 보장함으로써 메모리 보호도 제공한다.
하지만 동적 재배치는 비효율성도 가지고 있는데, 내부 단편화(internal fragmentation) 이라 부르는 것이다. 내부 단편화는 할당된 단위 내의 공간이 전부 사용되지 못해 낭비되는 것을 말한다. 위 예시에서 프로세스는 32KB에서 48KB의 공간을 차지하는데, 이 프로세스의 힙과 스택은 충분히 자라지 않았기 때문에 그 사이의 공간은 낭비되고 있다.
따라서 물리 메모리를 좀 더 효율적으로 사용하고 내부 단편화를 피하기 위한 좀더 복잡한 방법이 필요하다. 이를 해결하기 위한 방법 중 하나인 세그멘테이션(segmentation) 은 다음으로 다루게 될 것이다.텍스트