深入分析Linux虛擬化KVM-Qemu之ioeventfd與irqfd
說明:
KVM版本:5.9.1
QEMU版本:5.0.0
工具:Source Insight 3.5, Visio
1. 概述

圖中的各個(gè)模塊,只剩下通知機(jī)制沒講了,本文來一篇終結(jié)者;
Guest與KVM及Qemu之間的通知機(jī)制,如下圖:

irqfd
:提供一種機(jī)制,可以通過文件描述fd來向Guest注入中斷,路徑為紫色線條所示;ioeventfd
:提供一種機(jī)制,可以通過文件描述符fd來接收Guest的信號,路徑為紅色線條所示;eventfd
和irqfd
這兩種機(jī)制,都是基于eventfd
來實(shí)現(xiàn)的;
本文會先介紹eventfd
機(jī)制,然后再分別從Qemu/KVM來介紹ioeventfd
和irqfd
,開始吧。
2. eventfd
? 說來很巧,我曾經(jīng)在工作中實(shí)現(xiàn)過一個(gè)類似eventfd
機(jī)制的內(nèi)核模塊,用于多線程之間的輕量級通知,算是重復(fù)造輪子了。
eventfd
的機(jī)制比較簡單,大體框架如下圖:

內(nèi)核中創(chuàng)建了一個(gè)
struct eventfd_ctx
結(jié)構(gòu)體,該結(jié)構(gòu)體中維護(hù)一個(gè)count
計(jì)數(shù),以及一個(gè)等待隊(duì)列頭;線程/進(jìn)程在讀
eventfd
時(shí),如果count
值等于0時(shí),將當(dāng)前任務(wù)添加到等待隊(duì)列中,并進(jìn)行調(diào)度,讓出CPU。讀過程count
值會進(jìn)行減操作;線程/進(jìn)程在寫
eventfd
時(shí),如果count
值超過最大值時(shí),會將當(dāng)前任務(wù)添加到等待隊(duì)列中(特殊情況),寫過程count
值會進(jìn)行加操作,并喚醒在等待隊(duì)列上的任務(wù);內(nèi)核的其他模塊也可以通過
eventfd_signal
接口,將count
值加操作,并喚醒在等待隊(duì)列上的任務(wù);
代碼實(shí)現(xiàn)如下圖:

eventfd機(jī)制對用戶層提供的系統(tǒng)調(diào)用接口包括
eventfd()
,write()
,read()
,select/poll
等;通過
eventfd
來創(chuàng)建文件描述符,從代碼中可以看出,該接口的實(shí)現(xiàn)為do_eventfd
,完成的工作包括:
1)在內(nèi)核中分配struct eventfd_ctx結(jié)構(gòu)體來維護(hù)上下文;
2)初始化等待隊(duì)列頭用于存放睡眠等待的任務(wù);
3)分配未使用的文件描述符fd,創(chuàng)建file實(shí)例(該實(shí)例會綁定操作函數(shù)集),將文件描述符fd與file實(shí)例建立連接等;
? 最終系統(tǒng)調(diào)用read/write時(shí),便會分別調(diào)用到eventfd_read/eventfd_write
函數(shù):
eventfd_read
:如果count值為0,將自身添加到等待隊(duì)列中,設(shè)置任務(wù)的狀態(tài)后調(diào)用schedule讓出CPU,等待被喚醒。讀操作中會對count值進(jìn)行減操作,最后再判斷等待隊(duì)列中是否有任務(wù),有則進(jìn)行喚醒;eventfd_write
:判斷count值在增加ucnt后是否會越界,越界則將自身添加到等待隊(duì)列中,設(shè)置任務(wù)的狀態(tài)后調(diào)用schedule讓出CPU,等待被喚醒。寫操作會對count值進(jìn)行加操作,最后再判斷等待隊(duì)列中是否有任務(wù),有則進(jìn)行喚醒;此外,還有
eventfd_signal
接口,比較簡單,完成的工作就是對count值進(jìn)行加操作,并喚醒等待任務(wù);
基石講完了,我們來看看基于之上的ioeventfd
和irqfd
。
【文章福利】小編推薦自己的Linux內(nèi)核技術(shù)交流群:【749907784】整理了一些個(gè)人覺得比較好的學(xué)習(xí)書籍、視頻資料共享在群文件里面,有需要的可以自行添加哦?。。。ê曨l教程、電子書、實(shí)戰(zhàn)項(xiàng)目及代碼)? ?


3. ioeventfd
3.1 Qemu側(cè)
以PCI設(shè)備為例:

