KVM VHOST中irqfd的使用

2018-01-18數據結構


 

其實在以前的文章中已經簡要介紹了VHOST中經過irqfd通知guest,可是並無對irqfd的具體工做機制作深刻分析,本節簡要對irqfd的工做機制分析下。這裏暫且不討論具體中斷虛擬化的問題,由於那是另外一個內容,這裏僅僅討論vhost如何使用中斷的方式對guest進行通知,這裏答案就是IRQFD。ide

在vhost的初始化過程當中,qemu會經過ioctl系統調用和KVM交互,註冊guestnotifer,見kvm_irqchip_assign_irqfd函數。qemu中對irqfd的定義以下函數

struct kvm_irqfd {
    __u32 fd;
    __u32 gsi;
    __u32 flags;
    __u8  pad[20];
};

該結構是32個字節對齊的,fd是irqfd綁定的eventfd的描述符,gsi是給irqfd綁定的一個 全局中斷號,flag紀錄標誌位,能夠斷定本次請求是irqfd的註冊仍是消除。qemu把該結構傳遞給KVM,KVM中作了什麼處理呢?this

在kvm_vm_ioctl函數中的case KVM_IRQFD分支,這裏把kvm_irqfd複製到內核中,調用了kvm_irqfd函數。通過對flags的判斷,若是發現是註冊的話,就調用kvm_irqfd_assign函數,並把kvm_irqfd結構做爲參數傳遞進去,該函數是註冊irqfd的核心函數。在此以前,先看下內核中對於irqfd對應的數據結構spa

struct _irqfd {
    /* Used for MSI fast-path */
    struct kvm *kvm;
    wait_queue_t wait;
    /* Update side is protected by irqfds.lock */
    struct kvm_kernel_irq_routing_entry __rcu *irq_entry;
    /* Used for level IRQ fast-path */
    int gsi;
    struct work_struct inject;
    /* The resampler used by this irqfd (resampler-only) */
    struct _irqfd_resampler *resampler;
    /* Eventfd notified on resample (resampler-only) */
    struct eventfd_ctx *resamplefd;
    /* Entry in list of irqfds for a resampler (resampler-only) */
    struct list_head resampler_link;
    /* Used for setup/shutdown */
    struct eventfd_ctx *eventfd;
    struct list_head list;//對應KVM中的irqfd鏈表
    poll_table pt;
    struct work_struct shutdown;
};

 irqfd在內核中必然屬於某個KVM結構體,所以首個字段即是對應KVM結構體的指針;在KVM結構體中也有對應的irqfd的鏈表;第二個是wait,是一個等待隊列對象,irqfd須要綁定一個eventfd,且在這個eventfd上等待,當eventfd有信號是,就會處理該irqfd。目前暫且忽略和中斷路由相關的字段;GSI正是用戶空間傳遞過來的全局中斷號;indect和shutdown是兩個工做隊列對象;pt是一個poll_table對象,做用後面使用時在進行描述。接下來看kvm_irqfd_assign函數代碼,這裏咱們分紅三部分:準備階段、對resamplefd的處理,對普通irqfd的處理。第二種咱們暫時忽略指針

第一階段:rest

    irqfd = kzalloc(sizeof(*irqfd), GFP_KERNEL);
    if (!irqfd)
        return -ENOMEM;

    irqfd->kvm = kvm;
    irqfd->gsi = args->gsi;
    INIT_LIST_HEAD(&irqfd->list);
    /*設置工做隊列的處理函數*/
    INIT_WORK(&irqfd->inject, irqfd_inject);
    INIT_WORK(&irqfd->shutdown, irqfd_shutdown);
    /*獲得eventfd對應的file結構*/
    file = eventfd_fget(args->fd);
    if (IS_ERR(file)) {
        ret = PTR_ERR(file);
        goto fail;
    }
    /*獲得eventfd對應的描述符*/
    eventfd = eventfd_ctx_fileget(file);
    if (IS_ERR(eventfd)) {
        ret = PTR_ERR(eventfd);
        goto fail;
    }
    /*進行綁定*/
    irqfd->eventfd = eventfd;

 

這裏的任務比較明確,在內核爲irqfd分配內存,設置相關字段。重要的是對於前面提到的inject和shutdown兩個工做隊列綁定了處理函數。而後根據用戶空間傳遞進來的fd,解析獲得對應的eventfd_ctx結構,並和irqfd進行綁定。code

 第三階段:對象

