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發送信號,到中斷注入的執行流大體以下所示:
以馬內利:
參考資料: