Linux v6.18.37 · 개념과 코드 읽기

ARM64 KVM vCPU와 Stage-2 fault 소스 분석

이 코드는 어떤 문제를 푸나요?

게스트가 만든 VA→IPA 변환 뒤에는 호스트가 관리하는 IPA→PA 변환이 있습니다. 이 두 번째 표로 게스트가 접근할 수 있는 메모리를 제한합니다. 아래는 v6.18.37 KVM이 장치 물리 범위를 게스트 IPA에 연결하는 작은 함수를 읽는 예이며 일반 RAM의 demand fault 전체를 대신하지는 않습니다.

읽을 범위: v6.18.37 · arch/arm64/kvm/mmu.c · kvm_phys_addr_ioremap 1144–1180행입니다. 아래에 이 범위의 원문과 각 줄의 설명을 실었습니다. 주제 전체의 흐름과 다른 경로는 기존 분석에서 함께 읽으실 수 있습니다.

먼저 알아둘 개념

IPA

게스트가 물리 주소로 생각하는 주소입니다. 호스트 RAM의 최종 물리 주소와 같다고 가정하지 않습니다.

Stage-2 권한

게스트 Stage-1이 허용해도 Stage-2에서 막으면 접근이 실패할 수 있습니다.

장치 매핑

일반 RAM과 달리 장치 속성이 필요한 범위입니다. 잘못된 캐시 속성으로 MMIO를 접근하면 동작이 달라질 수 있습니다.

처음 읽을 때

VA, IPA, PA를 서로 다른 세 상자로 그려 두 변환을 따로 설명하십시오.

더 깊이 살펴볼 때

MMU 캐시 준비, 쓰기 권한, KVM mmu_lock과 매핑 실패 정리를 함께 검토하십시오.

그림으로 보는 변화

ARM64 KVM vCPU와 Stage-2 fault 소스 분석의 단계별 개념 그림
각 단계에 화살표 의미와 생략 범위를 표시했습니다. 주소·숫자 예제는 실제 장치 값을 뜻하지 않습니다.
1단계 설명

1단계 고정

GIF 원본 열기

1. 범위와 권한 준비

게스트 IPA와 호스트 물리 범위, 장치 속성을 준비합니다.

첫 화살표는 두 주소 범위를 대응시키는 준비입니다.

2. 페이지 표 자원 준비

Stage-2 표 생성에 필요한 메모리를 마련합니다.

게스트 데이터 페이지를 복사하는 단계는 아닙니다.

3. Stage-2 매핑 설치

잠금 아래 표를 만들고 준비한 자원을 정리합니다.

이후 게스트 접근에 적용될 번역과 권한을 설치합니다.

kvm_phys_addr_ioremap를 한 줄씩 읽기

줄 번호는 v6.18.37 원문 기준입니다. 주석·빈 줄을 포함한 함수 전체를 먼저 보고, 그 아래에서 각 줄을 설명합니다.

int kvm_phys_addr_ioremap(struct kvm *kvm, phys_addr_t guest_ipa,
			  phys_addr_t pa, unsigned long size, bool writable)
{
	phys_addr_t addr;
	int ret = 0;
	struct kvm_mmu_memory_cache cache = { .gfp_zero = __GFP_ZERO };
	struct kvm_s2_mmu *mmu = &kvm->arch.mmu;
	struct kvm_pgtable *pgt = mmu->pgt;
	enum kvm_pgtable_prot prot = KVM_PGTABLE_PROT_DEVICE |
				     KVM_PGTABLE_PROT_R |
				     (writable ? KVM_PGTABLE_PROT_W : 0);

	if (is_protected_kvm_enabled())
		return -EPERM;

	size += offset_in_page(guest_ipa);
	guest_ipa &= PAGE_MASK;

	for (addr = guest_ipa; addr < guest_ipa + size; addr += PAGE_SIZE) {
		ret = kvm_mmu_topup_memory_cache(&cache,
						 kvm_mmu_cache_min_pages(mmu));
		if (ret)
			break;

		write_lock(&kvm->mmu_lock);
		ret = KVM_PGT_FN(kvm_pgtable_stage2_map)(pgt, addr, PAGE_SIZE,
				 pa, prot, &cache, 0);
		write_unlock(&kvm->mmu_lock);
		if (ret)
			break;

		pa += PAGE_SIZE;
	}

	kvm_mmu_free_memory_cache(&cache);
	return ret;
}
int kvm_phys_addr_ioremap(struct kvm *kvm, phys_addr_t guest_ipa,

VM과 장치를 보이게 할 게스트 IPA 시작 주소를 받습니다. 사용자 VA를 받는 함수로 읽지 않습니다.

			  phys_addr_t pa, unsigned long size, bool writable)

실제 물리 시작점·크기·쓰기 허용 여부를 받습니다. 호출자가 제공하는 주소 범위와 페이지 정렬 조건을 주변 경로와 함께 확인해야 합니다.

	phys_addr_t addr;

한 페이지씩 처리할 게스트 주소를 보관합니다.

	int ret = 0;

아직 오류가 없으므로 결과를 0으로 시작합니다.

	struct kvm_mmu_memory_cache cache = { .gfp_zero = __GFP_ZERO };

Stage-2 표를 만들 때 사용할 메모리 캐시를 준비합니다. 새 표가 0으로 초기화되도록 __GFP_ZERO를 설정합니다.

	struct kvm_s2_mmu *mmu = &kvm->arch.mmu;

이 VM의 Stage-2 MMU 상태를 선택합니다.

	struct kvm_pgtable *pgt = mmu->pgt;

그 MMU가 사용하는 페이지 표 객체를 꺼냅니다.

	enum kvm_pgtable_prot prot = KVM_PGTABLE_PROT_DEVICE |

장치용 메모리 속성을 매핑 권한에 포함합니다. 일반 cacheable RAM 매핑으로 취급하지 않습니다.

				     KVM_PGTABLE_PROT_R |

장치 매핑에 읽기 권한을 포함합니다.

				     (writable ? KVM_PGTABLE_PROT_W : 0);

호출자가 writable을 요청한 경우에만 쓰기 권한을 더합니다.

	if (is_protected_kvm_enabled())

보호 KVM 구성이 활성화된 경우인지 확인합니다.

		return -EPERM;

이 경로에서 허용하지 않는 보호 KVM 구성이면 EPERM으로 반환합니다. 모든 보호 VM 매핑을 이 함수가 지원한다고 말할 수 없는 근거입니다.

	size += offset_in_page(guest_ipa);

게스트 IPA의 페이지 내부 offset을 길이에 더하여 정렬 전의 끝 범위를 포함하게 합니다.

	guest_ipa &= PAGE_MASK;

게스트 시작 주소를 페이지 경계로 내립니다. 이 두 줄이 모든 잘못된 입력을 검증하는 일반 범위 검사라고 가정하지 않습니다.

	for (addr = guest_ipa; addr < guest_ipa + size; addr += PAGE_SIZE) {

준비한 게스트 범위를 PAGE_SIZE 단위로 반복합니다. 반복 변수는 IPA이며 실제 물리 주소 pa도 아래에서 함께 증가합니다.

		ret = kvm_mmu_topup_memory_cache(&cache,

잠금을 잡기 전에 페이지 표 구축에 필요한 캐시 메모리를 채웁니다.

						 kvm_mmu_cache_min_pages(mmu));

현재 MMU에 필요한 최소 페이지 수를 계산해 준비 요청에 넘깁니다.

		if (ret)

메모리 준비 중 오류가 발생했는지 확인합니다.

			break;

오류면 더 매핑하지 않고 반복을 끝냅니다. 아래의 공통 캐시 정리로 이어집니다.

		write_lock(&kvm->mmu_lock);

다른 경로의 Stage-2 표 변경과 충돌하지 않도록 쓰기 잠금을 잡습니다.

		ret = KVM_PGT_FN(kvm_pgtable_stage2_map)(pgt, addr, PAGE_SIZE,

Stage-2 매핑 함수를 통해 현재 IPA의 한 페이지를 연결합니다. KVM_PGT_FN은 구성에 맞는 표 함수 연결 매크로입니다.

				 pa, prot, &cache, 0);

대응하는 호스트 물리 주소, 권한, 준비한 표 메모리 캐시를 매핑 함수에 전달합니다.

		write_unlock(&kvm->mmu_lock);

한 페이지 설치 시도가 끝나면 MMU 잠금을 풉니다.

			break;

실패하면 다음 페이지를 계속 설치하지 않습니다. 이 함수가 이미 성공한 앞선 매핑을 여기서 전부 되돌린다고 설명하면 안 됩니다.

		pa += PAGE_SIZE;

다음 IPA와 대응할 물리 주소도 한 페이지 전진시킵니다.

	kvm_mmu_free_memory_cache(&cache);

남은 페이지 표 캐시 자원을 정리합니다. 이것이 앞에서 설치된 실제 Stage-2 표를 모두 해제한다는 뜻은 아닙니다.

	return ret;

성공 또는 중간 오류 값을 호출자에게 반환합니다.

함께 생각해 볼 질문

게스트가 자기 페이지 표를 바꾸면 호스트 전체 RAM을 읽습니까?

Stage-2가 허용한 범위와 권한이 추가로 적용됩니다.

이 함수는 모든 guest page fault 경로입니까?

아닙니다. 장치 범위를 매핑하는 helper를 대표로 읽습니다.

Stage-2 fault는 항상 오류 종료입니까?

지연 매핑으로 해결되거나 MMIO 처리로 이어질 수도 있으며 원인에 따라 다릅니다.

출처와 읽은 범위

Linux stable v6.18.37 · arch/arm64/kvm/mmu.c

해당 버전 원본 파일 · 기존 코드 분석 · 설명 원고

맨 위로 ↑