Search

'가상화'에 해당되는 글 2건

  1. 2018.08.11 kvm irqfd
  2. 2018.06.30 VFIO, Passthrough

kvm irqfd

컴퓨터공부/가상화기술 2018. 8. 11. 14:02 Posted by 아는 개발자

* 개인 공부용도로 정리한 것이라 부정확한 정보가 있을 수도 있으니 참고용으로만 사용하세요.


IRQFD


irqfd는 QEMU에서 정의한 eventfd와 GSI(Global System Interrupt) 값을 이용해서 Guest에 바로 interrupt를 바로 쏘아 줄 때(irqfd_inject) 사용하는 kvm interrupt 라이브러리다. 간단한 event를 만들 때 사용하는 eventfd 메커니즘을 응용한 대표적인 예다. 


좀더 디테일한 동작 내용을 이해하기 위해 초기화 코드를 순서대로 분석해보자. 


1. QEMU 장치 정보 세팅 후 kvm으로 ioctl 전달


QEMU에서는 장치가 사용하려는 GSI(Global system interrupt)와 상태에 대한 정보를 저장하는 flag 값 그리고 qemu/VM 간 데이터를 주고받는 통로인 virtqueue의 file descriptor 값을 kvm에게 ioctl로 전달한다.

// qemu/accel/kvm/kvm-all.c
static int kvm_irqchip_assign_irqfd(KVMState *s, int fd, int rfd,
                                               int virq, bool assign)
{
    struct kvm_irqfd irqfd = {
        .fd = fd,
        .gsi = virq,
        .flags = assign ? 0 : KVM_IRQFD_FLAG_DEASSIGN,

    return kvm_vm_ioctl(s, KVM_IRQFD, &irqfd);
}

2. irqfd 자료구조 생성 및 동작 함수 세팅


QEMU로부터 전달받은 정보들을 토대로 kvm은 현재 VM 이 사용할 irqfd 자료구조를 할당하고 값을 세팅한다. args로 참조하는 값은 QEMU에서 받아온 값이고 eventfd는 QEMU에서 전달받은 VirtQueue의 file descriptor 값을 이용해 추출한 주소다. VirtQueue가 eventfd 기반으로 만들어진 자료구조 인 것 같다. irqfd->inject,shutdown은 irqfd가 runtime에 수행할 함수들이다. irqfd_inject는 인터럽트를 guest에 주입하는 함수고 irqfd_shutdown은 irqfd 자료구조를 제거할 때 사용된다.

// linux/virt/kvm/eventfd.c
static int kvm_irqfd_assign(struct kvm *kvm, struct kvm_irqfd *args) 
{
    irqfd->kvm = kvm;
    irqfd->gsi = args->gsi;
    INIT_LIST_HEAD(&irqfd->list);
    INIT_WORK(&irqfd->inject, irqfd_inject);
    INIT_WORK(&irqfd->shutdown, irqfd_shutdown);
    seqcount_init(&irqfd->irq_entry_sc);
...
    irqfd->eventfd = eventfd;


3. poll table, waitqueue 초기화


irqfd의 poll table의 콜백 함수와 waitqueue의 콜백 함수를 설정한다. 설정 목적은 eventfd 메커니즘으로 전달받은 이벤트를 irqfd로 쏘아주기 위함이다. eventfd부터 최종적으로 irqfd 함수가 불리기 까지의 동작은 마지막에 설명하도록 하자. 여기서는 poll_table이라는 것을 사용했다는 것에 주목하자

// linux/virt/kvm/eventfd.c
static int kvm_irqfd_assign(struct kvm *kvm, struct kvm_irqfd *args) 
{
    init_waitqueue_func_entry(&irqfd->wait, irqfd_wakeup);
    init_poll_funcptr(&irqfd->pt, irqfd_ptable_queue_proc);


4. kvm irq routing 정보 입력


Guest로부터 넘겨받은 GSI 값에 대한 irq routing entry 정보를 받고 업데이트한다. 이부분은 배경 지식이 부족해 정확하게 이해하지 못한 부분이기는 한데 코드만 보면 대충 짐작하기로는 kvm 자료구조에서 irq routing에 대한 정보가 있으며 이 값은 GSI값에 따라 처리할 수 있는 entry 정보가 별도로 존재하는 것 같다. 추출한 entry 값을 irqfd 자료구조에 설정한다.

// linux/virt/kvm/eventfd.c
static int kvm_irqfd_assign(struct kvm *kvm, struct kvm_irqfd *args) 
{
    irqfd_update(kvm, irqfd);
static void irqfd_update(struct kvm *kvm, 
                             struct kvm_kernel_irqfd *irqfd)
{
    // HK: Get the entries of irq_rt->map[gsi]
    // Set mapping between irqfd(kernel) to user space
    n_entries = kvm_irq_map_gsi(kvm, entries, irqfd->gsi);
// linux/virt/kvm/irqchip.c
int kvm_irq_map_gsi(struct kvm *kvm,
            struct kvm_kernel_irq_routing_entry *entries, int gsi)
{
    irq_rt = srcu_dereference_check(kvm->irq_routing, &kvm->irq_srcu,
                    lockdep_is_held(&kvm->irq_lock));

    // HK: Define kvm_irq_routing_entry
    //     Each entry properties depend on gsi value...
    if (irq_rt && gsi < irq_rt->nr_rt_entries) {
        hlist_for_each_entry(e, &irq_rt->map[gsi], link) {
            entries[n] = *e; 
            ++n;


5. irqfd 리스트 추가 및 pending된 인터럽트 처리


지금까지 세팅한 irqfd 자료구조를 kvm irqfds 리스트에 추가한다. 장치가 여러개 있으니 VM별로 여러개의 irqfd를 가질 수 있다. 그리고 지금까지 초기화 하는 도중에 인터럽트가 발생했는지 확인하고 있으면 스케줄링으로 저리한다. 여기서 poll이라는 콜백함수로 이벤트가 있는지 확인하는데 poll 함수는 eventfd_poll이다. eventfd 값이 변경되면 새로운 event가 발생한 것으로 보고 interrupt를 날리려는 용도다.

// linux/virt/kvm/eventfd.c
static int kvm_irqfd_assign(struct kvm *kvm, struct kvm_irqfd *args) 
{
    /* HK: Add new irqfd to kvm irqfd list */
    list_add_tail(&irqfd->list, &kvm->irqfds.items);
    events = f.file->f_op->poll(f.file, &irqfd->pt);

    if (events & POLLIN)
        schedule_work(&irqfd->inject);


초기화 작업을 바탕으로 분석한 irqfd의 동작 과정은 다음과 같다


eventfd_poll => irqfd->pt (poll table) => (callback) irqfd_ptable_queue_proc

=> add_wait_queue(wqh, &irqfd->wait) => irqfd_wakeup => schedule_work(&irqfd->inject);

=> kvm_set_irq(kvm, KVM_USERSPACE_IRQ_SOURCE_ID, irqfd->gsi, 1,


eventfd_poll 함수에서는 irqfd가 갖고 있는 eventfd 값이 변경되길 기다리며 event 발생시 poll table에 물려있는 콜백함수(irqfd_ptable_queue_proc)함수가 불리고 이 함수에서는 waitqueue의 콜백 함수로 설정한 irqfd_wakeup 함수를 부른다. 이 함수에서는 interrupt를 날리는 함수인 irqfd_inject 함수가 불리고 이 함수에서 최종적으로 guest에게 interrupt를 쏴주게 된다. 휴 어렵네;


kvm_set_irq 함수 내의 while문에서 콜백 함수로 set이 불리는데 이 함수는 아키텍처별로 설정되는 함수다. ARM의 경우에는 vgic의 vgic_irqfd_set_irq 함수가 불리며 x86 에서는 irqchip 타입에 따라서 콜백 함수가 다르다.

// virt/kvm/irqchip.c
int kvm_set_irq(struct kvm *kvm, int irq_source_id, u32 irq, int level,
        bool line_status)
{
    while (i--) {
        int r;
        // HK: kvm_set_msi architecture specific callback!
        //     set -> vgic_irqfd_set_irq (vgic-irqfd.c)
        r = irq_set[i].set(&irq_set[i], kvm, irq_source_id, level,
                   line_status);


사용 예시


Guest가 virtio 드라이버를 사용하는 경우, QEMU에서 에뮬레이션된 virtio backend 드라이버가 요청 받은 작업을 마치고 Guest에 VirtQueue를 이용해서 알림을 주게 된다. 이때 QEMU에서 VirtQueue에 결과 값을 입력하면 커널에서는 이전에 초기화한 VirtQueue의 eventfd 값이 변경된 것을 감지하고하고 초기화된 함수들이 연달아 불리게 된다. 최종적으로 interrupt_inject 함수가 불리게 되면서 Guest에게 interrupt가 전달된다.

'컴퓨터공부 > 가상화기술' 카테고리의 다른 글

QEMU를 이용해 커널 이미지 바꿔서 부팅해보기  (0) 2018.12.20
kvm ioeventfd  (0) 2018.08.11
kvm irqfd  (0) 2018.08.11
vhost  (0) 2018.07.08
virtio  (0) 2018.07.08
VFIO, Passthrough  (0) 2018.06.30

VFIO, Passthrough

컴퓨터공부/가상화기술 2018. 6. 30. 13:40 Posted by 아는 개발자

VFIO (Virtual Function I/O)


일반적으로 유저 애플리케이션에서 특정 장치를 이용하기 위해선 먼저 Host OS에서 해당 장치를 전담하는 유저 서비스(안드로이드는 surface flinger 같은게 있다)에게 요청하고, 유저 서비스는 커널 단의 장치 드라이버에 작업을 전달하며 장치 드라이버는 전달 받은 요청에 따라 장치를 실제로 움직이게 된다. 이러한 형태는 유저 앱의 입장에서 꽤 단순한 작업을 해도 커널 단의 장치드라이버에 불필요한 작업이 많다면 이에 비례해서 처리하는 시간이 늘어나게돼 유저앱의 성능이 저하되는 일이 발생한다.


이를 해결하고자 리눅스 커널에서는 유저 영역에서 직접 장치에 접근 할 수 있는 플랫폼을 만들었다. 간단히 말해 장치 드라이버를 커널에 두지 않고 유저 영역에 드라이버를 둘 수 있는 형태다. 이런 구조는 불필요한 커널 스택을 거치지 않고 사용 목적에 따라 드라이버를 만들 수 있기 때문에 효율적이고 최적화된 코드를 짤 수 있으며 혹시나 만든 드라이버가 죽더라도 커널 영역이 아니기 때문에 시스템 전체가 크러쉬되는 부담은 줄어드는 장점이 있다. 그리고 무엇보다 회사 입장에서는 유저 영역에서 코드를 작성하기 때문에 GPL 라이센스를 따르지 않아 코드를 공개하지 않아도 되는 메리트가 있기도 하다. (물론 오픈소스 철학과는 상반되지만)


Passthrough


애초에 Type2 하이퍼바이저를 목표로두고 설계한 구조는 아니지만 구글에 VFIO를 치면 QEMU와 연관된 자료들이 무수히 많이 나오는데 아마 VFIO를 가장 유용하게 사용할 수 있는 대표적인 예가 가상화이기 때문에 그런것 같다. QEMU에서 만들어준 장치들은 이미 VM내의 커널 드라이버를 거치고 왔기 때문에 그 이후 이뤄지는 Host 커널 영역에서 이뤄지는 작업은 어찌보면 같은 일은 두번 반복하는 불필요한 작업이기도 하다. 이때 VFIO를 사용하면 불필요한 작업 없이 에뮬레이션 장치의 결과물을 그래도 실제 장치에 전달 할 수 없어 성능을 대폭 향상 시킬 수 있다. VM이 직접 장치에 접근 할 수 있는 구조가 되는 것이다.


이처럼 Guest가 Host의 중재 없이 직접 장치에 접근 할 수 있는 구조를 Passthrough라고 한다. 여기서 VFIO는 Type2 하이퍼바이저에서 Passthrough를 할 수 있는 일종의 플랫폼 역할을 하는 것이며 Xen과 같은 Type1 하이퍼바이저에서는 장치를 front/backend의 형태로 안쓰고 Guest가 native driver를 사용할 수 있도록 변형해서 Passthrough를 사용한다.



그림 1. VFIO 개념도

  • 간단하게 그래픽 장치에 바로 접근한다고 설명했지만 실제로 고려해야할 일들은 무수히 많다. 벤더에 따라서 해야하는 일이 천차만별.

  • 주로 그래픽카드와 네트워크 장치에 사용한다.

보안 위험성

대부분의 장치가 MMIO로 사용하기 때문에 VM이 직접 장치를 사용하기 위해선 장치의 물리 메모리 주소에 VM이 접근 할 수 있어야 한다.  그런데 VM이 실제 물리 주소를 알고 변환 과정 없이 물리주소로 사용하는 경우 보안상 위험이 존재한다. 실제 물리 주소를 알게 되면 물리 메모리 영역의 전반적인 주소 값을 추측 할 수 있게 되고 (물론 정확하진 않지만) 추측한 값을 통해 할당된 장치 뿐만 아니라 주변 장치 또는 메모리 영역까지 접근 할 수 있게 된다. 악의적인 목적을 가지면 Host나 다른 VM의 메모리 값을 오염시키는 것도 가능하다. 

위와 같은 보안상의 위험을 막기 위해선 VM이 자신에게 할당된 장치 영역 이외의 물리 주소는 접근 하지 못하도록 막아야 한다. iommu를 사용하면 page table을 사용할 때처럼 VM의 가상 주소를 실제 장치의 물리주소와 매핑하기 때문에 VM으로부터 장치의 주소의 정보를 숨기면서도 접근은 가능하게 되고 뿐만 아니라 할당 되지 않은 메모리에 접근하려고 하는 경우에는 fault를 발생시켜서 보안 위협을 막을 수 있다. 

참고자료


- Virtual Open System, vfio에 대해서 전반적인 소개를 하는 자료. 

- Platform Device Assignment to KVM-on-ARM Virtual Machines via VFIO 논문, 그림을 가져왔다. 자세하게 설명해주고 있어 많은 도움이 됐다.

'컴퓨터공부 > 가상화기술' 카테고리의 다른 글

vhost  (0) 2018.07.08
virtio  (0) 2018.07.08
VFIO, Passthrough  (0) 2018.06.30
QEMU 성능 문제 - 개론  (0) 2018.05.30
KVM - ARM  (0) 2018.01.01
QEMU와 KVM - 2  (0) 2017.11.11