Search

'kvm'에 해당되는 글 4건

  1. 2018.12.23 Virtio Block 성능 벤치마크
  2. 2018.08.11 kvm ioeventfd
  3. 2018.08.11 kvm irqfd
  4. 2017.11.11 QEMU와 KVM - 2

Virtio Block 성능 벤치마크

컴퓨터공부/가상화기술 2018.12.23 14:08 Posted by 아는 개발자 아는 개발자

QEMU와 KVM환경에서 띄운 Ubuntu VM의 스토리지 성능을 Virtio Block을 사용할 때와 아닐 때를 각각 나누어서 측정을 해보았고 최적화 옵션을 적용할 때 Host대비 얼마정도의 성능을 내는지도 실험 해봤다.


1. 실험 방법


벤치마크툴은 iozone을 사용했고 적용한 옵션은 다음과 같다.

iozone -e -I -a -s 100M -r 4k -r 4096k -r 16384k -i 0 -i 1 -i 2


여기서 주목해야할 옵션 항목만 짚고 넘어가자.

  • -r 은 record할 사이즈를 말하며 여러개의 인자를 전달하면 인자의 개수만큼 측정한다.
  • -i 는 측정할 실험 항목을 의미한다. (0=write/rewrite, 1=read/re-read, 2=random-read/write)
  • -s 는 읽고 쓸 데이터의 크기를 의미한다.

2. 실험환경


Host 환경


- CPU: Intel(R) Core(TM) i5-6600 CPU @ 3.30GHz

- Memory: 8G

- Storage: SSD


Guest VM 환경


- CPU: Intel(R) Core(TM) i5-6600 CPU @ 3.30GHz

- Memory: 4G

- Storage: VirtioBlock(1) / Qemu Storage(2)


3. 측정결과 및 분석


(1) Record Size: 4K


4K

write

rewrite

read

reread

R/Read

R/Reread

Host

108531

140209

130116

149202

41372

108057

VM(VirtioIO)

56260

76539

74808

76864

33816

73393

VM(QEMU Storage)

10231

13252

11558

15289

13324

17782


(2) Record Size: 4096K


4096K

write

rewrite

read

reread

R/Read

R/Reread

Host

475827

493225

500618

506168

506449

491150

VM(VirtioIO)

381458

385537

406131

403043

408653

387712

VM(QEMU Storage)

254328

260574

261297

268813

254842

259518


(3) Record Size: 16384K


16384K

write

rewrite

read

reread

R/Read

R/Reread

Host

495826

502281

524808

521351

520112

502445

VM(VirtioIO)

394283

431118

423213

417768

435611

418307

VM(QEMU Storage)

276397

269190

273982

288246

288831

268841


* 결과 단위는 KB/S이다.


- 측정 단위가(Record Size) 작을수록 VirtioBlock과 QEMU Storage의 성능차이가 많이 나며 커질 수록 어느정도 좁혀지나 40~60% 정도 Virtio Block의 성능이 더 우수한 것으로 수렴한다.


- QEMU Storage 일때는 Host의 절반 정도(53%~55%)이지만  Virtio Block 옵션을 적용하면 Host 대비 80% 정도의 성능을 낸다. Host의 Storage 성능이 워낙 빠르기 때문에 (SSD) 20% 정도의 성능을 손해보더라도 불편함없이 사용할 수 있을 것 같다.


- SSD로 띄운 VM은 최적화 옵션을 넣지 않아도 HDD로 띄운 Host보다 성능이 우수하게 나온다. 성능 구린 PC를 하나 더 사는 것 보다는 성능 좋은 PC에 Virtual Machine을 띄우는게 경제적으로나 성능적으로나 우수해보인다.


16384K

write

rewrite

read

reread

R/Read

R/Reread

Host(HDD)

135480

126629

155426

168672

136012

126976

VM (Virtio based SSD)

394283

431118

423213

417768

435611

418307

VM(QEMU Storage)

276397

269190

273982

288246

288831

268841


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

Virtio Block 성능 세부 분석  (0) 2019.05.20
Virtio Block 성능 벤치마크  (0) 2018.12.23
QEMU를 이용해 커널 이미지 바꿔서 부팅해보기  (0) 2018.12.20
kvm ioeventfd  (0) 2018.08.11
kvm irqfd  (0) 2018.08.11
vhost  (0) 2018.07.08

kvm ioeventfd

컴퓨터공부/가상화기술 2018.08.11 23:10 Posted by 아는 개발자 아는 개발자

