이번 포스팅에서는 IT 인프라 가상화의 기초가 되는 서버 가상화의 역사를 알아보고, 실제로 현대의 서버 가상화 기술이 어떻게 구현되는지 알아볼 것이다.
우리는 가장 먼저 서버 가상화와 하이퍼바이저가 무엇인지 알아보고, 가상화 기술을 탄생시킨 IBM 메인프레임에 대한 이야기를 잠깐 다룰 것이다. 그리고 현대 x86 가상화의 역사와, x86 CPU 가상화의 구현 방법을 설명할 것이다.
서버 가상화 기술은 컴퓨터 하나의 물리적인 리소스를 여러 운영체제가 공유하여 사용하되, 상호간의 컴퓨팅 환경을 훼손하지 않고 동작할 수 있도록 격리(isolation)하는 기술이다. 하이퍼바이저(Hypervisor) 위에서 동작하는 OS들은 자신이 모든 하드웨어 리소스를 독점한다고 생각하기 때문에, 하이퍼바이저는 모든 장치를 OS에게 가상화하여 보여줘야만 한다. 여기서 가상화(Virtualization)는 실제 하드웨어 대신 가상의 하드웨어를 소프트웨어적으로 구현하여 OS에게 보여준다는 의미로 사용되었다. 즉,
가상화(Virtualization)는 실제 하드웨어 대신 가상의 하드웨어를 OS에 노출시키는 기술을 의미한다
그런데 하이퍼바이저란 무엇일까? 누가, 언제, 왜 하이퍼바이저라는 단어를 만들었을까?
'Hypervisor'라는 단어의 역사를 거슬러 올라가 보면 우리는 서버 가상화 기술의 시작점을 만나게 된다. 바로 IBM의 Cambridge Scientific Center에서 시작된 CP/CMS 프로젝트이다. 이 프로젝트는 1964년에 시작되었고, CP(Control Plane) -가상 머신을 생성하는 '하이퍼바이저' (아마도 처음에는 하이퍼바이저라고 불리지 않았을 것이다)- 와 그 위에서 실행되는 CMS(Cambridge Monitor System)를 만드는 프로젝트였다. 이 프로젝트의 목표 중 하나는 완전한 가상 하드웨어를 가상 머신에게 제공하는 전가상화(Full Virtualization)을 구현하는 것이었고, 이것이 최초의 VMM(Virtual Machine Monitor)이 되었다.
확인 가능한 공개된 레퍼런스 중에서 처음으로 'Hypervisor'라는 단어가 언급되는 자료는 Popek & Goldberg (1974)
의 논문, "Formal Requirements for Virtualizable Third Generation Architectures"[1] 이다.
1974년에 발표된 이 논문의 키워드에 hypervisor라는 단어가 처음으로 등장하지만, 본문에서는 VMM(Virtual Machine Monitor)라는 단어를 사용한다. 이 논문의 저자들이 사용하던 시스템이 바로 CP/CMS 프로젝트의 산출물인 CP-67이기 때문에, Hypervisor라는 단어가 CP-67에서 왔을 것임을 짐작할 수 있다.
한편, 당시 IBM 메인프레임은 시스템 전체에 영향을 미칠 수 있는 명령을 실행하는 일종의 특권 모드(지금의 커널 모드)인 Supervisor Mode
를 가지고 있었고, 이 기능은 Supervisor Call 명령어를 통해 호출될 수 있었다.
이제 Hypervisor라는 단어가 어디서 온 것인지 짐작이 갈 것이다. CP/CMS를 만들던 IBM 엔지니어들은 'Supervisor를 감독하는 더 높은 Supervisor' 의 개념인 VMM(Virtual Machine Monitor)을 만들어 냈고, Supervisor보다 더 높은 권한을 가진 존재라는 의미에서 Hypervisor라는 단어를 사용하였다. 그리고 이것이 일반 용어로 고착화된 결과 우리는 Hypervisor라는 단어를 일반 명사로 사용하게 된 것이다.
하이퍼바이저는 일반적인 OS가 하는 일을 모두 수행한다. 다시 말해 CPU와 메모리의 할당을 관리하고, System Call을 처리하며 유저 프로세스와 실제 물리 리소스의 상호 작용을 관리한다. 단지 유저 프로세스가 응용 프로그램이 아닌, OS 일 뿐이다.
하지만 응용 프로그램 대신 OS를 실행해야 하기 때문에, 하이퍼바이저는 다음과 같은 핵심적인 기능 3가지를 가상화하여 Guest OS에 제공해야 한다.
이번 글에서는 CPU 가상화를 자세히 다룰 것이며, 나머지 기능의 가상화에 대해서는 시리즈의 다음 글에서 다룰 것이다.
이제 일반적인 하이퍼바이저의 구분을 알아보자.
VMM이 물리 하드웨어 위에 위치하며, 하드웨어 자원을 독점적으로 제어한다. VMM은 가상화된 장치를 VM에게 제공하며, 하드웨어 장치와 통신하기 위한 드라이버를 가지고 있다.
이 분류에 해당되는 하이퍼바이저는 VMware ESXi, Microsoft Hyper-V 등이 있다.
VMM은 OS 위의 애플리케이션으로 동작하며, 하드웨어 자원을 사용하기 위해 OS의 서포트를 받는다.
VMM은 가상화된 하드웨어를 VM에게 제공하지만, 실제 하드웨어와는 하단의 OS가 제공하는 HAL을 통해 통신한다. OS의 드라이버를 사용할 수 있기 때문에 별도의 하드웨어 드라이버를 가지고 있지 않다. 성능 관점에서 OS 스케줄러의 간섭을 받으며 Type 1대비 더 큰 오버헤드를 가진다.
이 분류에 해당되는 하이퍼바이저는 VMware Player(Workstation), VMware Fusion, Oracle VirtualBox, QEMU 등이 있다.
만약 에뮬레이션을 이용할 경우, Type 1과는 달리, 하드웨어 아키텍처가 다른 OS도 구동할 수 있다는 장점이 있다.
하지만 Linux KVM은 어떨까?
KVM은 Type 1로 분류하기도, Type 2로 분류하기도 어렵다. 일단 활성화되면 메인 OS의 마이크로커널 자체가 가상화되어 VM으로 올라가는 Hyper-V와 달리, KVM은 리눅스 커널 모듈의 일부이다. 리눅스 커널 인터페이스를 통해 하드웨어 자원에 접근하지만, 덕분에 별도의 HAL과 드라이버는 필요하지 않다.
KVM을 Type 1 하이퍼바이저로 단정짓는 경우가 많이 있지만, KVM은 Type 1과 2의 특징을 모두 가지고 있기 때문에 종래의 분류에 집어넣기에는 다소 무리가 따른다. 사용자 관점에서는 성능이 가장 중요한 포인트이기 때문에, 굳이 따지자면 KVM은 Type 1에 보다 가깝다고 이야기 할 수 있을 것이다.
앞서 언급한 Popek & Goldberg (1974)
[1]는 CPU 아키텍처가 가상화를 지원하기 위해 필요한 충분조건을 최초로 제시한 기념비적인 논문이며, 이 논문의 방법론에 따라 IBM CP-67/CMS가 디자인되었다. CP-67/CMS는 바로 아래에서 언급할 Trap and Emulate를 통해 가상 머신을 구현한 CP-40/CMS를 System/360-67에 포팅한 것으로 최초로 판매된 상용 VMM 구현체이며, 1972년 IBM이 발표한, S/370에 CP-67을 포팅한 VM/370의 소스코드 기반이 되었다.
당대의 메인프레임은 시분할 방식으로 운용되었으나, 다른 사용자의 응용프로그램이 메모리 공간을 침범할 수도 있었으며, 디스크 디렉토리를 공유해야 했다. CP/CMS는 가상 머신을 통해 이러한 제약사항을 제거하고, 사용자별로 완전히 독립된 가상 메모리, 가상 디스크, 가상 IO 장치를 제공할 수 있었다.[4]
(Trap and Emulate 방식의 privileged instruction 처리 예시)
앞서 언급한 IBM VM/370이 사용한 방식이자, 가장 일반적인 구현 방식이다.
이 방식으로 가상화를 구현하기 위한 아이디어는 다음과 같다.[3]
De-privileging
이 개념을 설명하려면 먼저 OS의 특권 모드(Privileged Mode)에 대한 개념을 알고 있어야 한다.
OS가 제공하는 가장 핵심적인 기능은 하드웨어 리소스의 스케줄링이다. OS가 제한된 자원을 독점하며, 응용 프로그램은 해당 자원이 필요할 때 OS의 System Call을 통해서 OS에게 제어권을 넘기고, OS가 해당 요청을 완료한 다음 다시 응용 프로그램을 실행한다.
이 때, 커널은 특권 모드(Privileged Mode)에서 동작하기 때문에 리소스가 한정되어 있는 하드웨어에 독점적으로 접근할 수 있는 권한을 가지고 있다. 즉, 커널이 리소스 스케줄링이라는 핵심 기능을 수행하기 위해 필요한 것이 바로 실행 모드의 구분과 특권 모드인 것이다.
이제 본론으로 돌아가, VMM의 관점에서 이 개념을 다시 생각해 보자.
VM의 OS(이하 Guest OS) 커널은 자신이 특권 모드에서 동작하고 있다고 생각하고, 특권 모드에서 사용할 수 있는 명령어를 실행할 것이다. 하지만 실제로 특권 모드에서 실행되는 것은 VMM이기 때문에 Guest OS 커널이 실행하는 특권 명령어는 Exception(Trap)을 일으킬 것이다.
VMM은 CPU의 Exception handler의 실행 주소를 자신의 Exception handler로 설정하여 Guest OS에서 발생한 특권 모드 명령어를 정상적으로 처리한 뒤 실행 컨텍스트를 VM에게 반환한다.
이렇게 하이퍼바이저가 Guest OS에서 발생하는 Trap을 가로채서 VM에 맞게 에뮬레이션 하는 것을 De-privileging이라 한다.
Primary and shadow structures
가상머신이 가진 CPU Priviliged state와 하이퍼바이저의 priviliged state는 서로 다르다. 이런 차이점을 마스킹하기 위해 하이퍼바이저는 Guest OS가 가진 primary structure를 모방한 shadow structure를 구현한다. CPU의 경우 Page Table Base Register, Processor Status Register 등이 있고, 게스트 OS가 해당 레지스터에 접근할 때마다 Trap을 발생시켜, 하이퍼바이저가 가지고 있는 shadow structure를 대신 보여준다.
Memory traces
일반적으로 메모리 접근은 Trap을 일으키지 않기 때문에, 메모리 일관성 유지를 위한 특별한 방법이 필요하게 된다. Trap and Emulate의 경우, 게스트의 CR3 레지스터에 Shadow Page Table Pointer를 노출시킨 뒤, 게스트에서 Page fault exception이 발생하면 VMM이 제어권을 넘겨받아서(Trap) 이 page fault가 잘못된 접근 때문에 발생한 것인지, 아니면 Shadow Page Table entry miss 때문에 발생한 것인지 판단한 뒤, 정상 동작일 경우 적절한 shadow PTE를 노출시키고, 그렇지 않을 경우 예외를 다시 게스트 OS에 보내게 된다.
하지만 이러한 접근 방식은 큰 오버헤드를 수반하기 때문에 심각한 성능 패널티를 가져온다. Shadow Page Table에 대한 기술적 디테일은 차후 메모리 가상화를 다룰 때 다시 언급할 것이다.
한편, x86 ISA의 경우 18개 명령어의 특권 모드에서의 동작과 비특권 모드에서의 동작이 다르게 구현되었기 때문에 Trap and Emulate를 사용할 수 없다는 것이 Robin, J. & Irvine, C. (2000)
[2]에서 알려져 있다. 하지만 VMware 연구원들이 2006년 ASPLOS '06에서 Binary Translation을 통한 x86 가상화 구현을 소개하면서 'x86 CPU에서 Popek & Goldberg (1974)
[3]를 만족하는 VMM을 구현할 수 있을까?' 라는 유구한 논쟁의 종지부를 찍었다.
이것을 Classical Binary Translation 이라 하며, 다음과 같이 구현되었다.
실제 워크로드에서 Trap이 필요한 명령의 수가 아주 적기 때문에, Binary Translation을 수행하는 경우 User Context의 Trap을 제거할 수 있고, Kernel Context에서 발생하는 Trap의 오버헤드 또한 일부 케이스의 경우 더 줄일 수 있었다고 VMware의 연구원들은 밝혔다[3]
하지만 이 경우에도 non-priviliged 명령어이지만, PTE에 접근하는 load/store 명령어는 shadow PTE에 접근해야 하기 때문에 Trap이 필요하다. 하지만 load/store 명령을 모두 Trap할 수는 없지 않은가?
그래서, VMware 연구팀은 동일 논문에서 Adaptive Binary Translation을 소개하며, 이 구현체가 non-privileged instruction trap으로 인한 성능 오버헤드를 제거할 수 있으며, 심지어 당대 하드웨어 기반 가상화 구현체의 성능을 능가할 수 있음을 보인다.
Adaptive Binary Translation의 핵심 아이디어는 다음과 같다.
무죄 추정의 원칙: 유죄라는 것이 확정되지 않았다면 무죄인 것으로 가정한다
다시 말해, 실행 초기에는 실행 코드를 최대한 그대로 번역한다. 이 시점에는 가상화 오버헤드가 최소화된다.
이 때, Trap count를 모니터링 하면서 프로그램이 과도한 Trap을 일으키지 않는지 감시한다. 만약 과도한 Trap이 발생하면 실행 코드를 번역하여 non-identical 코드로 치환한다. 이제 프로그램의 실행 흐름이 바뀌어 번역된 코드로 점프하면서 Trap이 제거된다.
그리고 다시 실행을 모니터링하여 innocent한 상태가 되면 identical 코드를 다시 사용한다.
여기서 설명한 Trap 모니터링은 하나의 예시이며, 실제로는 더 다양한 컨텍스트에 Adaptive Binary Traslation을 사용한다고 VMware 연구팀은 밝히고 있다.[3]
Xen의 경우 privileged instruction 문제를 해결하기 위하여 VMware와는 다른 접근 방식을 택했다. x86 CPU의 Protection Ring level이 0-3까지 존재한다는 것을 이용하여 OS 커널을 수정, Ring-1으로 올린다. Xen 하이퍼바이저는 Ring-0에서 동작하므로, 이제 하이퍼바이저에서 실행되는 privileged instruction을 구분할 수 있다.
여기에 더해, Guest OS가 아니라 VMM에서 실행되어야 하는 명령을 위한 Hypercall 인터페이스를 구현하였다. 예를 들어, Xen의 Guest OS는 CR3 레지스터에 직접 접근할 수 없고, EFLAGS를 업데이트 할 수 없다. 대신 커널에서 Hypercall을 호출하여 해당 작업을 수행해야 한다.[5]
그리고 Hypercall은 기본적으로 커널 구현을 위한 인터페이스이지만, 필요하다면 User Mode에서도 사용할 수 있게 유저 인터페이스가 구현되어 있다.
예를 들어 xend의 경우, libxenctrl을 통해 Hypercall을 호출, Xen에 직접 접근한다.
이렇듯 Para-virtualization의 경우 Guest OS의 커널 수정이 필요하다는 단점이 있지만, 명령어 변환 오버헤드를 제거할 수 있고, Xen은 오픈소스이기 때문에 필요하다면 임의의 Hypercall을 추가할 수 있다는 장점도 있다. 때문에 Xen은 연구 목적으로 많이 사용되었고, AWS에서도 Xen을 포크한 물건을 주 하이퍼바이저로 사용하였으나, 지금은 자체 하이퍼바이저로 변경하였다.
Intel VMX (Virtual Machine Extension) 또는 VT-x는 인텔이 Robin, J. & Irvine, C. (2000)
[2] 에 대해 내놓은 궁극적인 해답이다. 특권 모드의 동작과 비특권 모드의 동작이 다르다면, VM의 커널이 비특권 모드에서 동작할 수 있도록 하드웨어적으로 지원을 추가해 주면 되는 것이 아닐까?
여기서 먼저, x86 CPU의 Protection Level 개념인 Ring Level을 언급하고 지나가야 할 것 같다.
(x86의 Protection Ring - 출처: Wikipedia)
Ring-0는 OS 커널이 위치하는 레벨로, 시스템의 모든 자원에 접근할 수 있는 일종의 관리자 모드이다. Ring-1, Ring-2는 예약되어 있고, 사용자 애플리케이션은 Ring-3에서 동작한다.
그러나 앞서 이야기했듯이, 하이퍼바이저가 Ring-0에 올라가게 되면, OS 커널은 Ring-1 이상으로 올라가야 한다. 인텔은 이 문제를 해결하기 위해 하이퍼바이저를 위한 Ring-(-1)을 만들고, Ring-(-1)에 진입하기 위한 명령어들을 추가했다. 그리고 VM의 상태 관리를 위한 VMCS라는 구조체를 정의하고, 하이퍼바이저가 VMCS를 통해 CPU와 상호작용 할 수 있도록 하였다.
(VMCS의 State machine diagram - 출처: Intel Sofrware Developer's Manual: 3C))
Intel SDM에서 발췌한 아주 유명한 다이어그램이다.
VMX를 사용하면 VMM은 VMPTRLD를 통해 VMCS를 선택/활성화하고, 선택한 VMCS의 VM을 VMLAUNCH/VMRESUME으로 실행할 수 있다. VM이 실행되던 중 Fault가 발생하면 (Trap), VM Exit가 실행되고, 제어권은 VMM으로 넘어간다.
VMCS는 VM의 실행 상태 외에도 primary and shadow structures 에서 언급한 VM에 마스킹해야 할 데이터의 사본들을 포함하고 있다. 대표적으로 컨트롤 레지스터인 CR0, CR3, CR4와 디버그 레지스터, Selector/Segment 레지스터 등이 있고, 일부 MSR과 인터럽트 상태를 포함한다.
또한, VMX는 VMCS와 VM* 명령어 외에도 VPID의 지원을 추가한다. 자세한 내용은 메모리 가상화와 Shadow Page Table을 다루면서 다시 이야기 하겠지만, VPID는 가상 머신을 식별하기 위한 목적으로 TLB에 기록되는 ID이다. 이 기능은 네할렘 아키텍처에서 최초로 추가되었고, VM 환경에서 TLB Flush를 막는 역할을 수행한다.
이렇게 x86에서 하드웨어 기반 가상화가 구현되면서 CPU 가상화 시 성능 패널티의 주요 원인이었던 특권 명령어 처리를 위한 Trap, shadow structure 관리를 위한 오버헤드, VMM으로의 컨텍스트 스위치 시 발생하는 TLB Flush가 모두 제거되었다.
업계에서 널리 사용되는 VMware vSphere 7 Hypervisor (ESXi)의 vCPU 성능 오버헤드는 베어메탈 환경 대비 5% 미만이다.[6]
현 시점 기준으로 최소한 CPU에 대해서는 가상화의 성능 패널티가 거의 없다고 할 수 있으며, 이러한 VMX/SVM의 성능 향상 덕분에 VMware는 ESXi의 Binary Translation 지원을 vSphere 6.7에서 드랍하였다.[7]
이번 포스팅에서는 가상화의 역사와 x86 CPU 가상화를 다뤘다. 다음 포스팅에서 다룰 주제는 메모리 가상화이다.
[1] https://dl.acm.org/doi/pdf/10.1145/361011.361073
[2] https://www.usenix.org/legacy/events/sec2000/full_papers/robin/robin.pdf
[3] https://www.vmware.com/pdf/asplos235_adams.pdf
[4] https://pages.cs.wisc.edu/~stjones/proj/vm_reading/ibmrd2505M.pdf
[5] https://wiki.xenproject.org/images/b/b3/Xen_2_interface.pdf
[6] https://neil.computer/notes/bare-metal-vs-virtualization-performance/
[7] https://kb.vmware.com/s/article/2147608