-
아주아주 먼옛날 가상화 기술이 핫 할때 Intel과 ARM 같은 제조사들은 자사의 칩에서 동작하는 가상화 소프트웨어의 성능을 높이고자 하드웨어단에서 여러 옵션을 추가 했다. 소프트웨어 개발자들은 제조사들이 제공하는 옵션을 활용해 하이퍼바이저를 만들었는데 KVM 또한 이때 만들어진 하이퍼바이저중 하나다. 좀더 구체적으로 말하면 QEMU같은 Type2 소프트웨어가 하드웨어의 가상화 확장 기능을 쉽게 사용 할 수 있도록 인터페이스의 역할을 하는 커널의 모듈이다.그런데 제조사들은 가상화 기술의 성능을 높이기 위해 어떤 기능을 제공하고 있을까? 여러 OS를 동작하는 작업인 만큼 매우 오버헤드가 심할텐데 어떤 옵션이 있었기에 VMware로 리눅스가 쌩쌩 잘 돌아가는거지? 그리고 KVM은 하드웨어의 기능을 어떻게 반영 했을까? 기능을 반영하기 위한 특별한 구조적 의사 결정 과정은 없었을까? 파일과 코드의 양은 상당한데 말이다.
궁금증을 해소하고자 아키텍처단의 양대 산맥인 ARM과 x86에서 제공하는 가상화 기능을 살펴보고 이들의 특성이 어떻게 KVM에 적용됐는지를 아주 차근차근 분석해보려고 한다. 이번 포스트는 ARM에 대한 내용이다.
포스트를 작성하기 위해 KVM/ARM: The Design and Implementation of the Linux ARM Hypervisor 논문을 참조했다. 이 논문은 ARM에서 제공하는 가상화 기술이 나오게 된 계기와 작동 방식을 다루었고 위 특성을 반영한 KVM 코드를 리눅스 커널 메인라인에 반영하는 작업을 쭉 정리했다. 하드웨어 옵션을 쓰기 위한 여러 후보 구조를 도출하고 각각의 장단점을 분석한 후 선택하는 구조적 의사 결정 과정도 정리했는데 혹시 관심 있는 분들은 읽어보면 좋을 것 같다. ARM 아키텍처를 심도있게 이해하는데도 도움이 된다. 이번 포스트에서는 구조적 의사 결정의 결과만 다룰 예정이다.
꼼꼼하게 여러번 읽어 봤지만 글쓴이의 역량 부족으로 아직 온전히 이해하지 못했다. 잘 이해가 되지 못하는 부분은 두루뭉실 하게 쓴 것 같은데 이런 부분은 논문은 참조 해주셨으면 좋겠다. 친절하게 링크도 남긴다. 15달러는 못드린다.
1. CPU Virtualization, HYP Mode
HYP Mode는 SVC, USR 모드보다 더 높은 권한을 갖고 있는 CPU 모드중 하나다. SVC가 USR에서 동작하는 프로세스의 Trap을 받는 것처럼 HYP 모드는 SVC, USR의 Trap을 대신 받아 처리할 수 있으며 SVC, USR의 레지스터값들을 읽고 수정 할 수 있다. Exception Level 2로 불리기도 한다.
ARM에서 HYP 모드를 추가하게 된 배경은 Xen과 같은 standalone 하이퍼바이저 때문이다. Host OS 없이 동작하는 Type-1 하이퍼바이저의 경우에는 위에 동작하는 Guest OS보다 더 높은 권한을 쥐고 말썽을 부리는 경우 적극적으로 개입해 통제하는 역할을 해야한다. 그런데 CPU가 SVC, USR인 경우 이런 요구사항을 아키텍처적으로 설계하기가 애매하다. 하이퍼바이저의 권한을 살려 SVC 모드에 하이퍼바이저만 동작하게 하고 USR 모드에 Guest OS를 올리자니 SVC 모드에서 동작하는 걸 염두해두고 설계된 OS가 실행할 수 없게 된다. 그렇다고 Guest OS와 하이퍼바이저가 SVC 모드에 공존하게 하자니 Guest OS가 말썽 부리는 경우 대처 할 방법이 없다. 개념적으로는 훌륭한 구조였으나 SVC, USR 모드만으로는 설계가 불가능하다.
이런 문제를 해결하기위해 ARM은 SVC와 USR보다 권한이 더 높은 HYP 모드를 만들고 소프트웨어 개발자들로 하여금 하이퍼바이저를 HYP 모드에 넣도록 했다. 이것 덕분에 교통 정리가 끝났다. 이제 하이퍼바이저가 Guest OS에 제대로 통제권을 쥘 수 있게 되고 Guest OS는 SVC모드에서 동작하는 코드를 그대로 실행 할 수 있게 됐다.
KVM처럼 Type-2 하이퍼바이저는 좀 애매하다. Host OS의 모듈의 형태로 동작하기 때문에 일부 기능을 Host API에서 가져와야하는데 만약 Host가 EL1으로 동작하고 있다면 KVM또한 EL1에서 동작하고 있어야 한다. 그런데 Guest 보다 더 높은 권한을 가지려면 EL2에서 동작하기도 해야한다. 참 골치가 아픈 상황이다.
KVM은 두 가지 요구 사항을 반영해 구조를 Lowvisor/Highvisor로 두가지로 분리했다. Lowvisor는 HYP Mode에서 동작하는 Hypervisor이며 VM의 instruction을 trap하고 이를 Highvisor에게 전달한다. Highvisor는 Kernel Mode에서 동작하며 Host에 있는 커널 코드를 사용해 VM 운영중 필요한 기능을 제공한다.
Lowvisor에서 동작하는 코드는 Highvisor에 비하면 매우 적다. EL2 벡터테이블과 World Switch 사용되는 코드 일부 정도다. 코드의 양만 보면 Type1에 비하면 활용도는 적은것 같다.
그림 1. ARM/KVM 구조도. 논문 내용을 참조해서 그렸다.
논문에선 위와 같은 두개의 구조로(Highvisor/Lowvisor) 만들기 전에 아예 커널을 HYP Mode에서 부팅하는 작업도 고려했었다. Mode 변경으로 인한 cost를 줄일 수 있는 방법인데 왜 위 구조를 채택하지 않았는지는 논문을 참조해보길. 생각보다 간단한 이유였다.
* ARM은 하이퍼바이저를 넣으라고 HYP 모드를 설계 했는데 정작 퀄컴이나 삼성같은 칩 제조사들은 여기에 하드웨어 기능을 추가/보완 할 수 있는 소프트웨어 모듈을 넣고 있다. 역시 인생은 의도대로 흘러가지 않는 것 같다
2. Memory Virtualization
ARM은 Stage-2 Translation이란 기능을 제공했다. 간단히 말해서 Virtual Machine 내에서 도출한 물리 메모리 주소(IPA: Intermediate Physical Address)를 하드웨어 물리 주소(PA: Physical Memory)로 변환 할 수 있는 기능을 하드웨어 단에서 제공한다. 위 기능을 사용하기 전에는 IPA를 매번 소프트웨어적으로 물리 주소를 찾아가야 했고 Fault가 발생하면 처음부터 찾아야해 성능이 저하될 수 있는 문제가 있었다. 그런데 메모리 가상화 기능이 추가되면서 IPA 주소를 MMU에서 PA로 변환해줄 수 있게 됐다. KVM은 Host와 VM 간의 World Switch가 일어날 때 Stage-2 Translation 기능을 On/Off만 하면 된다. MMU는 Stage-2 Table의 register 주소 값을 참조해 변형한다.
그림 2. ARM/KVM 메모리 translation 작업.
3. Interrupt Virtualization
Interrupt Virtualization을 도입 하기 전에는 GIC의 CPU Interface를 VM과 공유하기 때문에 하드웨어에서 전달하는 Interrupt를 어디서 trap해 처리할 것인지가 주요한 이슈였다. Kernel 모드에서 모두 trap 한다면 효율적이나 Hypervisor가 하드웨어 통제권을 갖지 못하는 문제가 있고 HYP 모드에서 모두 처리한다면 매번 Interrupt를 처리하기 위해 HYP 모드로 변경해야 하는 오버헤드가 발생한다.
이 문제를 해결하고자 ARM에서는 CPU가 VM과 통신 할 수 있는 Virtual CPU Interface를 제공한다. 위 인터페이스를 활용해 CPU는 별다른 Mode 변경 없이 interrupt를 Kernel Mode에서 동작하는 VM에 전달 할 수 있게 된다. Hypervisor는 더이상 인터럽트를 대신 받고 에뮬레이션 해주지 않아돼 성능적으로 우수하며 또한 VM이 VGIC과 바로 연결돼 있기 떄문에 여전히 통제권을 가지고 있는 것으로 볼 수 있다. VM과 Host가 분리되어 있기 때문에 따로 운영이 가능하며 초기 VM을 생성할 때 VGIC과 잘 연결만 시켜주면 된다.
그림 3. VGIC interface
단 CPU간의 interrupt인 경우(Inter Processor Interrupt, IPI) Distributor를 거쳐야 하는에 이부분은 여전히 Host와 공유하는 영역이라서 Hypervisor의 통제를 받는다. KVM은 Highvisor에 Virtual Distributor를 두어서 받은 interrupt를 에뮬레이션해 전달한다.
4. Timer Virtualization
OS에서 스케줄링 할 때 주기적으로 프로세스가 CPU에서 동작 할 수 있는 시간을 할당하고 사용한 시간을 확인하게 되는데 이때 사용하는 기기가 타이머다.. 스케줄링의 주기는 ms 단위로 짧기 때문에 Host는 타이머를 주기적으로 자주 이용하고 있으며 VM 또한 내부적으로 스케줄링을 위해 에뮬레이션된 타이머에 자주 접근하게 되는데 이를 Hypervisor에서 모두 처리해준다면 매번 Mode Change가 일어나서 시스템 전반 성능에 부담이 된다.
이에 ARM에서는 Virtual Timer라는 것을 두어서 VM이 주기적으로 Virtual Timer 값을 읽을 수 있도록 했다. VM은 trap 모드로 이동하지 않도 타이머 값을 읽을 수 있어 Mode Change의 부담을 덜 수 있게 됐다. 단 Timer가 expire 돼 CPU에 interrupt를 보내야 하는 경우에는 HYP 모드로 이동해서 하이퍼바이저를 통해 Virtual Interrupt를 보낸다. 이는 아키텍처적인 고려사항이라고 한다.
사진 출처
그림 3. http://slideplayer.com/slide/7615418/
'개발 > 가상화' 카테고리의 다른 글
VFIO, Passthrough (0) 2018.06.30 QEMU 성능 문제 - 개론 (0) 2018.05.30 QEMU와 KVM - 2 (1) 2017.11.11 QEMU와 KVM - 1 (0) 2017.11.01 6. XenStore, Xenbus (0) 2017.01.22