Qemu中模擬PCI設(shè)備時(shí),在初始化過程中會調(diào)用
memory_region_init_io
來進(jìn)行IO內(nèi)存空間初始化,這個(gè)過程中會綁定內(nèi)存區(qū)域的回調(diào)函數(shù)集,當(dāng)Guest OS訪問這個(gè)IO區(qū)域時(shí),可能觸發(fā)這些回調(diào)函數(shù)的調(diào)用;Guest OS中的Virtio驅(qū)動配置完成后會將狀態(tài)位置上
VIRTIO_CONFIG_S_DRIVER_OK
,此時(shí)Qemu中的操作函數(shù)調(diào)用virtio_pci_common_write
,并按圖中的調(diào)用流逐級往下;event_notifier_init
:完成eventfd
的創(chuàng)建工作,它實(shí)際上就是調(diào)用系統(tǒng)調(diào)用eventfd()
的接口,得到對應(yīng)的文件描述符;memory_region_add_eventfd
:為內(nèi)存區(qū)域添加eventfd
,將eventfd
和對應(yīng)的內(nèi)存區(qū)域關(guān)聯(lián)起來;
? 看一下memory_region_add_eventfd
的流程:

內(nèi)存區(qū)域
MemoryRegion
中的ioeventfds
成員按照地址從小到大排序,memory_region_add_eventfd
函數(shù)會選擇合適的位置將ioeventfds
插入,并提交更新;提交更新過程中最終觸發(fā)回調(diào)函數(shù)
kvm_mem_ioeventfd_add
的執(zhí)行,這個(gè)函數(shù)指針的初始化是在Qemu進(jìn)行kvm_init
時(shí),針對不同類型的內(nèi)存區(qū)域注冊了對應(yīng)的memory_listener
用于監(jiān)聽變化;kvm_vm_ioctl
:向KVM注冊ioeventfd
;
Qemu中完成了初始化后,任務(wù)就轉(zhuǎn)移到了KVM中。
3.2 KVM側(cè)
KVM中的ioeventfd
注冊如下:

KVM中注冊
ioeventfd
的核心函數(shù)為kvm_assign_ioeventfd_idx
,該函數(shù)中主要工作包括:
1)根據(jù)用戶空間傳遞過來的fd獲取到內(nèi)核中對應(yīng)的struct eventfd_ctx結(jié)構(gòu)體上下文;2)使用ioeventfd_ops操作函數(shù)集來初始化IO設(shè)備操作;3)向KVM注冊IO總線,比如KVM_MMIO_BUS,注冊了一段IO地址區(qū)域,當(dāng)操作這段區(qū)域的時(shí)候出發(fā)對應(yīng)的操作函數(shù)回調(diào);
當(dāng)Guest OS中進(jìn)行IO操作時(shí),觸發(fā)VM異常退出,KVM進(jìn)行捕獲處理,最終調(diào)用注冊的
ioevnetfd_write
,在該函數(shù)中調(diào)用eventfd_signal
喚醒阻塞在eventfd
上的任務(wù),Qemu和KVM完成了閉環(huán);
總體效果如下圖:

4. irqfd
Qemu和KVM中的流程如下圖:

Qemu中通過
kvm_irqchip_assign_irqfd
向KVM申請注冊irqfd
;在KVM中,內(nèi)核通過維護(hù)
struct kvm_kernel_irqfd
結(jié)構(gòu)體來管理整個(gè)irqfd
的流程;kvm_irqfd_assign
:
1)分配struct kvm_kernel_irqfd結(jié)構(gòu)體,并進(jìn)行各個(gè)字段的初始化;2)初始化工作隊(duì)列任務(wù),設(shè)置成irqfd_inject,用于向Guest OS注入虛擬中斷;3)初始化等待隊(duì)列的喚醒函數(shù),設(shè)置成irqfd_wakeup,當(dāng)任務(wù)被喚醒時(shí)執(zhí)行,在該函數(shù)中會去調(diào)度工作任務(wù)irqfd_inject;4)初始化pll_table pt字段的處理函數(shù),設(shè)置成irqfd_ptable_queue_proc,該函數(shù)實(shí)際是調(diào)用add_wait_queue將任務(wù)添加至eventfd的等待隊(duì)列中,這個(gè)過程是在vfs_poll中完成的;
當(dāng)Qemu通過irqfd機(jī)制發(fā)送信號時(shí),將喚醒睡眠在
eventfd
上的任務(wù),喚醒后執(zhí)行irqfd_wakeup
函數(shù),在該函數(shù)中調(diào)度任務(wù),調(diào)用irqfd_inject
來注入中斷;
總體效果如下圖:

原文作者:LoyenWang

深入分析Linux虛擬化KVM-Qemu之ioeventfd與irqfd的評論 (共 條)
