Virtio and QEMU storage stack

virtio

Virtio是IO虛擬化中的一個(gè)優(yōu)化方案,屬于para-virtulization的一種實(shí)現(xiàn)柒室,即Guest OS中需要運(yùn)行virtio的驅(qū)動(dòng)程序,通過virtio設(shè)備和后端(KVM/QEMU)進(jìn)行交互逗宜。

Virtio設(shè)備可以視為QEMU為Guest模擬的一個(gè)PCI設(shè)備雄右,因此可以像普通PCI設(shè)備一樣配置、使用中斷和DMA機(jī)制纺讲,這對設(shè)備驅(qū)動(dòng)開發(fā)者來說很方便擂仍。

Virtio 使用 virtqueue 來實(shí)現(xiàn)其 I/O 機(jī)制,每個(gè) virtqueue 就是一個(gè)承載大量數(shù)據(jù)的 queue熬甚。vring 是virtqueue的具體實(shí)現(xiàn)方式逢渔,后面會(huì)詳細(xì)介紹vring的實(shí)現(xiàn)。

Virtio-blk

QEMU為虛擬機(jī)指定一個(gè)Virtio-blk設(shè)備 乡括,使得Guest中能看到一個(gè)”/dev/vda”設(shè)備

-drive file=../sdb.img,cache=none,if=virtio

Virtio-blk前端驅(qū)動(dòng)

Guest系統(tǒng)中涉及的Virtio-blk drivers包括(按照執(zhí)行的先后順序):

  • virtio.c
    • 注冊virtio_bus
  • virtio_pci.c
    • 注冊pci_driver到pci總線(pci_bus_type)
    • probe函數(shù)會(huì)根據(jù)pci_dev創(chuàng)建virtio_pci_device肃廓,并將virtio_pci_device添加到virtio_bus
  • virtio_blk.c
    • 注冊virtio_driver到virtio_bus下
    • probe函數(shù)完成virtio-blk設(shè)備具體的初始化:
      • 創(chuàng)建塊設(shè)備"/dev/vda"及其request_queue
      • 創(chuàng)建和Host通信需要的virtqueue和vring

從Linux設(shè)備驅(qū)動(dòng)的框架來看,virtio-blk涉及到:

  • 兩個(gè)bus:pci_bus_type, virtio_bus
  • 兩個(gè)driver:virtio_pci_driver, virtio_blk
  • 兩個(gè)device:pci_dev, virtio_pci_device

Virtio-blk前端IO流程

virtblk_probe函數(shù)中為gendisk分配了request_queue诲泌,內(nèi)核從v3.13開始盲赊,virtio開始使用multi-queue。(multi-queue的設(shè)計(jì)犧牲了全局范圍的request合并敷扫;認(rèn)為大部分相鄰的訪問都集中在同一個(gè)進(jìn)程哀蘑,所以request只在本CPU的軟件隊(duì)列處理,因而不需要加鎖呻澜。)


virtio_blk

“/dev/vda”和讀寫普通的磁盤一樣递礼,VFS的讀寫請求在到達(dá)塊設(shè)備之前會(huì)經(jīng)過一個(gè)漫長的旅程

user memory  -->  page -->  buffer_head  -->  bio  -->  request

最終構(gòu)造成request提交給塊設(shè)備的請求隊(duì)列:

submit_bh(write_op, bh);
    submit_bio(rw, bio);
        generic_make_request 
            q->make_request_fn(q, bio);  /* blk_sq_make_request */
                blk_mq_run_hw_queue 
                    __blk_mq_run_hw_queue 
                        q->mq_ops->queue_rq   /* virtio_queue_rq */

對于一個(gè)讀寫請求惨险,最終需要交給后端的信息有:

  • page/offset/len Guest的物理內(nèi)存地址
  • sector 虛擬塊設(shè)備的地址
  • type 讀還是寫
