[메모리 가상화 #1] 멀티프로그래밍, 주소공간, 주소공간 변환 본문
초기 시스템

- 메모리 관점에서 초기 컴퓨터는 많은 개념을 사용자에게 제공하지 않았다.
- 운영체제는 메모리에 상주하는 루틴(라이브러리) 의 집합 이었다.
- 물리 메모리에 하나의 실행중인 프로그램(프로세스)이 존재했고, 나머지 메모리를 사용하였다.
- 특별한 가상화 X
멀티프로그래밍과 시분할
- 시간이 흐른 후 사람들이 더 효과적으로 컴퓨터를 공유하기 시작하며 멀티 프로그래밍 시대가 도래했다.
- 여러 프로세스가 실행 준비 상태에 있고 운영체제는 그들을 전환하면서 실행하였다.
- 한 프로세스가 입출력을 실행하면, CPU는 다른 프로세스로 전환하였다. (CPU 이용률 증가)
- 오랜 시간이 걸리는 프로그램-디버그 사이클에 지친 프로그래머가 일괄처리방식 컴퓨팅의 한계를 느꼇다
- 현재 실행 중인 작업으로부터 즉시 응답을 원하기 때문에 대화식 이용(interactivity)의 개념이 중요하게 되었다.
- 시분할을 구현하는 한 가지 방법은 하나의 프로세스를 짧은 시간 동안 실행시키는 것이다. 해당 기간 동안 프로세스에게 모든 메모리를 접근할 권한이 주어진다.
- 그런 후에, 이 프로세스를 중단하고, 중단 시점의 모든 상태를 디스크 종류의 장치(모든 물리 메모리를 포함하여)에 저장하고 다른 프로세스의 상태를 탑재하여 또 짧은 시간 동안 실행시킨다. 시분할 시스템을 이런 식으로 엉성하게 구현하였다
- 이 방법에는 큰 문제점이 있다. 너무 느리게 동작한다는 것이고, 메모리가 커질수록 느리게된다. 레지스터 상태를 저장하고 복원하는 것은 빠르지만 메모리의 내용 전체를 디스크에 저장하는 것은 엄청나게 느리다. 우리가 할 일은 프로세스를 메모리에 그대로 유지하면서, 운영체제가 시분할 시스템을 효율적으로 구현할 수 있게 하는 것이다.

- 위 그림처럼 프로세스 (A, B, C)가 있고 각 프로세스는 512KB 물리 메모리에서 각기 작은 부분을 할당받았다.
- 하나의 CPU를 가정할 때, 운영체제는 실행할 한 개의 프로세스 A를 선택하고, 다른 프로세스 B, C는 준비 큐에서 실행을 기다린다.
- 시분할 시스템이 대중화되면서 여러 프로그램이 메모리에 동시에 존재하려면 보호(protection)가 중요한 문제가 된다.
- 우리는 한 프로세스가 다른 프로세스의 메모리를 읽거나 혹은 더 안 좋게는 쓸 수 있는 상황을 원하지 않는다.
주소 공간

- 그런 위험에 대비하여 운영체제는 사용하기 쉬운 메모리 개념을 만들어야 한다. 이 개념이 주소 공간(address space)이다.
- 주소 공간은 실행 프로그램의 모든 메모리 상태를 갖고 있다.
- 프로그램의 코드(code, 명령어)는 반드시 메모리에 존재해야 하고 따라서 주소 공간에 존재한다.
- 정적으로 초기화된 변수도 존재 (설명에서는 제외)
- 주소 공간 가장 위쪽에 위치한다. 코드는 정적이기 때문에 메모리에 저장하기 쉽다.
- 프로그램이 실행되면서 추가 메모리를 필요하지 않는다.
- 스택은 함수 호출 체인 상의 현재 위치, 지역 변수, 함수 인자와 반환 값 등을 저장하는 데 사용된다.
- 주소 공간의 하단에 존재함 ( 확장할 수 있어야 하기 때문에
- 힙(heap)은 동적으로 할당되는 메모리를 위해 사용된다.
- 주소 공간의 상단에 존재함 ( 확장 할 수 있기 때문)
- 실제로 프로그램이 물리 주소 0에서 16 KB 사이에 존재하는 것은 아니다. 실제로는 임의의 물리 주소에 탑재된다.
- 그림 16.2의 프로세스 A, B, C를 보자. 각 프로세스가 어떤 식으로 다른 주소에 탑재되었는지 알 수 있다.
- 운영체제가 이 일을 할 때, 우리는 운영체제가 메모리를 가상화(virtualizing memory)한다고 말한다.
- 실행 중인 프로그램은 자신이 특정 주소의 메모리에 탑재되고 매우 큰 주소 공간을 가지고있다고 생각하기 때문이다.
- 그림 16.2의 프로세스 A가 주소 0으로부터(우리는 이를 가상 주소(virtualaddress)라고 부를 것이다)
- load 연산을 수행할 때, 운영체제는 하드웨어의 지원을 통해 물리 주소 0이 아니라 물리 주소 320 KB(A가 탑재된 메모리)를 읽도록 보장해야 한다.
- 이것이 메모리 가상화의 열쇠이고, 현대 모든 컴퓨터 시스템의 기저를 이룬다.
메모리 가상화의 목표
고립의 원칙
고립은 신뢰할 수 있는 시스템을 구축하는 데 중요한 원칙이다. 두 개체가 서로 적절하게 고립된 경우, 한 개체가 실패하더라도 상대 발체에 아무 영향을 주지 않는다는 것을 암시한다. 운영체제는 프로세스를 서로 고립시키기 위해 노력하고 이런 방식으로 한 프로세스가 다른 프로세스에게 피해를 주는 것을 방지한다. 더 나아가 메모리 고립을 사용하여 운영체제는 프로그램이 운영체제 동작에 영향을 줄 수 다는 것을 보장한다. 일부 현대 운영체제는 고립을 더 확장하여 운영체제의 구성 요소를 서로 고립시킨다. 이러한 마이크로커널은 전통적인 모놀리식 커널보다 더 큰 신뢰성을 제공할 수 있다.
- 가상 메모리 시스템(VM) 의 주요 목표 중 하나는 **투명성(transparency)**이다
- 운영체제는 실행 중인 프로그램이 가상 메모리의 존재를 인지하지 못하도록 가상 메모리 시스템을 구현해야 한다.
- 프로그램은 메모리가 가상화되었다는 사실을 인지해서는 안 된다. 오히려 프로그램은 자신이 전용 물리 메모리를 소유한 것처럼 행동해야 한다.
- VM의 또 다른 목표는 **효율성(efficiency)**이다.
- 운영체제는 가상화가 시간과 공간 측면에서 효율적이도록 해야 한다.
- 시간적으로는 프로그램이 너무 느리게 실행되서는 안되고 공간적으로는 가상화를 지원하기 위한 구조를 위해 너무 많은 메모리를 사용해서는 안 된다.
- 운영체제는 TLB 등의 하드웨어 기능을 포함하여 하드웨어의 지원을 받아야 한다.
- 마지막으로 VM의 세 번째 목표는 **보호(protection)**이다.
- 운영체제는 프로세스를 다른 프로세스로부터 보호해야 하고 운영체제 자신도 프로세스로부터 보호해야 한다.
- 프로세스가 탑재, 저장, 혹은 명령어 반입 등을 실행할 때 어떤 방법으로든 다른 프로세스나 운영체제의 메모리 내용에 접근하거나 영향을 줄 수 있어서는 안 된다. 즉, 자신의 주소 공간 의 어느 것도 접근할 수 있어서는 안 된다. 보호 성질을 이용하여 우리는 프로세스들을 서로 고립(isolate)시킬 수 있다.
주소 변환의 원리
어떻게 효율적이고 유연하게 메모리를 가상화하는가? 어떻게 효율적인 메모리 가상화를 구축할 수 있을까? 프로그램이 필요로 하는 유연성(프로그래머들이 원하는 대로 주소 공간을 사용)을 어떻게 제공하는가? 프로그램이 접근할 수 있는 메모리의 위치에 대한 제어를 어떻게 유지하는가? 메모리 접근을 어떻게 적절히 제한할 수 있는가? 어떻게 이 모든 것을 효율적으로 할 수 있는가?
- 주소 변환을 통해 하드웨어는 명령어 반입, 탑재, 저장 등의 가상 주소를 정보가 실제 존재하는 물리 주소로 변환한다.
- 프로그램의 모든 메모리 참조를 실제 메모리 위치로 재지정하기 위하여 하드웨어가 주소를 변환한다.
- 물론 하드웨어에 의해 제공되는 low level 기능들은 변환을 가속화 시키는 도움을 주지만 하드웨어 만으로는 메모리 가상화를 구현할 수 없다.
- 운영체제가 관여하여 메모리의 빈 공간과 사용 중인 공간을 알고, 메모리 사용을 제어하고 관리한다.
- 프로그램이 자신의 전용 메모리를 소유하고 그 안에 자신의 코드와 데이터가 있다는 환상을 만드는 것이다.
가정
- 당분간 사용자 주소 공간은 물리 메모리에 연속적으로 배치되어야 한다고 가정한다.
- 논의를 단순화하기 위해 주소 공간의 크기가 너무 크지 않다고 가정한다.
- 주소 공간은 물리 메모리 크기보다 작다. 우리는 또한 각 주소 공간의 크기는 같다고 가정한다.
사례
void func() {
int x = 3000;
x = x + 3;
}
컴파일러는 이 코드를 어셈블리 코드로 변환하고 그 결과는 x86 어셈블리로 다음과 같을 것이다.
128: movl 0x0(\\%ebx) , \\%eax ; 0+ebx eax에 저장
132: addl \\$0x03 , \\%eax ; eax레지스터에 3을 더한다
135: movl \\%eax , 0x0(\\%ebx) ; eax를 메모리에 다시 저장
x의 주소는 레지스터 ebx에 저장되어 있다고 가정하고, 이 주소에 저장되어있는 값을 범용 레지스터 eax에 넣는다. eax에 3을 더하고 eax 값을 같은 위치의 메모리에 저장한다.

그림 18.1에서 코드와 데이터가 프로세스 주소 공간에 어떻게 배치되어 있는지 볼 수있다. 세 개 명령어의 코드는 주소 128에 위치하고(상단 코드 섹션에), 변수 x의 값은 주소 15 KB(아래 쪽 스택에)에 위치한다. 그림에서 x의 초기 값은 3000이다(스택 영역 참조).
- 주소 128의 명령어를 반입
- 이 명령어 실행(주소 15 KB에서 탑재)
- 주소 132의 명령어를 반입
- 이 명령어 실행(메모리 참조 음)
- 주소 135의 명령어를 반입
- 이 명령어 실행(15 KB에 저장)
프로그램 관점에서 주소 공간은 주소 0부터 시작하여 최대 16 KB까지이다. 프로그램이 생성하는 모든 메모리 참조는 이 범위 내에 있어야 한다. 메모리 가상화를 위해 운영체제는 프로세스를 물리 메모리 주소 0이 아닌 다른 곳에 위치시키고 싶다. 어떻게 하면 프로세스 모르게 메모리를 다른 위치에 재배치 하느냐가 우리가 해결해야 할 문제이다. 프로세스 주소 공간이 실제로는 다른 물리 주소에 배치되어 있을 때, 주소 0 번지부터 시작하는 가상 주소 공간의 환상을 어떻게 제공할 수 있을까?

- 물리 메모리의 첫 번째 슬롯은 운영체제 자신이 사용한다.
- 위 예의 프로세스는 물리주소 32KB에서 시작하는 슬롯에 재배치된 것을 볼 수 있다.
동적(하드웨어-기반) 재배치
- 1950년대 후반의 첫번째 시분할 컴퓨터에서 **베이스와 바운드(base and bound)**라는 아이디어 채택,
- 동적 재배치 라고도 한다.
- 각 CPU마다 2개의 하드웨어 레지스터가 필요하다. 하나는 베이스(base) 레지스터라고 불리고, 다른 하나는 바운드(bound) 레지스터 혹은 한계(limit) 레지스터라고 불린다.
- 이 베이스와 바운드 쌍은 우리가 원하는 위치에 주소 공간을 배치할 수 있게 한다.
- 배치와 동시에 프로세스가 오직 자신의 주소 공간에만 접근한다는 것을 보장한다.
- 이 설정에서 각 프로그램은 주소 0에 탑재되는 것처럼 작성되고 컴파일된다.
- 프로그램 시작 시, 운영체제가 프로그램이 탑재될 물리 메모리 위치를 결정하고 베이스 레지스터를 그 주소로 지정한다. 위의 예에서 운영체제는 프로세스를 물리 주소 32 KB에 저장하기로 결정하고 베이스 레지스터를 이 값으로 설정한다.
- 프로세스에 의해 생성되는 모든주소가 다음과 같은 방법으로 프로세서에 의해 변환된다.
physical address = virtual address + base
- 프로세스가 생성하는 메모리 참조는 가상 주소이다. 하드웨어는 베이스 레지스터의 내용을 이 주소에 더하여 물리 주소를 생성한다.
- 이를 이해하기 위해 명령어가 실행될 때 무슨 일이 벌어지는지 추적해 보자. 우리의 예전 코드 중 하나를 살펴보자.
128: movl 0x0(%EBX) , % eax
프로그램 카운터(PC) 는 128로 설정된다. 하드웨어가 이 명령어를 반입할 때, 먼저 PC 값을 베이스 레지스터의 값 32 KB(32768) 에 더해 32896의 물리 주소를 얻는다. 그런 후 하드웨어는 해당 물리 주소에서 명령어를 가져온다. 그리고 프로세서는 명령어의 실행을 시작한다. 얼마 후 프로세스는 가상 주소 15 KB의 값을 탑재하라는 명령어를 내린다. 이 주소를 프로세서가 받아 다시 베이스 레지스터(32 KB) 를 더하고 물리 주소 47 KB에서 원하는 내용을 탑재한다.
가상 주소에서 물리 주소로의 변환이 주소 변환이라고 부르는 로 그 기술이다. 하드웨어는 프로세스가 참조하는 가상 주소를 받아들여 데이터가 실제로 존재하는 물리 주소로 변환한다. 이 주소의 재배치는 실행 시에 일어나고, 프로세스가 실행을 시작한 이후에도 주소 공간을 이동할 수 있기 때문에, 동적 재배치(dynamic relocation) 라고 도 불린다.
이제 다음과 같은 의문을 가질 수 있다. 바운드(한계) 레지스터는 어디에 쓰는 거지? 베이스와 운드 방식이라고 하지 않았나? 사실 맞다. 예측했을지 모르지만, 바운드 레지스터는 보호를 지원하기 위해 존재한다. 프로세서는 먼저 메모리 참조가 합법적인가를 확인하기 위해 가상 주소가 바운드 안에 있는지 확인한다. 앞에서 다룬 간단한예에서, 바운드 레지스터는 항상 16 KB로 설정된다. 프로세스가 바운드보다 큰 가상 주소 또는 음수인 가상 주소를 참조하면 CPU는 예외를 발생시키고 프로세스는 종료될 것이다. 바운드의 요점은 프로세스가 생성한 모든 주소가 합법적이고 프로세스의“범위”에 있다는 것을 확인하는 것이다.
베이스와 비운드 레지스터는 CPU 칩 상에 존재하는 하드웨어 구조임을 주의하라 (CPU당 1쌍). 주소 변환에 도움을 주는 프로세서의 일부를 메모리 관리 장치(memory management unit, MMU)라고 부르기도 한다. 더 정교한 메모리 관리 기법을 개발 할수록 MMU에 더 많은 회로를 추가하게 될 것이다
하드웨어 지원

운영체제 이슈
동적 재배치 지원을 위해 하드웨어가 새로운 기능을 제공하는 것과 마찬가지로, 운영체제에도 새로운 이슈가 등장한다.
- 프로세스가 생성될 때 운영체제는 주소 공간이 저장될 메모리 공간을 찾아 조치를 취해야 한다.
- 프로세스가 종료할 때, 프로세스가 사용하던 메모리를 회수하여 다른 프로세스나 운영체제가 사용할 수 있게 해야 한다.
- 프로세스가 종료하면 운영체제는 종료한 프로세스의 메모리를 다시 빈 공간 리스트에 넣고 연관된 자료 구조를 모두 정리한다.
- 운영체제는 문맥 교환이 일어날 때에도 몇가지 추가 조치가 필요하다.
- CPU마다 한 쌍의 베이스-바운드 레지스터만 존재하고 각 프로그램은 다른 물리 주소에 탑재되어야 하기 때문에 실행 중인 프로그램마다 다른 값을 가진다.
- 운영체제는 프로세스 전환 시 베이스와 바운드 쌍을 저장하고 복원해야한다.
- 운영체제가 실행중인 프로세스를 중단시키기로 결정하면 운영체제는 메모리에 존재하는 프로세스 별 자료구조 안에 베이스와 바운드 레지스터의 값을 저장해야한다. 이 자료구조를 프로세스 제어 블럭(Process Control Block, PCB)라고 불린다.
- 운영체제는 예외 핸들러 또는 호출될 함수를 제공해야 한다.

요약

하드웨어/OS의 상호작용을 타임라인으로 보여준다. 재부팅할때 컴퓨터를 사용 가능한 상태로 만들기 위하여 운영체제가 무앗을 하는지 보여 준다.
주소 변환을 사용하면 운영체제는 프로세스의 모든 메모리 접근을 제어할 수 있고, 접근이 항상 주소 공간의 범위 내에서 이루어지도록 보장할 수 있다. 이 기술의 효율성의 열쇠는 하드웨어 지원이다. 하드웨어 지원은 프로세스가 이해하는 메모리인 가상 주소를 실제 메모리 모습인 물리 주소로 변환하며 이 변환을 빠르게 수행한다. 이 모든 것은 재배치된 프로세스에게 투명한 방식으로 이루어진다.
- 동적 재배치는 비효율 적이다.
- 재배치된 프로세스는 32 KB에서 48 KB까지의 물리 메모리를 사용한다.
- 그러나 프로세스 스택과 힙이 아주 크지 않기 때문에, 둘 사이의 공간이 단순히 낭비되고 있다.
- 할당된 영역의 내부 공간이 사용되지 않기 때문에, 즉 단편화가 발생되어 낭비된다. 이런 유형의 낭비를 내부 단편화(internal fragmentation)라고 한다.
- 현재 접근 방식에서 비록 더 많은 프로세스를 탑재할 수 있는 충분한 물리 메모리가 있더라도, 고정 크기의 슬롯에 주소 공간을 배치해야 하기 때문에 내부 단편화가 발생한다 .
- 물리 메모리의 이용률을 높이고 내부 단편화를 방지하기 위해 더 정교한 기법이 필요하다. 첫 번째 시도는 base-and-bound를 일반화하는 것이다. 이러한 일반화된 베이스-운드 기법을 세그멘테이션 (segmentation)이라고 부른다.
'Fundamentals > OS' 카테고리의 다른 글
| [메모리 가상화 #3] 빈 공간 관리 (1) | 2025.06.28 |
|---|---|
| [메모리 가상화 #2] 세그멘테이션 (0) | 2025.06.28 |
| [CPU 스케줄링 #3] 멀티 프로세서 SQMS, MQMS (1) | 2025.06.17 |
| [CPU 스케줄링 #2] 비례배분, 추첨 스케줄링, 보폭 스케줄링 (2) | 2025.06.16 |
| [CPU 스케줄링 #1] FCFS, SJF, STCF , RR , MLFQ (2) | 2025.06.15 |