* 개인 공부용으로 정리한 것이라 부정확한 내용이 있을 수 있으니 참고용으로만 사용하길 바랍니다.


IOEVENTFD


eventfd를 응용해서 guest에 interrupt를 보낼 수 있는 기능을 만든 irqfd것과 비슷하게 ioeventfd도 eventfd를 이용해서 guest에게 mmio 기능을 전달 할 수 있는 매커니즘을 만들었다. 초기화 작업도 irqfd와 거의 비슷하다.


1. QEMU에서 kvm으로 mmio 값 전달.


QEMU에서 장치가 사용할 주소 값과 flag, eventfd 값을 세팅하고 kvm에 ioctl 을 날린다. 이때 전달인자 fd는 MemoryRegionIoeventfd 라는 구조체의 EventNotifier 값을 뽑아낸 것이다. 구조체 이름이 매우 와닿지 않는다.

// qemu/accel/kvm-all.c
static int kvm_set_ioeventfd_pio(int fd, uint16_t addr, uint16_t val, 
                      bool assign, uint32_t size, bool datamatch)
{
    struct kvm_ioeventfd kick = {
        .datamatch = datamatch 
                       ? adjust_ioeventfd_endianness(val, size) : 0, 
        .addr = addr,
        .flags = KVM_IOEVENTFD_FLAG_PIO,
        .len = size,
        .fd = fd,
    };   
    r = kvm_vm_ioctl(kvm_state, KVM_IOEVENTFD, &kick);

2. 받은 값을 이용해 _ioeventfd 자료구조 생성 및 초기화


_ioevent라는 자료구조를 선언하고 qemu 에서 전달한 값을 세팅한다. args는 QEMU에서 전달받은 인자 값이고 eventfd는 MemoryRegionIoeventfd 의 EventNotifer의 파일 디스크립터 값이다. VirtQueue와 비슷하게 선언된 것 같다.

// virt/kvm/eventfd.c
static int kvm_assign_ioeventfd_idx(struct kvm *kvm,
                enum kvm_bus bus_idx,
                struct kvm_ioeventfd *args)
{
    // HK: Define ioeventfd data structure
    INIT_LIST_HEAD(&p->list);
    p->addr    = args->addr;
    p->bus_idx = bus_idx; // HK: bus_idx is defined by ioeventfd flag
    p->length  = args->len;
    p->eventfd = eventfd;
}

3. IO Device 초기화


초기화한 _ioeventfd에 ioeventfd 작업인 write와 destructor 함수를 세팅한다.. kvm ioeventfd 기반에서 사용할 수 있는 함수들을 설정하는 작업으로 보면 될 것 같다. ioeventfd_write 함수는 쓰려는 값이 range 안에 있는지 확인하고 있으면 eventfd_signal을 보낸다. 값을 업데이트해서 QEMU에 알림을주려는 용도다. 

// virt/kvm/eventfd.c
static int kvm_assign_ioeventfd_idx(struct kvm *kvm,
                enum kvm_bus bus_idx,
                struct kvm_ioeventfd *args)
{
    // HK: Set ioeventfd operation for this device
    kvm_iodevice_init(&p->dev, &ioeventfd_ops);

static const struct kvm_io_device_ops ioeventfd_ops = {
    .write      = ioeventfd_write,
    .destructor = ioeventfd_destructor,
};

/* MMIO/PIO writes trigger an event if the addr/val match */
static int
ioeventfd_write(struct kvm_vcpu *vcpu, struct kvm_io_device *this,
      gpa_t addr, int len, const void *val)
{
    struct _ioeventfd *p = to_ioeventfd(this);

    if (!ioeventfd_in_range(p, addr, len, val))
        return -EOPNOTSUPP;
            
    eventfd_signal(p->eventfd, 1);
    return 0;
}

4. KVM IO bus 등록


mmio로 사용할 주소와 bus index, mmio 동작 작업을 kvm io bus에 등록한다. 버스에서는 등록된 mmio 장치의 주소 값을 보고 event를 날려주는것 같다. bus쪽은 공부가 좀더 필요한 부분이다.

    ret = kvm_io_bus_register_dev(kvm, bus_idx, p->addr, p->length,
                      &p->dev);
    if (ret < 0) 
        goto unlock_fail;
    
    kvm->buses[bus_idx]->ioeventfd_count++;


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

Virtio Block 성능 벤치마크  (0) 2018.12.23
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

kvm irqfd

컴퓨터공부/가상화기술 2018.08.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

QEMU와 KVM - 2

컴퓨터공부/가상화기술 2017.11.11 11:03 Posted by 아는 개발자 아는 개발자

KVM(Kernel-based Virtual Machine)


그림1. KVM 공식 로고다. 펭귄이 던지고 있는 공은 VM을 의미하는 것 같다.


"KVM은 리눅스 커널을 하이퍼바이저로 변환하기 위한 가상화 인프라스트럭처의 하나이다"라고 위키 백과에선 설명하는데 이것만 가지곤 KVM의 제공하는 기능을 이해하긴 힘들다. KVM을 공부하기 전에 같이 사용되는 하이퍼바이저, QEMU에 대해 먼저 공부해보면 KVM의 사용 목적에 대해서 더 쉽게 이해할 수 있다. QEMU 포스트 읽어보기


Intel과 ARM같은 하드웨어 개발 회사들은 컴퓨터 내에서 가상화 기술을 지원하기 위한 장치들(Intel VT 또는 AMD-V )을 넣어뒀다. 이런 장치들은 가상화 기술의 고질적인 성능 저하 문제를 해결 하기 위해 만들어졌는데 이 장치들을 적절히 이용한 하이퍼바이저는 장치를 Emulation 해서 만든 방식보다 뛰어난 성능을 보였다(몇몇 기능은 Host OS와 거의 동일한 성능을 내기도 했다). 하지만 유저영역에서 동작하는 가상화 소프트웨어는 가상화 지원용 하드웨어를 직접 사용 할 수 없다. 하드웨어에 접근하는 일은 모두 커널을 거쳐야 하기 때문이다.


그림2. KVM 사용시 시스템 구조


KVM은 제조사에서 만든 가상화 하드웨어 기능을 범용성 있게 사용할 수 있도록 최적화하고 유저영역에서 동작하는 소프트웨어가 최적화한 기능을 쉽게 사용할 수 있도록 인터페이스를 제공한다. 인터페이스 접근은 간단하게 입출력 제어 함수(ioctl)를 호출해서 할수 있다. QEMU 소스 코드를 보면 실제로 kvm에 ioctl 함수를 보내는 코드들을 볼 수 있다.


지금까지는 하드웨어 제조사의 가상화 솔루션을 사용하기 위해 KVM이 사용되고 있다고 설명했는데 이것 말고도 다양한 기능을 제공하고 있다. 여기서 KVM이 제공하는 기능들 중 중요한 것 두가지만 살펴보자.


1. Virtual Machine 생성 및 관리


VM 생성 및 관리하는 작업을 하이퍼바이저가 아니라 KVM에서 관리 하기도 한다. 이런 경우 VM들이 커널내에서 관리되기 때문에 유저 영역에서 동작 할 때 보다 상대적으로 안정적이며 생성된 VM들은 커널 프로세스의 형태로 존재하기 때문에 유저프로세스로 존재할 때보다 전반적인 성능도 향상된다.


2. 장치 Emulation


CPU, Memory, Interrupt Handler처럼 VM 성능에 결정적인 장치들을 Emulation 해준다. 앞서 설명한 VM 생성 관리 작업처럼 이 장치들도 유저 영역에서 존재 할때보단 커널 영역에 있을 때 더 좋은 성능을 보이며 뿐만 아니라 하드웨어장치를 컨트롤 할 수 있는 함수를 호출 할 수 있는 장점이 있다. 여기서 Emulation 된 장치들은 제조사에서 추가한 가상화 솔루션을 지원하는 장치들이다.


이외에도 KVM이 제공하는 기능들을 적절하게 사용한다면 유저영역 하이퍼바이저에서 할 일은 "생성할 VM의 파라미터를 잘 받고 KVM 모듈의 함수를 잘 호출 하는 것"과 "KVM에서 제공하지 못한 장치들을 Emulation 하는 것"이다. 그래픽같은 경우 가상화 기술을 상용화 할 때 가장 크리티컬한 요소인데 아쉽게도 KVM에선 따로 지원하지 않는다. 이런 경우엔 하이퍼바이저에서 그래픽 관련 요소들을 처리 할 수 있는 솔루션을 제공해야한다.


사진 출처


- https://sort.veritas.com/public/documents/vie/7.3/linux/productguides/html/infoscale_virtualization/ch01s03.htm (그림 2)

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

QEMU 성능 문제 - 개론  (0) 2018.05.30
KVM - ARM  (0) 2018.01.01
QEMU와 KVM - 2  (0) 2017.11.11
QEMU와 KVM - 1  (0) 2017.11.01
6. XenStore, Xenbus  (0) 2017.01.22
5. 이벤트 채널 소개  (0) 2017.01.07