virtio_queue_rq()
    blk_rq_map_sg
        __blk_bios_map_sg
    __virtblk_add_req(vblk->vqs[qid].vq, vbr, vbr->sg, num);
        sg_init_one(&hdr, &vbr->out_hdr, sizeof(vbr->out_hdr))
        sgs[num_out + num_in++] = data_sg;
            virtqueue_add_sgs(vq, sgs, num_out, num_in, vbr, GFP_ATOMIC)
                virtqueue_add            /* 將sg填入到vring中去 */
                    desc[i].addr = sg_phys(sg);
                    desc[i].len = sg->length;
    virtqueue_kick_prepare
    virtqueue_notify(vblk->vqs[qid].vq);

我們可以看到向vring中寫了多個(gè)scatterlist:

  • out_hdr 用來向后端描述這次請求羹幸,包括type, sector, ioprio
  • Data 一個(gè)或者多個(gè)Guest OS的一個(gè)物理地址
  • Status Guest OS準(zhǔn)備好的一個(gè)字節(jié),后端在IO完成后填寫


    image.png

寫完vring之后通過virtqueue_notify來通知QEMU

virtqueue_notify
    vq->notify(_vq)     <--  vp_notify  
    iowrite16(vq->index, vp_dev->ioaddr + VIRTIO_PCI_QUEUE_NOTIFY)

其實(shí)質(zhì)是Guest寫io寄存器辫愉,從而觸發(fā)VM exit到KVM中處理栅受,KVM檢查退出的返回值,無法處理就一步步返回到最初的入口kvm_vcpu_ioctl,然后返回到用戶態(tài)也就是QEMU進(jìn)程空間屏镊。

Vring

vring

Vring由一個(gè)freelist和兩個(gè)ring組成:

desc數(shù)組構(gòu)造了一個(gè)freelist依疼,每一片里存放著Guest和Host之間傳輸?shù)臄?shù)據(jù):

  • addr/len Guest的物理地址和長度
  • flags next是否有效?讀 or 寫而芥? INDIRECT 律罢?
  • next

avail->ring[]是發(fā)送端(Guest)維護(hù)的環(huán)形隊(duì)列,指向需要host處理的desc(一次用了多片desc棍丐,但ring[]里只寫入了一個(gè)idx误辑;這多片desc通過鏈表組織起來)

used->ring[]是接收端(Host/QEMU)維護(hù)的環(huán)形隊(duì)列,指向自己已經(jīng)處理過了的desc

  • 發(fā)送端(Guest)更新
    • vring.avail->idx
    • vring_virtqueue.free_head歌逢,它指向desc數(shù)組里freelist的頭
    • vring_virtqueue.last_used_idx巾钉,它表示Guest下一次檢查used ring[]的位置
  • Host更新
    • vring.used->idx
    • VirtQueue.last_avail_idx,它表示Host下一次檢查avail ring[]的位置
  • 這四個(gè)計(jì)數(shù)會(huì)一直遞增下去

QEMU

KVM退出到QEMU之后進(jìn)入kvm_handle_io函數(shù)秘案,通過write eventfd將等待在ppoll系統(tǒng)調(diào)用上的QEMU的主線程喚醒