/*
     * Install our own custom wake-up handling so we are notified via
     * a callback whenever someone signals the underlying eventfd
     */
     /*設置等待隊列的處理函數*/
    init_waitqueue_func_entry(&irqfd->wait, irqfd_wakeup);
    //poll函數中會調用,即eventfd_poll中,這裏僅僅是綁定irqfd_ptable_queue_proc函數和poll_table
    init_poll_funcptr(&irqfd->pt, irqfd_ptable_queue_proc);

    spin_lock_irq(&kvm->irqfds.lock);

    ret = 0;
    /*檢查針對當前eventfd是否已經存在irqfd與之對應*/
    list_for_each_entry(tmp, &kvm->irqfds.items, list) {
        if (irqfd->eventfd != tmp->eventfd)
            continue;
        /* This fd is used for another irq already. */
        ret = -EBUSY;
        spin_unlock_irq(&kvm->irqfds.lock);
        goto fail;
    }
    /*獲取kvm的irq路由表*/
    irq_rt = rcu_dereference_protected(kvm->irq_routing,
                       lockdep_is_held(&kvm->irqfds.lock));
    /*更新kvm相關信息,主要是irq路由項目*/
    irqfd_update(kvm, irqfd, irq_rt);
    /*調用poll函數,把irqfd加入到eventfd的等待隊列中  eventfd_poll*/
    events = file->f_op->poll(file, &irqfd->pt);
    /*把該irqfd加入到虛擬機kvm的鏈表中*/
    list_add_tail(&irqfd->list, &kvm->irqfds.items);

    /*
     * Check if there was an event already pending on the eventfd
     * before we registered, and trigger it as if we didn't miss it.
     */
     /*若是有可用事件,就執行注入,把中斷注入任務加入到系統全局工做隊列*/
    if (events & POLLIN)
        schedule_work(&irqfd->inject);

    spin_unlock_irq(&kvm->irqfds.lock);

    /*
     * do not drop the file until the irqfd is fully initialized, otherwise
     * we might race against the POLLHUP
     */
    fput(file);

    return 0;

 

 第三階段首先設置了irqfd中等待對象的喚醒函數irqfd_wakeup,而後用init_poll_funcptr對irqfd中的poll_table進行初始化,主要是綁定一個排隊函數irqfd_ptable_queue_proc,其實這兩步也能夠看作是準備工做的一部分,不過因爲第二部分的存在,只能安排在第三部分。接下來遍歷KVM結構中的irqfd鏈表,檢查是否有irqfd已經綁定了本次須要的eventfd,言外之意是一個eventfd只能綁定一個irqfd。若是檢查沒有問題,則會調用irqfd_update函數更新中斷路由表項目。並調用VFS的poll函數對eventfd進行監聽,並把irqfd加入到KVM維護的鏈表中。若是發現如今已經有可用的信號(可用事件),就馬上調用schedule_work,調度irqfd->inject工做對象,執行中斷的注入。不然,中斷的注入由系通通一處理。具體怎麼處理呢?先看下poll函數作了什麼,這裏poll函數對應eventfd_poll函數blog

 

static unsigned int eventfd_poll(struct file *file, poll_table *wait)
{
    struct eventfd_ctx *ctx = file->private_data;
    unsigned int events = 0;
    unsigned long flags;
    /*執行poll_table中的函數,把irqfd加入eventfd的到等待隊列中*/
    poll_wait(file, &ctx->wqh, wait);

    spin_lock_irqsave(&ctx->wqh.lock, flags);

    if (ctx->count > 0)
        events |= POLLIN;//代表如今能夠read
    if (ctx->count == ULLONG_MAX)
        events |= POLLERR;
    if (ULLONG_MAX - 1 > ctx->count)
        events |= POLLOUT;//如今能夠write
    spin_unlock_irqrestore(&ctx->wqh.lock, flags);

    return events;
}

 

 該函數的能夠分紅兩部分,首先是經過poll_wait函數執行poll_table中的函數,即前面提到的irqfd_ptable_queue_proc,該函數把irqfd中的wait對象加入到eventfd的等待隊列中。這樣irqfd和eventfd的雙向關係就創建起來了。接下來的任務是判斷eventfd的當前狀態,並返回結果。若是count大於0,則表示如今可讀,即有信號;若是count小於ULLONG_MAX-1,則表示目前該描述符還能夠接受信號觸發。固然這種狀況絕大部分是知足的。那麼在evnetfd上等待的對象何時會被處理呢?答案是當有操做試圖給evnetfd發送信號時,即eventfd_signal函數

 

__u64 eventfd_signal(struct eventfd_ctx *ctx, __u64 n)
{
    unsigned long flags;
    spin_lock_irqsave(&ctx->wqh.lock, flags);
    if (ULLONG_MAX - ctx->count < n)
        n = ULLONG_MAX - ctx->count;
    ctx->count += n;
    /*mainly judge if wait is empty,若是等待隊列不爲空,則進行處理*/
    if (waitqueue_active(&ctx->wqh))
        wake_up_locked_poll(&ctx->wqh, POLLIN);
    spin_unlock_irqrestore(&ctx->wqh.lock, flags);

    return n;
} 

 

 函數首要任務是向對應的eventfd傳送信號,實質就是增長count值。由於此時count值必定大於0,即狀態可用,則檢查等待隊列中時候有等待對象,若是有,則調用wake_up_locked_poll函數進行處理

#define wake_up_locked_poll(x, m)                \
    __wake_up_locked_key((x), TASK_NORMAL, (void *) (m))

void __wake_up_locked_key(wait_queue_head_t *q, unsigned int mode, void *key)
{
    __wake_up_common(q, mode, 1, 0, key);
}

static void __wake_up_common(wait_queue_head_t *q, unsigned int mode,
            int nr_exclusive, int wake_flags, void *key)
{
    wait_queue_t *curr, *next;

    list_for_each_entry_safe(curr, next, &q->task_list, task_list) {
        unsigned flags = curr->flags;

        if (curr->func(curr, mode, wake_flags, key) &&
                (flags & WQ_FLAG_EXCLUSIVE) && !--nr_exclusive)
            break;
    }
}

 具體處理過程就是遍歷等待隊列上全部等待對象,並執行對應的喚醒函數。針對irqfd的喚醒函數前面已經提到,是irqfd_wakeup,在該函數中會對普通中斷執行schedule_work(&irqfd->inject);這樣對於irqfd註冊的inject工做對象處理函數就會獲得執行,因而,中斷就會被執行注入。到這裏不妨在看看schedule_work發生了什麼

static inline bool schedule_work(struct work_struct *work)
{
    return queue_work(system_wq, work);
}

 原來如此,該函數把工做對象加入到內核全局的工做隊列中,接下來的處理就有內核自身完成了。

 從給eventfd發送信號,到中斷注入的執行流大體以下所示:

 

 以馬內利:

參考資料:

  • qemu2.7源碼
  • Linux 內核3.10.1源碼
相關文章
相關標籤/搜索