Android는 Android Virtualization Framework를 구현하는 데 필요한 모든 구성 요소의 참조 구현을 제공합니다. 현재 이 구현은 ARM64로 제한됩니다. 이 페이지에서는 프레임워크 아키텍처에 대해 설명합니다.
배경
Arm 아키텍처는 최대 4개의 예외 수준을 허용합니다. 예외 수준 0(EL0)은 최소 권한이고 예외 수준 3(EL3)은 최대입니다. Android 코드베이스(모든 사용자 공간 구성 요소)의 가장 큰 부분은 EL0에서 실행됩니다. 일반적으로 "Android"라고 불리는 나머지 부분은 EL1에서 실행되는 Linux 커널입니다.
EL2 레이어를 사용하면 강력한 기밀성과 무결성을 보장하면서 EL1/EL0에서 메모리와 장치를 개별 pVM으로 격리할 수 있는 하이퍼바이저를 도입할 수 있습니다.
하이퍼바이저
보호된 커널 기반 가상 머신(pKVM) 은 생성 당시 '보호됨'으로 표시된 게스트 가상 머신에서 실행되는 페이로드에 대한 액세스를 제한하는 기능으로 확장된 Linux KVM 하이퍼바이저 를 기반으로 합니다.
KVM/arm64는 특정 CPU 기능, 즉 VHE(가상화 호스트 확장)(ARMv8.1 이상)의 가용성에 따라 다른 실행 모드를 지원합니다. 일반적으로 비 VHE 모드로 알려진 이러한 모드 중 하나에서 하이퍼바이저 코드는 부팅 중에 커널 이미지에서 분리되어 EL2에 설치되는 반면 커널 자체는 EL1에서 실행됩니다. Linux 코드베이스의 일부이지만 KVM의 EL2 구성 요소는 여러 EL1 간의 전환을 담당하는 작은 구성 요소이며 호스트의 커널에 의해 완전히 제어됩니다. 하이퍼바이저 구성 요소는 Linux로 컴파일되지만 vmlinux
이미지의 별도의 전용 메모리 섹션에 있습니다. pKVM은 새로운 기능으로 하이퍼바이저 코드를 확장하여 Android 호스트 커널 및 사용자 공간을 제한하고 게스트 메모리 및 하이퍼바이저에 대한 호스트 액세스를 제한함으로써 이 설계를 활용합니다.
부팅 절차
pKVM 부팅 절차는 그림 1에 나와 있습니다. 첫 번째 단계는 부트로더가 EL2에서 pKVM 지원 Linux 커널로 들어가는 것입니다. 초기 부팅 동안 커널은 EL2에서 실행 중임을 감지하고 EL1에 대한 권한을 박탈하고 pKVM을 남깁니다. 이 시점부터 Linux 커널은 사용자 공간에 도달할 때까지 필요한 모든 장치 드라이버를 로드하면서 정상적으로 부팅을 진행합니다. 이러한 단계는 pKVM을 제어하는 동안 발생합니다.
부트 절차는 부트로더를 신뢰하여 초기 부트 중에만 커널 이미지의 무결성을 유지합니다. 커널에 권한이 없으면 더 이상 하이퍼바이저에서 신뢰할 수 있는 것으로 간주되지 않으며, 하이퍼바이저는 커널이 손상된 경우에도 스스로를 보호해야 합니다.
Android 커널과 하이퍼바이저가 동일한 바이너리 이미지에 있으면 둘 사이에 매우 긴밀하게 연결된 통신 인터페이스가 허용됩니다. 이 긴밀한 결합은 두 구성 요소의 원자적 업데이트를 보장하므로 두 구성 요소 간의 인터페이스를 안정적으로 유지할 필요가 없으며 장기적인 유지 관리 가능성을 손상시키지 않으면서 상당한 유연성을 제공합니다. 또한 긴밀한 결합을 통해 두 구성 요소가 하이퍼바이저에서 제공하는 보안 보장에 영향을 주지 않고 협력할 수 있을 때 성능을 최적화할 수 있습니다.
또한 Android 생태계에서 GKI를 채택하면 자동으로 pKVM 하이퍼바이저가 커널과 동일한 바이너리로 Android 장치에 배포될 수 있습니다.
CPU 메모리 액세스 보호
Arm 아키텍처는 메모리의 다른 부분에 대한 주소 변환 및 액세스 제어를 구현하는 데 사용할 수 있는 두 개의 독립적인 단계로 분할된 MMU(메모리 관리 장치)를 지정합니다. 1단계 MMU는 EL1에 의해 제어되며 첫 번째 수준의 주소 변환을 허용합니다. 1단계 MMU는 Linux에서 각 사용자 공간 프로세스 및 자체 가상 주소 공간에 제공되는 가상 주소 공간을 관리하는 데 사용됩니다.
2단계 MMU는 EL2에 의해 제어되며 1단계 MMU의 출력 주소에 두 번째 주소 변환을 적용하여 물리적 주소(PA)를 생성합니다. 2단계 변환은 하이퍼바이저에서 모든 게스트 VM의 메모리 액세스를 제어하고 변환하는 데 사용할 수 있습니다. 그림 2와 같이 두 변환 단계가 모두 활성화된 경우 단계 1의 출력 주소를 중간 물리적 주소(IPA)라고 합니다. 참고: 가상 주소(VA)는 IPA로 변환된 다음 PA로 변환됩니다.
역사적으로 KVM은 게스트를 실행하는 동안 2단계 변환이 활성화된 상태로 실행되고 호스트 Linux 커널을 실행하는 동안 2단계가 비활성화된 상태로 실행됩니다. 이 아키텍처는 호스트 스테이지 1 MMU의 메모리 액세스가 스테이지 2 MMU를 통과하도록 하여 호스트에서 게스트 메모리 페이지로의 무제한 액세스를 허용합니다. 반면에 pKVM은 호스트 컨텍스트에서도 2단계 보호를 가능하게 하고 호스트 대신 게스트 메모리 페이지를 보호하는 역할을 하이퍼바이저에 넣습니다.
KVM은 2단계에서 주소 변환을 최대한 활용하여 게스트에 대한 복잡한 IPA/PA 매핑을 구현하므로 물리적 단편화에도 불구하고 게스트에 대한 연속 메모리의 환상을 만듭니다. 그러나 호스트에 대한 2단계 MMU의 사용은 액세스 제어로만 제한됩니다. 호스트 단계 2는 ID 매핑되어 호스트 IPA 공간의 연속 메모리가 PA 공간에서 연속되도록 합니다. 이 아키텍처를 사용하면 페이지 테이블에서 대규모 매핑을 사용할 수 있으므로 결과적으로 TLB(Translation Lookaside 버퍼)에 대한 압력이 줄어듭니다. ID 매핑은 PA에 의해 인덱싱될 수 있으므로 호스트 2단계는 페이지 테이블에서 직접 페이지 소유권을 추적하는 데에도 사용됩니다.
직접 메모리 액세스(DMA) 보호
앞에서 설명한 것처럼 CPU 페이지 테이블의 Linux 호스트에서 게스트 페이지 매핑을 해제하는 것은 게스트 메모리를 보호하는 데 필요한 단계이지만 충분하지 않습니다. pKVM은 또한 호스트 커널의 제어 하에 DMA 가능 장치에 의한 메모리 액세스와 악의적인 호스트에 의해 시작된 DMA 공격 가능성으로부터 보호해야 합니다. 이러한 장치가 게스트 메모리에 액세스하는 것을 방지하기 위해 pKVM은 그림 3과 같이 시스템의 모든 DMA 가능 장치에 대해 입출력 메모리 관리 장치(IOMMU) 하드웨어가 필요합니다.
최소한 IOMMU 하드웨어는 페이지 단위에서 물리적 메모리에 대한 장치에 대한 읽기/쓰기 액세스 권한을 부여하고 취소하는 수단을 제공합니다. 그러나 이 IOMMU 하드웨어는 ID 매핑 단계 2를 가정하므로 pVM에서 장치 사용을 제한합니다.
가상 머신 간의 격리를 보장하려면 적절한 페이지 테이블 세트가 변환에 사용될 수 있도록 다른 엔티티를 대신하여 생성된 메모리 트랜잭션을 IOMMU에서 구별할 수 있어야 합니다.
또한 EL2에서 SoC 관련 코드의 양을 줄이는 것은 pKVM의 전체 TCB(신뢰할 수 있는 컴퓨팅 기반)를 줄이는 핵심 전략이며 하이퍼바이저에 IOMMU 드라이버를 포함하는 것과 반대로 실행됩니다. 이 문제를 완화하기 위해 EL1의 호스트는 전원 관리, 초기화 및 적절한 경우 인터럽트 처리와 같은 보조 IOMMU 관리 작업을 담당합니다.
그러나 호스트가 장치 상태를 제어하도록 하면 IOMMU 하드웨어의 프로그래밍 인터페이스에 대한 추가 요구 사항이 적용되어 장치 재설정 후와 같은 다른 수단으로 권한 검사를 우회할 수 없도록 합니다.
격리와 직접 할당을 모두 가능하게 하는 Arm 장치용으로 잘 지원되는 표준 IOMMU는 Arm SMMU(시스템 메모리 관리 장치) 아키텍처입니다. 이 아키텍처는 권장되는 참조 솔루션입니다.
메모리 소유권
부팅 시 하이퍼바이저가 아닌 모든 메모리는 호스트가 소유한 것으로 가정하고 하이퍼바이저에서 이를 추적합니다. pVM이 생성되면 호스트는 메모리 페이지를 제공하여 부팅을 허용하고 하이퍼바이저는 해당 페이지의 소유권을 호스트에서 pVM으로 전환합니다. 따라서 하이퍼바이저는 호스트의 2단계 페이지 테이블에 액세스 제어 제한을 두어 호스트가 페이지에 다시 액세스하는 것을 방지하고 게스트에게 기밀성을 제공합니다.
호스트와 게스트 간의 통신은 제어된 메모리 공유를 통해 가능합니다. 게스트는 하이퍼바이저가 호스트 2단계 페이지 테이블에서 해당 페이지를 다시 매핑하도록 지시하는 하이퍼콜을 사용하여 호스트와 일부 페이지를 다시 공유할 수 있습니다. 마찬가지로 호스트와 TrustZone의 통신은 메모리 공유 및/또는 대여 작업을 통해 가능하며, 이 모든 작업은 FF-A(Firmware Framework for Arm) 사양을 사용하여 pKVM에서 면밀히 모니터링 및 제어됩니다.
하이퍼바이저는 시스템에 있는 모든 메모리 페이지의 소유권과 해당 페이지가 다른 엔티티에 공유 또는 임대되는지 여부를 추적하는 역할을 합니다. 이 상태 추적의 대부분은 호스트 및 게스트의 2단계 페이지 테이블에 연결된 메타데이터를 사용하여 수행되며 이름에서 알 수 있듯이 소프트웨어 사용을 위해 예약된 PTE(페이지 테이블 항목)의 예약된 비트를 사용합니다.
호스트는 하이퍼바이저가 액세스할 수 없게 만든 페이지에 액세스를 시도하지 않도록 해야 합니다. 잘못된 호스트 액세스로 인해 하이퍼바이저에 의해 호스트에 동기 예외가 주입되어 담당 사용자 공간 작업이 SEGV 신호를 수신하거나 호스트 커널이 충돌할 수 있습니다. 우발적인 액세스를 방지하기 위해 게스트에게 제공되는 페이지는 호스트 커널에서 스왑 또는 병합할 수 없습니다.
인터럽트 처리 및 타이머
인터럽트는 게스트가 장치와 상호 작용하는 방식과 CPU 간의 통신에 필수적인 부분입니다. 여기서 IPI(프로세서 간 인터럽트)는 주요 통신 메커니즘입니다. KVM 모델은 모든 가상 인터럽트 관리를 EL1의 호스트에 위임하는 것으로, 이를 위해 하이퍼바이저의 신뢰할 수 없는 부분으로 작동합니다.
pKVM은 기존 KVM 코드를 기반으로 하는 전체 GICv3(Generic Interrupt Controller version 3) 에뮬레이션을 제공합니다. 타이머와 IPI는 이 신뢰할 수 없는 에뮬레이션 코드의 일부로 처리됩니다.
GICv3 지원
EL1과 EL2 사이의 인터페이스는 인터럽트와 관련된 하이퍼바이저 레지스터의 복사본을 포함하여 전체 인터럽트 상태가 EL1 호스트에 표시되도록 해야 합니다. 이 가시성은 일반적으로 가상 CPU(vCPU)당 하나씩 공유 메모리 영역을 사용하여 달성됩니다.
시스템 레지스터 런타임 지원 코드는 SGIR(소프트웨어 생성 인터럽트 레지스터) 및 DIR(인터럽트 레지스터 비활성화) 레지스터 트래핑만 지원하도록 단순화할 수 있습니다. 아키텍처는 이러한 레지스터가 항상 EL2에 트랩되는 반면, 다른 트랩은 지금까지 정오표를 완화하는 데만 유용했습니다. 다른 모든 것은 하드웨어에서 처리됩니다.
MMIO 측에서는 모든 것이 EL1에서 에뮬레이트되어 KVM의 모든 현재 인프라를 재사용합니다. 마지막으로 WFI(Wait for Interrupt) 는 KVM이 사용하는 기본 스케줄링 프리미티브 중 하나이기 때문에 항상 EL1으로 중계됩니다.
타이머 지원
가상 타이머의 비교기 값은 vCPU가 차단되는 동안 EL1이 타이머 인터럽트를 삽입할 수 있도록 각 트래핑 WFI에서 EL1에 노출되어야 합니다. 물리적 타이머는 완전히 에뮬레이트되며 모든 트랩은 EL1에 릴레이됩니다.
MMIO 처리
VMM(가상 머신 모니터)과 통신하고 GIC 에뮬레이션을 수행하려면 추가 분류를 위해 MMIO 트랩을 EL1의 호스트로 다시 릴레이해야 합니다. pKVM에는 다음이 필요합니다.
- IPA 및 액세스 규모
- 쓰기의 경우 데이터
- 트래핑 시점의 CPU 엔디안
또한 소스/대상으로 범용 레지스터(GPR)가 있는 트랩은 추상 전송 의사 레지스터를 사용하여 릴레이됩니다.
게스트 인터페이스
게스트는 트랩된 영역에 대한 하이퍼콜 및 메모리 액세스 조합을 사용하여 보호된 게스트와 통신할 수 있습니다. 하이퍼콜은 SMCCC 표준 에 따라 KVM에 의해 벤더 할당을 위해 예약된 범위와 함께 노출됩니다. 다음 하이퍼콜은 pKVM 게스트에게 특히 중요합니다.
일반 하이퍼콜
- PSCI는 게스트가 온라인, 오프라인 및 시스템 종료를 포함한 vCPU의 수명 주기를 제어할 수 있는 표준 메커니즘을 제공합니다.
- TRNG는 게스트가 EL3에 대한 호출을 중계하는 pKVM에서 엔트로피를 요청할 수 있는 표준 메커니즘을 제공합니다. 이 메커니즘은 호스트가 하드웨어 RNG(난수 생성기)를 가상화하도록 신뢰할 수 없는 경우에 특히 유용합니다.
pKVM 하이퍼콜
- 호스트와 메모리 공유. 모든 게스트 메모리는 처음에 호스트에 액세스할 수 없지만 공유 메모리 통신 및 공유 버퍼에 의존하는 반가상화 장치에는 호스트 액세스가 필요합니다. 호스트와 페이지 공유 및 공유 해제를 위한 하이퍼콜을 통해 게스트는 핸드셰이크 없이 Android의 나머지 부분에서 액세스할 수 있는 메모리 부분을 정확히 결정할 수 있습니다.
- 호스트에 대한 메모리 액세스 트래핑. 일반적으로 KVM 게스트가 유효한 메모리 영역에 해당하지 않는 주소에 액세스하면 vCPU 스레드가 호스트로 종료되고 액세스는 일반적으로 MMIO에 사용되고 사용자 공간에서 VMM에 의해 에뮬레이트됩니다. 이 처리를 용이하게 하기 위해 pKVM은 주소, 등록 매개변수 및 잠재적으로 해당 내용과 같은 오류가 있는 명령에 대한 세부 정보를 호스트에 다시 알려야 합니다. 그러면 트랩이 예상되지 않은 경우 보호된 게스트의 민감한 데이터가 의도하지 않게 노출될 수 있습니다. pKVM은 게스트가 이전에 오류가 있는 IPA 범위를 호스트로 트랩백할 수 있는 액세스가 허용된 범위로 식별하기 위해 하이퍼콜을 실행하지 않은 경우 이러한 오류를 치명적으로 처리하여 이 문제를 해결합니다. 이 솔루션을 MMIO 가드 라고 합니다.
가상 I/O 장치(virtio)
Virtio는 반가상화 장치를 구현하고 상호 작용하기 위한 대중적이고 이식 가능하며 성숙한 표준입니다. 보호된 게스트에 노출되는 대부분의 장치는 virtio를 사용하여 구현됩니다. Virtio는 또한 보호된 게스트와 나머지 Android 간의 통신에 사용되는 vsock 구현을 지원합니다.
Virtio 장치는 일반적으로 게스트에서 virtio 장치의 MMIO 인터페이스로의 트랩된 메모리 액세스를 가로채고 예상되는 동작을 에뮬레이트하는 VMM에 의해 호스트의 사용자 공간에서 구현됩니다. MMIO 액세스는 장치에 액세스할 때마다 VMM으로 왕복해야 하기 때문에 상대적으로 비용이 많이 듭니다. 따라서 장치와 게스트 간의 실제 데이터 전송의 대부분은 메모리의 virtqueue 집합을 사용하여 발생합니다. virtio의 주요 가정은 호스트가 게스트 메모리에 임의로 액세스할 수 있다는 것입니다. 이 가정은 장치 에뮬레이션이 직접 액세스하도록 의도된 게스트의 버퍼에 대한 포인터를 포함할 수 있는 virtqueue의 설계에서 분명합니다.
앞에서 설명한 메모리 공유 하이퍼콜을 사용하여 게스트에서 호스트로 virtio 데이터 버퍼를 공유할 수 있지만 이 공유는 반드시 페이지 단위에서 수행되며 버퍼 크기가 페이지 크기보다 작으면 필요한 것보다 더 많은 데이터를 노출하게 될 수 있습니다. . 대신 게스트는 공유 메모리의 고정 창에서 virtqueue와 해당 데이터 버퍼를 모두 할당하도록 구성되며 필요에 따라 창으로 데이터가 복사(바운스)됩니다.
TrustZone과의 상호 작용
게스트가 TrustZone과 직접 상호 작용할 수는 없지만 호스트는 여전히 SMC 호출을 보안 세계로 보낼 수 있어야 합니다. 이러한 호출은 호스트에 액세스할 수 없는 물리적으로 주소가 지정된 메모리 버퍼를 지정할 수 있습니다. 보안 소프트웨어는 일반적으로 버퍼의 액세스 가능성을 인식하지 못하기 때문에 악의적인 호스트는 이 버퍼를 사용하여 혼란스러운 대리 공격(DMA 공격과 유사)을 수행할 수 있습니다. 이러한 공격을 방지하기 위해 pKVM은 EL2에 대한 모든 호스트 SMC 호출을 트랩하고 EL3에서 호스트와 보안 모니터 사이의 프록시 역할을 합니다.
호스트의 PSCI 호출은 최소한의 수정으로 EL3 펌웨어로 전달됩니다. 특히 CPU가 온라인 상태가 되거나 일시 중단에서 다시 시작하기 위한 진입점이 EL1에서 호스트로 돌아가기 전에 EL2에서 스테이지 2 페이지 테이블이 설치되도록 다시 작성됩니다. 부팅하는 동안 이 보호 기능은 pKVM에 의해 시행됩니다.
이 아키텍처는 PSCI를 지원하는 SoC에 의존하며, 바람직하게는 최신 버전의 TF-A 를 EL3 펌웨어로 사용합니다.
Arm용 펌웨어 프레임워크(FF-A)는 특히 보안 하이퍼바이저가 있는 경우 일반 세계와 보안 세계 간의 상호 작용을 표준화합니다. 사양의 주요 부분은 공통 메시지 형식과 기본 페이지에 대해 잘 정의된 권한 모델을 모두 사용하여 보안 세계와 메모리를 공유하는 메커니즘을 정의합니다. pKVM은 FF-A 메시지를 프록시하여 호스트가 충분한 권한이 없는 보안 측과 메모리 공유를 시도하지 않도록 합니다.
이 아키텍처는 메모리 액세스 모델을 적용하는 보안 세계 소프트웨어에 의존하여 보안 세계에서 실행되는 신뢰할 수 있는 앱 및 기타 소프트웨어가 보안 세계가 독점적으로 소유하거나 FF를 사용하여 명시적으로 공유된 경우에만 메모리에 액세스할 수 있도록 합니다. -ㅏ. S-EL2가 있는 시스템에서 메모리 액세스 모델을 적용하는 것은 보안 세계를 위한 2단계 페이지 테이블을 유지 관리하는 Hafnium 과 같은 SPMC(Secure Partition Manager Core)에서 수행해야 합니다. S-EL2가 없는 시스템에서 TEE는 대신 1단계 페이지 테이블을 통해 메모리 액세스 모델을 시행할 수 있습니다.
EL2에 대한 SMC 호출이 PSCI 호출 또는 FF-A 정의 메시지가 아닌 경우 처리되지 않은 SMC는 EL3으로 전달됩니다. 펌웨어가 pVM 격리를 유지하는 데 필요한 예방 조치를 이해하기 때문에 (필수적으로 신뢰할 수 있는) 보안 펌웨어가 처리되지 않은 SMC를 안전하게 처리할 수 있다고 가정합니다.
가상 머신 모니터
crosvm은 Linux의 KVM 인터페이스를 통해 가상 머신을 실행하는 VMM(가상 머신 모니터)입니다. crosvm을 독특하게 만드는 것은 호스트 커널을 보호하기 위해 가상 장치 주변의 샌드박스와 Rust 프로그래밍 언어를 사용하여 안전에 중점을 둡니다.
파일 설명자와 ioctl
KVM은 KVM API를 구성하는 ioctl을 사용하여 /dev/kvm
문자 장치를 사용자 공간에 노출합니다. ioctls는 다음 범주에 속합니다.
- 시스템 ioctls는 전체 KVM 하위 시스템에 영향을 미치는 전역 속성을 쿼리 및 설정하고 pVM을 생성합니다.
- VM ioctls는 가상 CPU(vCPU) 및 장치를 생성하고 메모리 레이아웃, 가상 CPU(vCPU) 및 장치 수를 포함하여 전체 pVM에 영향을 미치는 속성을 쿼리 및 설정합니다.
- vCPU ioctls는 단일 가상 CPU의 작동을 제어하는 속성을 쿼리하고 설정합니다.
- 장치 ioctls는 단일 가상 장치의 작동을 제어하는 속성을 쿼리하고 설정합니다.
각 crosvm 프로세스는 정확히 하나의 가상 머신 인스턴스를 실행합니다. 이 프로세스는 KVM_CREATE_VM
시스템 ioctl을 사용하여 pVM ioctl을 발행하는 데 사용할 수 있는 VM 파일 설명자를 생성합니다. VM FD의 KVM_CREATE_VCPU
또는 KVM_CREATE_DEVICE
ioctl은 vCPU/장치를 생성하고 새 리소스를 가리키는 파일 설명자를 반환합니다. vCPU 또는 장치 FD의 ioctl은 VM FD의 ioctl을 사용하여 생성된 장치를 제어하는 데 사용할 수 있습니다. vCPU의 경우 여기에는 게스트 코드를 실행하는 중요한 작업이 포함됩니다.
내부적으로 crosvm은 edge-triggered epoll
인터페이스를 사용하여 커널에 VM의 파일 설명자를 등록합니다. 그런 다음 커널은 파일 설명자에서 보류 중인 새 이벤트가 있을 때마다 crosvm에 알립니다.
pKVM은 pVM 환경에 대한 정보를 얻고 VM에 대한 보호 모드를 설정하는 데 사용할 수 있는 KVM_CAP_ARM_PROTECTED_VM
이라는 새로운 기능을 추가합니다. crosvm은 --protected-vm
플래그가 전달되는 경우 pVM 생성 중에 이를 사용하여 pVM 펌웨어에 대한 적절한 양의 메모리를 쿼리 및 예약한 다음 보호 모드를 활성화합니다.
메모리 할당
VMM의 주요 책임 중 하나는 VM의 메모리를 할당하고 메모리 레이아웃을 관리하는 것입니다. crosvm은 아래 표에 느슨하게 설명된 고정 메모리 레이아웃을 생성 합니다.
일반 모드의 FDT | PHYS_MEMORY_END - 0x200000 |
자유 공간 | ... |
램디스크 | ALIGN_UP(KERNEL_END, 0x1000000) |
핵심 | 0x80080000 |
부트로더 | 0x80200000 |
BIOS 모드의 FDT | 0x80000000 |
물리적 메모리 기반 | 0x80000000 |
pVM 펌웨어 | 0x7FE00000 |
장치 메모리 | 0x10000 - 0x40000000 |
물리적 메모리는 mmap
으로 할당되고 메모리는 KVM_SET_USER_MEMORY_REGION
ioctl로 memslots 라고 하는 메모리 영역을 채우기 위해 VM에 제공됩니다. 따라서 모든 게스트 pVM 메모리는 이를 관리하는 crosvm 인스턴스에 기인하며 호스트에서 여유 메모리가 부족해지기 시작하면 프로세스가 종료(VM 종료)될 수 있습니다. VM이 중지되면 메모리가 하이퍼바이저에 의해 자동으로 지워지고 호스트 커널로 반환됩니다.
일반 KVM에서 VMM은 모든 게스트 메모리에 대한 액세스를 유지합니다. pKVM을 사용하면 게스트 메모리가 게스트에게 제공될 때 호스트 물리적 주소 공간에서 매핑이 해제됩니다. 유일한 예외는 virtio 장치와 같이 게스트가 명시적으로 다시 공유하는 메모리입니다.
게스트 주소 공간의 MMIO 영역은 매핑되지 않은 상태로 유지됩니다. 게스트가 이러한 영역에 액세스하면 트랩되고 VM FD에서 I/O 이벤트가 발생합니다. 이 메커니즘은 가상 장치를 구현하는 데 사용됩니다. 보호 모드에서 게스트는 실수로 정보가 누출될 위험을 줄이기 위해 하이퍼콜을 사용하는 MMIO에 주소 공간의 영역이 사용됨을 확인해야 합니다.
스케줄링
각 가상 CPU는 POSIX 스레드로 표시되고 호스트 Linux 스케줄러에 의해 예약됩니다. 스레드는 vCPU FD에서 KVM_RUN
ioctl을 호출하여 하이퍼바이저가 게스트 vCPU 컨텍스트로 전환합니다. 호스트 스케줄러는 게스트 컨텍스트에서 소요된 시간을 해당 vCPU 스레드에서 사용한 시간으로 설명합니다. KVM_RUN
은 I/O, 인터럽트 종료 또는 vCPU 중지와 같이 VMM에서 처리해야 하는 이벤트가 있을 때 반환됩니다. VMM은 이벤트를 처리하고 KVM_RUN
을 다시 호출합니다.
KVM_RUN
동안 스레드는 선점형이 아닌 EL2 하이퍼바이저 코드 실행을 제외하고 호스트 스케줄러에 의해 선점형으로 유지됩니다. 게스트 pVM 자체에는 이 동작을 제어하는 메커니즘이 없습니다.
모든 vCPU 스레드는 다른 사용자 공간 작업과 마찬가지로 예약되기 때문에 모든 표준 QoS 메커니즘이 적용됩니다. 특히, 각 vCPU 스레드는 물리적 CPU에 연결되고, cpusets에 배치되고, 사용률 클램핑을 사용하여 부스트되거나 제한되고, 우선 순위/스케줄링 정책이 변경되는 등의 작업을 수행할 수 있습니다.
가상 장치
crosvm은 다음을 포함한 여러 장치를 지원합니다.
- 복합 디스크 이미지용 virtio-blk, 읽기 전용 또는 읽기-쓰기
- 호스트와의 통신을 위한 vhost-vsock
- virtio 전송으로서의 virtio-pci
- pl030 실시간 시계(RTC)
- 직렬 통신용 16550a UART
pVM 펌웨어
pVM 펌웨어(pvmfw)는 물리적 장치의 부팅 ROM과 유사하게 pVM에 의해 실행되는 첫 번째 코드입니다. pvmfw의 주요 목표는 보안 부팅을 부트스트랩하고 pVM의 고유한 비밀을 도출하는 것입니다. pvmfw는 OS가 crosvm에서 지원되고 제대로 서명된 한 Microdroid 와 같은 특정 OS와 함께 사용하는 것으로 제한되지 않습니다.
pvmfw 바이너리는 같은 이름의 플래시 파티션에 저장되며 OTA 를 사용하여 업데이트됩니다.
장치 부팅
다음 단계 시퀀스가 pKVM 지원 장치의 부팅 절차에 추가됩니다.
- Android 부트로더(ABL)는 파티션에서 메모리로 pvmfw를 로드하고 이미지를 확인합니다.
- ABL은 신뢰 루트에서 DICE(장치 식별자 구성 엔진) 비밀(CDI(복합 장치 식별자) 및 BCC(부트 인증서 체인))을 얻습니다.
- ABL은 pvmfw의 비밀(CDI)에 대한 측정 및 DICE 파생을 수행하고 이를 pvmfw 바이너리에 추가합니다.
- ABL은 pvmfw 바이너리의 위치와 크기 및 이전 단계에서 파생된 비밀을 설명하는
linux,pkvm-guest-firmware-memory
예약 메모리 영역 노드를 DT에 추가합니다. - ABL은 제어를 Linux에 넘기고 Linux는 pKVM을 초기화합니다.
- pKVM은 호스트의 2단계 페이지 테이블에서 pvmfw 메모리 영역을 매핑 해제하고 장치 가동 시간 동안 호스트(및 게스트)로부터 보호합니다.
장치 부팅 후 Microdroid는 Microdroid 문서의 부팅 순서 섹션에 있는 단계에 따라 부팅됩니다.
pVM 부팅
pVM을 생성할 때 crosvm(또는 다른 VMM)은 하이퍼바이저에 의해 pvmfw 이미지로 채워질 만큼 충분히 큰 memslot을 생성해야 합니다. VMM은 초기 값을 설정할 수 있는 레지스터 목록에서도 제한됩니다(기본 vCPU의 경우 x0-x14, 보조 vCPU의 경우 없음). 나머지 레지스터는 예약되어 있으며 hypervisor-pvmfw ABI의 일부입니다.
pVM이 실행되면 하이퍼바이저는 먼저 기본 vCPU의 제어를 pvmfw에 넘깁니다. 펌웨어는 crosvm이 부트로더 또는 기타 이미지가 될 수 있는 AVB 서명 커널과 서명되지 않은 FDT를 알려진 오프셋의 메모리에 로드할 것으로 예상합니다. pvmfw는 AVB 서명의 유효성을 검사하고 성공하면 수신된 FDT에서 신뢰할 수 있는 장치 트리를 생성하고 메모리에서 비밀을 지우고 페이로드의 진입점으로 분기합니다. 확인 단계 중 하나가 실패하면 펌웨어가 PSCI SYSTEM_RESET
하이퍼콜을 실행합니다.
부팅 사이에 pVM 인스턴스에 대한 정보는 파티션(virtio-blk 장치)에 저장되고 pvmfw의 비밀로 암호화되어 재부팅 후 비밀이 올바른 인스턴스에 프로비저닝되도록 합니다.