int kvm_cpu_exec(CPUArchState *env)
{
    do {
        run_ret = kvm_vcpu_ioctl(env, KVM_RUN, 0);
        switch (run->exit_reason) { /* Qemu根據(jù)退出的原因進(jìn)行處理 */
        case KVM_EXIT_IO:
            kvm_handle_io();
            ...

main線程處理vring的主要流程:調(diào)用vq的回調(diào)函數(shù)砰苍,從vring中讀取Guest的物理地址,并轉(zhuǎn)化為自己的虛擬地址后構(gòu)造成QEMU的request

main()  main_loop() main_loop_wait ()
    os_host_main_loop_wait()
        glib_pollfds_poll()
            g_main_context_dispatch () 
                aio_ctx_dispatch    aio_dispatch
                    virtio_queue_host_notifier_read
                        virtio_queue_notify_vq 
                            virtio_blk_handle_output

Vring的處理函數(shù)

Vring注冊的處理函數(shù)virtio_blk_handle_output阱高,從vring中讀取請求赚导,然后構(gòu)造成QEMU的request,然后創(chuàng)建協(xié)程赤惊,在協(xié)程中完成IO的提交辟癌。


處理vring

QEMU協(xié)程

如果指定了aio=native

-drive if=none,id=drive0,cache=none,aio=native,format=qcow2,file=path/to/disk.img \
-device virtio-blk,drive=drive0,scsi=off

那么IO主流程和協(xié)程的交互過程大致如下圖所示:


協(xié)程

要理解協(xié)程,上圖有幾個(gè)關(guān)鍵跳轉(zhuǎn)需要注意:

  1. 原線程調(diào)用qemu_coroutine_enter進(jìn)入?yún)f(xié)程荐捻;
  2. 協(xié)程submit_io后通過qemu_coroutine_yield直接“退出”協(xié)程黍少,返回到原線程調(diào)用enter處,而不是“返回”到調(diào)動(dòng)yield處处面,此時(shí)協(xié)程的代碼邏輯是沒有執(zhí)行完的厂置;原線程可以繼續(xù)在循環(huán)中創(chuàng)建新的協(xié)程來不斷的提交io;
  3. io完成后main_loop中再次調(diào)用qemu_coroutine_enter再次進(jìn)入?yún)f(xié)程魂角,協(xié)程的代碼邏輯好像是調(diào)用yield返回一樣昵济,然后開始執(zhí)行yield之后的代碼,一步步返回到上層函數(shù)野揪;
  4. 協(xié)程調(diào)用blk_aio_complete

QEMU block driver

上圖協(xié)程的部分里的回調(diào)函數(shù)需要關(guān)注

  • 在協(xié)程的IO棧里bdrv_aligned_preadv被調(diào)用了兩次访忿,但兩次調(diào)用drv->bdrv_co_readv是不一樣的,第一次的drv是bdrv_qcow2斯稳,第二次的drv是bdrv_file
  • 對于本例中的塊設(shè)備IO海铆,QEMU協(xié)程中實(shí)際上分了兩步:QCOW2處理和file處理,分別對應(yīng)兩個(gè)struct BlockDriverState挣惰,它們有不同的drv
  • bs->drv->bdrv_aio_readv卧斟,這是不同drv提交IO的函數(shù)殴边,對于本地文件系統(tǒng)就是raw_aio_submit,最終選擇io_submit或者pread/pwrite系統(tǒng)調(diào)用珍语;而對于其它類型的存儲锤岸,比如Ceph rbd就參考bdrv_rbd中的實(shí)現(xiàn)。

如果qemu參數(shù)沒有指定aio=native板乙,那么協(xié)程中將會(huì)使用線程池來模擬異步IO是偷,paio_submit會(huì)從線程池中找一個(gè)worker線程,然后在worker線程中調(diào)用pread/pwrite:

| start_thread 
|     worker_thread 
|         req->func(req->arg)        /*  aio_worker  */
|             handle_aiocb_rw
|                 handle_aiocb_rw_linear
|                     pwrite/pread      /* syscall */
|         qemu_bh_schedule
|             aio_notify(ctx)            /* 寫main_loop中阻塞的fd */

main_loop線程被qemu_bh_schedule喚醒之后:

| main_loop  -- > glib_pollfds_poll -- > thread_pool_completion_bh -- > ...
|     bdrv_co_io_em_complete        < -- 調(diào)用drv->bdrv_aio_readv時(shí)指定的回調(diào)函數(shù)
|         qemu_coroutine_enter(co->coroutine, NULL)
|             qemu_coroutine_switch        /* 再次進(jìn)入?yún)f(xié)程 */

對于不同的BlockBackend募逞,其對應(yīng)的BlockDriver也不相同晓猛,我們需要的就是實(shí)現(xiàn)自己的BlockDriver中的各種函數(shù),比如. bdrv_file_open和.bdrv_aio_readv

Vhost

Virtio-vring實(shí)現(xiàn)了一套Guest和Host之間基于PCI設(shè)備的標(biāo)準(zhǔn)接口凡辱,同時(shí)將原來多次的IO寄存器的訪問改為vring的讀寫戒职,從而減少了VM Exit和Resume的次數(shù)。

但是Virtio避免不了Host上內(nèi)存的拷貝:
QEMU仍然是一個(gè)普通的進(jìn)程透乾,QEMU也需要通過syscall發(fā)起IO請求洪燥,Host內(nèi)核正常情況下會(huì)將數(shù)據(jù)讀/寫到內(nèi)核的page中,然后從內(nèi)核page拷貝到QEMU的虛擬地址中乳乌。

Vhost可以實(shí)現(xiàn)Guest和Host Kernel直接進(jìn)行數(shù)據(jù)交換捧韵,從而避免syscall和數(shù)據(jù)拷貝的性能消耗。

vhost和kvm是兩個(gè)獨(dú)立的運(yùn)行模塊汉操,用戶態(tài)程序通過“/dev/vhost-net”來訪問再来,對于Guest來說,vhost并沒有模擬一個(gè)完整的PCI適配器磷瘤。它內(nèi)部只涉及了virtqueue-vring的操作芒篷,而virtio設(shè)備的適配模擬仍然由Qemu來負(fù)責(zé)。

vhost與kvm的事件通信通過eventfd機(jī)制來實(shí)現(xiàn)采缚,主要包括兩個(gè)方向的event针炉,一個(gè)是Guest到Vhost方向的kick event,通過ioeventfd承載扳抽;另一個(gè)是Vhost到Guest方向的call event篡帕,通過irqfd承載。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末贸呢,一起剝皮案震驚了整個(gè)濱河市镰烧,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌楞陷,老刑警劉巖怔鳖,帶你破解...
    沈念sama閱讀 216,372評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異猜谚,居然都是意外死亡败砂,警方通過查閱死者的電腦和手機(jī)赌渣,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評論 3 392
  • 文/潘曉璐 我一進(jìn)店門魏铅,熙熙樓的掌柜王于貴愁眉苦臉地迎上來昌犹,“玉大人,你說我怎么就攤上這事览芳⌒崩眩” “怎么了?”我有些...
    開封第一講書人閱讀 162,415評論 0 353
  • 文/不壞的土叔 我叫張陵沧竟,是天一觀的道長铸敏。 經(jīng)常有香客問我,道長悟泵,這世上最難降的妖魔是什么杈笔? 我笑而不...
    開封第一講書人閱讀 58,157評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮糕非,結(jié)果婚禮上蒙具,老公的妹妹穿的比我還像新娘。我一直安慰自己朽肥,他們只是感情好禁筏,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,171評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著衡招,像睡著了一般篱昔。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上始腾,一...
    開封第一講書人閱讀 51,125評論 1 297
  • 那天州刽,我揣著相機(jī)與錄音,去河邊找鬼浪箭。 笑死怀伦,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的山林。 我是一名探鬼主播房待,決...
    沈念sama閱讀 40,028評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼驼抹!你這毒婦竟也來了桑孩?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,887評論 0 274
  • 序言:老撾萬榮一對情侶失蹤框冀,失蹤者是張志新(化名)和其女友劉穎流椒,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體明也,經(jīng)...
    沈念sama閱讀 45,310評論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡宣虾,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,533評論 2 332
  • 正文 我和宋清朗相戀三年惯裕,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片绣硝。...
    茶點(diǎn)故事閱讀 39,690評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡蜻势,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出鹉胖,到底是詐尸還是另有隱情握玛,我是刑警寧澤,帶...
    沈念sama閱讀 35,411評論 5 343
  • 正文 年R本政府宣布甫菠,位于F島的核電站挠铲,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏寂诱。R本人自食惡果不足惜拂苹,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,004評論 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望痰洒。 院中可真熱鬧瓢棒,春花似錦、人聲如沸带迟。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽仓犬。三九已至嗅绰,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間搀继,已是汗流浹背窘面。 一陣腳步聲響...
    開封第一講書人閱讀 32,812評論 1 268
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留叽躯,地道東北人财边。 一個(gè)月前我還...
    沈念sama閱讀 47,693評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像点骑,于是被迫代替她去往敵國和親酣难。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,577評論 2 353

推薦閱讀更多精彩內(nèi)容