Search

'eventfd'에 해당되는 글 2건

  1. 2018.08.11 kvm irqfd
  2. 2018.07.18 eventfd

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

eventfd

컴퓨터공부/리눅스 2018. 7. 18. 21:37 Posted by 아는 개발자

eventfd는 리눅스기반 운영체제에서 파일 디스크립터를 통해 프로세스들끼리 통신 할 수 있는 API중 하나다. IPC로 가장 유명한 pipe는 read와 write용으로 채널을 따로 만드는 것과는 달리 eventfd에서는 읽고 쓸 파일 디스크립터 하나면 충분하기 때문에 번거로운 세팅작업 없이 간단하게 구현 할 수 있다. 그러나 단 한개의 채널을 사용하는만큼 사용할 수 있는 기능은 제한적이다. 이 파일 디스크립터로 교환할 수 있는 값은 1 이상의 64비트 정수 하나가 전부라 데이터 전송보다는 이름처럼 프로세스간에 event를 전달하는 용도로 사용한다.


1. eventfd(uint64 initval, int flag)


프로세스들끼리 통신 할 수 있는 파일 디스크립터를 만드는 함수다. 종료후에는 채널로 사용 할 수 있는 64비트 정수형 파일 디스크립터가 리턴된다.


첫번째 인자는 counter 값이다. counter 값은 eventfd의 고유한 값 정도로 보면 될 것 같다. 이 값은 write로 추가 할 수 있고 read로 읽을 수 있다. 그런데 flag에 따라서 동작이 달라지니까 유의하도록 한다.


2. read(int fd, void* buf, size_t count)


eventfd의 counter 값을 읽는 함수다. 단 counter 값은 무조건 1 이상이 되야 하고 counter 값이 0이면 되면 읽지 못하고 에러를 리턴한다. 1이상이면 가지고 있던 counter 값을 리턴한다. 그런데 리턴한 후에는 flag 값에 따라서 counter 값을 변형한다.


- EFD_SEMAPHORE가 세팅된 경우에는 counter 값을 1만큼 감소시킨다. 세마포어 기능을 한다고 보면 된다. 여러개 프로세스가 읽는 경우를 생각한 것 같다.


- EFT_SEMAPHORE가 세팅이 안된 경우에는 counter 값을 0으로 리셋한다.


3. write(int fd, const void *buf, size_t count)


counter 값을 추가할 수 있는 함수다. counter 값은 uint64 범위 내에서 자유롭게 추가 할 수 있으며 만약 최대값을 넘는 경우에는 막히고 에러를 리턴한다. 별거 어려운게 없다.


나중에는 실제로 프로세스들이 어떻게 사용하고있는지 분석해봐야겠다.




'컴퓨터공부 > 리눅스' 카테고리의 다른 글

CPU pinning과 taskset  (0) 2018.08.27
스핀락  (0) 2018.07.23
eventfd  (0) 2018.07.18
workqueue 사용법  (0) 2018.07.16
tasklet 사용법  (0) 2018.06.17
tasklet과 workqueue의 차이점  (0) 2018.06.15