가상화 기술 관련 공부를 심도있게 하고 싶은데 참고할 자료를 찾기 쉽지 않았다. 그러던 중 Virtual Machines : Versatile Platforms For Systems And Processes 라는 영문 원서의 pdf 본을 구할 수 있었는데, 가상화 기술 관련해서는 운영체제의 공룡책 만큼이나 바이블이라고 한다. 기술적으로 상당히 깊이 있어 보여서 이걸 완독을 하는 건 조금 나중의 일일 것 같고, 일단 개론 격에 해당하는 1장을 읽어서 내맘대로 정리 해본다.
이후 목차는 2장부터 6장까지는 JVM 같은 프로세스 레벨의 가상 머신을 다루고, 7장 ~ 9장 부분이 시스템 VM 을 다룬다. 책이 2005 년에 발간되었는데, 알아보니 Intel 의 VT-x 가 처음 도입된게 2005년이라고 해서 관련 내용이 없을 줄 알았는데 VT-x 도 직접적으로 언급이 되어있다. 추후 시간이 되는대로 7장 ~ 9장을 까지 마저 읽어봐야겠다.
1. 가상화 기본 개념
현대의 컴퓨터는 매우 복잡한 구조를 가지고 있다. 복잡성을 관리하는 열쇠는 다양한 층위의 추상화(abstraction) 와 잘 정의된 인터페이스이다. 예를 들어서 인텔의 CPU 는 IA-32(x86) 명령어 집합(IS, Instruction sets) 이라는 인터페이스를 정의하고 이를 구현함으로써 컴파일러가 고수준의 언어와 CPU 수준의 명령어를 맵핑할 수 있도록 한다. 추상화는 많은 장점이 있지만 한편으로는 일종의 제약(confining)으로 작용하기도 한다. 예를 들어서 특정 애플리케이션이 바이너리 코드로 배포된다면, 해당 애플리케이션을 운용하는 환경은 특정한 IS를 가진 CPU나 운영체제에 제한되게 된다. 이런 측면에서 보면 다양성이라는 요소가 혁신을 주도하기도 하지만 호환성(interoperability)을 떨어트리기도 한다.
가상화는 이러한 제약사항을 다소 완화(relaxing) 해준다. 가상화는 <guest 시스템과 host 시스템을 맵핑하는 동형사상(isomorphism)의 구축> 이다. 예를 들어서 하나의 큰 디스크를 여러개의 가상 디스크로 분할하는 것을 생각해보자. 가상화 소프트웨어는 이렇게 나뉘어진 가상 디스크(하나의 큰 파일) 파일을 실제 디스크에 맵핑을 해줌으로써 실제 논리적 트랙과 섹터가 있는 것처럼 보이게 한다. 가상 디스크에서 쓰기 작업을 하면, 호스트 시스템에서는 실제로는 파일 쓰기 작업으로 미러링 된다. (Isomorphism) . 이런 개념이 확장 되면 디스크 같은 세부 시스템이 아닌, 전체 machine 까지 가상화가 가능하다.
가상 머신은 에뮬레이션 기술(하드웨어에서 지원되지 않는 ISA 를 소프트웨어적으로 구현)을 도입해서 소프트웨어 호환성을 제공한다. 호스트 하드웨어가 지원하지 않는 다른 명령어 집합을 사용하는 소프트웨어를 가상머신이 적절하게 컨버팅해주는 것이다. 이러한 호환성(Compatibility) 은 시스템 레벨(Window to Mac) 또는 프로그램이나 프로세스 레벨(Excel on Solaris platform) 에서 모두 제공될 수 있다.
가상머신은 실제 존재하는 머신만을 맵핑해주는 것이 아니다. 실제로 존재하지 않는 가상의 머신도 실제 머신에 매핑해줄 수 있다. 예를 들어 JVM 은 실재하지 않는 완전한 가상의 머신이다. JVM 은 내부적으로 바이트 코드를 실행하는데, Interpreter 가 실제하는 호스트 머신의 기계어로 실시간 번역을 해주는 방식으로 동작한다. 이를 통해 엄청난 플랫폼 독립성을 가지게 되었다.
2. 가상머신이 구현하는 인터페이스
ISA (Instruction Sets Architecture)
ISA 는 소프트웨어와 하드웨어를 사이의 인터페이스이다. ISA 는 두가지 파트로 나뉘는데 애플리케이션 프로그램에게 보이는 부분(User ISA) 과 OS 측면에서 보이는 부분(System ISA) 이 있다. (System ISA 는 User ISA 를 포함함)
ABI (Application Binary Interface)
어플리케이션은 하드웨어 리소스에 직접적으로 접근을 하기도 하지만(User ISA 를 통해) 일부 하드웨어의 접근의 경우 운영체제가 정의하고 있는 인터페이스를 통해 간접적으로 접근한다.(시스템 콜) 이때 운영체제가 정의하는 규약에 따라 함수를 호출하고 인자들을 전달하게 되는데, 이 때문에 바이너리 컴파일된 프로그램은 ISA 에만 종속적인 것이 아니라 운영체제에도 종속되게 되는 것이다.
API (Application Program Interface)
API는 응용프로그램이 운영체제나 하드웨어와 상호작용하는 방법을 추상화한 인터페이스이다. 사용자는 복잡한 시스템의 세부 사항을 몰라도, API를 통해 쉽게 기능을 사용할 수 있게된다. API의 일부 루틴(함수) 은 운영 체제와 직접 소통하는 ABI 수준의 시스템 콜을 포함한다. 예를 들어서 malloc() 같은 API 함수는 운영 체제에 메모리를 요청할 때, 시스템 콜을 통해 메모리를 할당받는 복잡한 과정을 숨기고, 개발자는 단순히 malloc()을 호출하도록 해준다.
3. 시스템 VM 과 프로세스 VM
가상머신을 이해하기 위해서는 '머신' 이라는 단어가 어떻게 정의되는지를 알아야한다. 머신이라는 말은 관점에 따라서 다른 의미를 지닌다. 운영체제 위에서 동작하는 Process 의 관점에서 보면, Process 에게 허용된 CPU 자원 (User IS) 과 I/O 작업을 위해서 운영체제에게 간접적으로 요청하게 되는 시스템 콜 부분 (종합해서 ABI) 만이 Process 와 Machine 간의 인터페이스를 구현하는 것이 머신이 된다.
운영체제의 관점에서 보면 모든 CPU 를 실행할 수 있는 환경이 주어져야 하며, 논리적인 메모리 주소를 가지고 있는 프로세스와는 다르게 실제 물리 장치의 주소값을 가지고 있어야 하며, I/O 장치와의 소통도 가능해야 한다. 생성되고 사라지는 Process 의 생명주기와는 다르게 끄고 켤 수도 있어야 한다. 그러므로 CPU 뿐만 아니라 모든 하드웨어 시스템에 대한 인터페이스를 구현하여야 머신으로서 기능하게 된다.
이 두가지 종류의 머신을 프로세스 VM / 시스템 VM 이라고 지칭할 수 있다. 특히 프로세스 VM 은 '런타임' 이라고 지칭하기도 한다.
4. VM 의 종류
1) 프로세스 레벨의 VM
멀티프로그래밍
아주 당연하게 생각하지만, 하드웨어를 시분할하여 사용하게 하는 운영체제 또한 프로세스 수준의 가상머신이라고 지칭할 수 있다. 각각의 프로세스는 완전한 하나의 머신위에서 동작하는 것처럼 보이기 때문이다.
에물레이터와 다이나믹 바이너리 트랜스레이터(Dynamic Binary Translator)
가상의 하드웨어 환경을 구성하는 것을 에뮬레이트(Emulate) 라고 한다. 이를 위해서는 가상의 하드웨어에 주어지는 명령을 실제 물리머신의 명령어로 변환해야한다. 예를 들어 특정 어플리케이션 프로그램이 특정 CPU 아키텍쳐에서 실행되도록 컴파일 되었을 때(source ISA), 이를 다른 CPU 아키텍쳐 하드웨어 환경(target ISA)에서 구동하기 위해서는 ISA 간 변환이 필요할 것이다.
이를 구현하는 가장 직관적인 방법은 인터프리터에 의한 인터프리테이션(Interpretation) 이다. 인터프리터는 source ISA 를 위한 코드를 한줄씩 읽어서 해석하여 target ISA 하드웨어를 실행해준다. 이 때 source ISA 명령어 하나를 인터프리테이션 하기 위해서 수십개의 target ISA 를 필요로 하는 경우도 있기 때문에 이 과정은 굉장히 느려질 수 있다.
이 때 성능을 더 높이기 위한 방법이 Binary Translation 이다. 이 방법은 인터프리터 방식처럼 한줄 한줄 코드를 해석하는 것과는 다르게 블록 단위로 변환을 시도한다. 블록 단위로 변환하게 되면 더 효율적인 변환이 가능하며 자체적인 최적화 알고리즘을 도입할 수도 있다. 이렇게 해석된 명령어들은 캐싱되어 반복적으로 실행될 수 있기 때문에 인터프리터 방식보다 더 빠르다. 프로세스 VM 에서는 이 방식이 성능에 아주 중요한 영향을 가지고 있기 때문에 Dynamic Binary Translator 라고 불리기도 한다.
인터프리테이션은 초기 구동은 빠르나, 명령어 번역과 실행에 많은 시간이 드는 반면 Binary Translation 은 초기 번역에 시간이 많이 드는데 그 이후의 반복 수행에서 빠른 성능을 보인다. 그래서 두가지 전략을 결합해서 프로파일링이라는 걸 하게 된다. 프로그램의 행동을 통계적으로 분석해서, 자주 실행되는 코드만을 선택적으로 Translation 하는 것이다. 이 때에도 Binary Translation 시에 로직을 최적화하게 되는데 이러한 최적화의 효용을 위해서 소스와 타겟의 ISA 가 동일한 경우에도 가상머신을 쓰기도 한다.(same-ISA Dynamic Binary Translation)
// 대표적으로 JVM 엔진에서 Interpreter 와 JIT Compiler 의 관계가 위와 같다.
2) 시스템 레벨의 VM
완전한 시스템을 제공하는 가상머신을 말한다. 1960 년대 ~ 1970 년대 초반에 처음 개발이 되었고 이것이 Virtual Machine 이라는 말의 기원이 된다. 당시에는 매우 크고 비싼 메인프레임을 여러 유저들이 동시에 사용하곤 했는데, 이를 위해서 다른 운영체제를 여러개 운용하고 싶었기 때문이었다. 그러나 컴퓨터가 작고 저렴해지면서 이런 수요는 줄어들게 되었다. 이제 다시 가상머신의 수요가 급증하고 있다. 이러한 인기를 끄는 가장 큰 이유는 가상머신이 보안이나 장애의 위협에 처했을 때 다른 가상머신에게 아무런 영향을 미치지 않는다는 격리성 덕분이었다.
가상머신 위에 운용되는 게스트 OS VMM (Virtual Machine Manager) 에 의해서 관리가 된다. 만약 게스트 OS 에서 하드웨어 자원의 직접적인 개입이 필요한 특권 명령같은 작업을 수행하려고 하면, VMM 이 이 요청을 가로채어 대신 수행해준다. 이로써 게스트 OS 는 VMM 의 존재를 모르게 된다.
VM 은 어떻게 구현되는지에 따라서 여러 종류로 나누어지는데, 크게 두가지로 볼 수 있다. 하나는 VMM 을 베어메탈 위에 설치하고 그 위에 게스트 OS 들을 설치하는 것이다. 이 경우 VMM 이 최고 권한을 가지게 되고 게스트 OS 는 그보다 낮은 수준의 권한을 부여 받으며, 앞서 설명한대로 VMM 이 게스트 OS 의 권한 밖의 요청들을 가로채어 처리해준다. 이때 VMM 은 실제 I/O 장치들의 드라이버가 설치되어 있어야 하며(I/O 와 소통하는 것은 VMM 이므로) 설치가 조금 복잡하다는 단점이 있다.
또 다른 형태의 가상 머신은 이미 존재하는 호스트 OS 위에 가상머신 소프트 웨어를 설치하는 것인데 이 경우 가상머신이 프로세스처럼 동작하게 되며, 다른 특권 명령들은 VMM 의 지원 없이 호스트 OS 를 통해서 수행한다. (그래서 Hosted VM 이라고 부르기도 한다.) 이 경우 가상머신이 전자의 방법보다 더 많은 계층의 개입을 통해 구현되므로, 덜 효율적인 편이다.
// 내가 Hypervisor 라고 알고 있는 개념을 VMM 이라고 지칭하고 있는데, 책의 9 챕터에서 부터는 Hypervisor 에 대한 설명이 나오고 이후 부터 Hypervisor 에 대해 깊이 다룬다.
만약 PowerPC 위에서 구동되는 Mac OS 피씨에서 윈도우를 구동하고 싶다면 어떻게 해야할까? 두 OS 는 강한 하드웨어 종속성이 있기 때문에 가상 머신이 서로 다른 ISA 환경을 에뮬레이트 해야할 것이다. 그리고 게스트의 모든 ISA 명령을 호스트 OS 콜로 맵핑하여야 한다. binary translation 이 사용된다고 해도 가상 메모리 관리나 트랩 핸들링 등 전혀 다른 ISA 환경으로 구현되어야 하므로 많은 제약이 따를것이다.