精品欧美一区二区三区在线观看 _久久久久国色av免费观看性色_国产精品久久在线观看_亚洲第一综合网站_91精品又粗又猛又爽_小泽玛利亚一区二区免费_91亚洲精品国偷拍自产在线观看 _久久精品视频在线播放_美女精品久久久_欧美日韩国产成人在线

探秘Linux IO虛擬化:virtio的奇幻之旅

系統(tǒng) Linux
virtio 的數(shù)據(jù)收發(fā)流程是其核心功能的具體體現(xiàn),這個流程就像是一場緊張有序的接力賽,各個環(huán)節(jié)緊密配合,確保數(shù)據(jù)的高效傳輸。

在當(dāng)今數(shù)字化時代,虛擬化技術(shù)早已成為推動計算機領(lǐng)域發(fā)展的重要力量。想象一下,一臺物理主機上能同時運行多個相互隔離的虛擬機,每個虛擬機都仿佛擁有自己獨立的硬件資源,這一切是如何實現(xiàn)的呢?

今天,就讓我們一起踏上這場充滿奧秘的 Linux IO 虛擬化探索之旅,而我們的主角 ——virtio,將為我們揭開這層神秘的面紗。它是如何在虛擬化的世界里巧妙運作,解決了 I/O 虛擬化中的諸多難題?又有著怎樣獨特的設(shè)計和實現(xiàn),讓眾多開發(fā)者為之著迷?接下來,就跟我一同深入 virtio 的奇妙世界,探尋其中的秘密。

一、Linux IO虛擬化簡介

1.1虛擬化概述

在虛擬化的大家族中,Linux IO 虛擬化占據(jù)著重要的地位。它專注于解決虛擬機與物理硬件之間輸入 / 輸出(I/O)通信的問題,力求打破 I/O 性能瓶頸,讓虛擬機在數(shù)據(jù)傳輸?shù)母咚俟飞蠒承袩o阻。想象一下,虛擬機就像一個個繁忙的工廠,不斷地需要原材料(輸入數(shù)據(jù))和輸出產(chǎn)品(輸出數(shù)據(jù)),而 Linux IO 虛擬化就是優(yōu)化工廠運輸線路和裝卸流程的關(guān)鍵技術(shù),確保原材料和產(chǎn)品能夠快速、高效地進(jìn)出工廠。

而 virtio,作為 Linux IO 虛擬化領(lǐng)域的璀璨明星,發(fā)揮著舉足輕重的作用。它就像是一座堅固的橋梁,連接著虛擬機和物理設(shè)備,為兩者之間的通信搭建了一條高效、穩(wěn)定的通道。virtio 提供了一套通用的 I/O 設(shè)備虛擬化框架,使得不同的虛擬機監(jiān)控器(Hypervisor)和設(shè)備驅(qū)動能夠基于統(tǒng)一的標(biāo)準(zhǔn)進(jìn)行交互,大大提高了代碼的可重用性和跨平臺性。無論你使用的是 KVM、Xen 還是其他虛擬化解決方案,virtio 都能像一位可靠的伙伴,為你提供出色的 I/O 虛擬化支持。

Virtio的好處:

  • virtio作為一種Linux內(nèi)部的API,提供了多種前端驅(qū)動模塊
  • 框架通用,方便模擬各種設(shè)備
  • 使用半虛擬化可以大大減少VMEXIT次數(shù),提高性能

1.2Linux IO 虛擬化

在深入了解 virtio 之前,讓我們先來回顧一下 Linux IO 虛擬化的傳統(tǒng)實現(xiàn)方式,以及它所面臨的挑戰(zhàn)。傳統(tǒng)的 Linux IO 虛擬化中,Qemu

當(dāng)客戶機中的設(shè)備驅(qū)動程序發(fā)起 I/O 操作請求時,整個流程就像一場精心編排的接力賽。KVM 模塊中的 I/O 操作捕獲代碼首先攔截這次 I/O 請求,就像接力賽中的第一棒選手,迅速接過請求的 “接力棒”。然后,它將本次 I/O 請求的信息存放到 I/O 共享頁,并通知用戶空間的 Qemu 程序。

Qemu 模擬程序獲得 I/O 操作的具體信息之后,交由硬件模擬代碼來模擬出本次的 I/O 操作,完成之后,將結(jié)果放回到 I/O 共享頁,并通知 KVM 模塊中的 I/O 操作捕獲代碼。最后,由 KVM 模塊中的捕獲代碼讀取 I/O 共享頁中的操作結(jié)果,并把結(jié)果返回客戶機中。在這個過程中,客戶機作為一個 Qemu 進(jìn)程在等待 I/O 時也可能被阻塞,就像接力賽中的選手在傳遞接力棒時可能會遇到一些阻礙。

這種模擬方式雖然具有很強的靈活性,能夠通過軟件模擬出各種各樣的硬件設(shè)備,包括一些不常用的或很老很經(jīng)典的設(shè)備,而且不用修改客戶機操作系統(tǒng),就可以使模擬設(shè)備在客戶機中正常工作,為解決手上沒有足夠設(shè)備的軟件開發(fā)及調(diào)試提供了很大的幫助。但它的缺點也很明顯,每次 I/O 操作的路徑比較長,有較多的 VMEntry、VMExit 發(fā)生,需要多次上下文切換,就像接力賽中選手頻繁交接接力棒,耗費大量時間和精力。

同時,也需要多次數(shù)據(jù)復(fù)制,這無疑進(jìn)一步降低了效率,導(dǎo)致其性能較差。在一些對 I/O 性能要求較高的場景中,如大規(guī)模數(shù)據(jù)處理、實時通信等,傳統(tǒng)的 Qemu 模擬 I/O 設(shè)備的方式往往難以滿足需求,就像一輛老舊的汽車,在高速公路上無法達(dá)到預(yù)期的速度。

隨著虛擬化技術(shù)的廣泛應(yīng)用,對 I/O 性能的要求越來越高,傳統(tǒng)的 IO 虛擬化方式逐漸暴露出其局限性,這也促使了新的技術(shù) ——virtio 的出現(xiàn),它將為我們帶來怎樣的驚喜呢?讓我們繼續(xù)深入探索。

二、揭開virtio神秘面紗

virtio,作為 Linux IO 虛擬化領(lǐng)域的關(guān)鍵技術(shù),究竟是什么呢?簡單來說,virtio 是一種用于虛擬化平臺的 I/O 虛擬化標(biāo)準(zhǔn) ,它就像是一個智能的翻譯官,讓虛擬機和宿主系統(tǒng)能夠順暢地交流。它由 Rusty Russell 開發(fā),最初是為了支持自己的虛擬化解決方案 lguest。在半虛擬化的世界里,virtio 扮演著至關(guān)重要的角色,它是對一組通用模擬設(shè)備的抽象,就像一個萬能的模具,可以根據(jù)不同的需求塑造出各種虛擬設(shè)備。

在半虛擬化的架構(gòu)中,來賓操作系統(tǒng)(也就是虛擬機中的操作系統(tǒng))需要與 Hypervisor(虛擬機監(jiān)視器)進(jìn)行緊密的合作 。而 virtio 就像是一座橋梁,連接著來賓操作系統(tǒng)和 Hypervisor。它提供了一組通用的接口,讓來賓操作系統(tǒng)能夠以一種標(biāo)準(zhǔn)化的方式與 Hypervisor 進(jìn)行交互。這樣一來,不同的虛擬化平臺就可以基于 virtio 實現(xiàn)統(tǒng)一的 I/O 虛擬化,大大提高了開發(fā)效率和兼容性。想象一下,有了 virtio 這座橋梁,不同的虛擬化平臺就像不同語言的人,通過 virtio 這個翻譯官,能夠輕松地溝通和協(xié)作,實現(xiàn)高效的 I/O 虛擬化。

那么,virtio 是如何抽象模擬設(shè)備的呢?它通過定義一套通用的設(shè)備模型和接口,將各種物理設(shè)備的功能抽象出來 。無論是網(wǎng)絡(luò)適配器、磁盤驅(qū)動器還是其他設(shè)備,virtio 都為它們提供了統(tǒng)一的抽象表示。在虛擬化環(huán)境中,虛擬機中的網(wǎng)絡(luò)設(shè)備可以通過 virtio 接口與 Hypervisor 中的網(wǎng)絡(luò)后端進(jìn)行通信,而不需要關(guān)心具體的物理網(wǎng)絡(luò)設(shè)備是什么。這種抽象模擬的方式,使得 virtio 具有很強的通用性和靈活性,能夠適應(yīng)各種不同的虛擬化場景。就像一個萬能的遙控器,無論你是控制電視、空調(diào)還是其他電器,都可以通過這個遙控器進(jìn)行操作,而不需要為每種電器都配備一個專門的遙控器。

2.1virtio 數(shù)據(jù)流交互機制

vring+主要通過兩個環(huán)形緩沖區(qū)來完成數(shù)據(jù)流的轉(zhuǎn)發(fā),如下圖所示:

圖片圖片

vring 包含三個部分,描述符數(shù)組 desc,可用的 available ring 和使用過的 used ring。

desc 用于存儲一些關(guān)聯(lián)的描述符,每個描述符記錄一個對 buffer 的描述,available ring 則用于 guest 端表示當(dāng)前有哪些描述符是可用的,而 used ring 則表示 host 端哪些描述符已經(jīng)被使用。

Virtio 使用 virtqueue來實現(xiàn) I/O 機制,每個 virtqueue 就是一個承載大量數(shù)據(jù)的隊列,具體使用多少個隊列取決于需求,例如,virtio 網(wǎng)絡(luò)驅(qū)動程序(virtio-net)使用兩個隊列(一個用于接受,另一個用于發(fā)送),而 virtio 塊驅(qū)動程序(virtio-blk)僅使用一個隊列。

具體的,假設(shè) guest 要向 host 發(fā)送數(shù)據(jù),首先,guest 通過函數(shù) virtqueue_add_buf 將存有數(shù)據(jù)的 buffer 添加到 virtqueue 中,然后調(diào)用 virtqueue_kick 函數(shù),virtqueue_kick 調(diào)用 virtqueue_notify 函數(shù),通過寫入寄存器的方式來通知到 host。host 調(diào)用 virtqueue_get_buf 來獲取 virtqueue 中收到的數(shù)據(jù)。

圖片圖片

存放數(shù)據(jù)的 buffer 是一種分散-聚集的數(shù)組,由 desc 結(jié)構(gòu)來承載,如下是一種常用的 desc 的結(jié)構(gòu):

圖片圖片

  • 當(dāng) guest 向 virtqueue 中寫數(shù)據(jù)時,實際上是向 desc 結(jié)構(gòu)指向的 buffer 中填充數(shù)據(jù),完了會更新 available ring,然后再通知 host。
  • 當(dāng) host 收到接收數(shù)據(jù)的通知時,首先從 desc 指向的 buffer 中找到 available ring 中添加的 buffer,映射內(nèi)存,同時更新 used ring,并通知 guest 接收數(shù)據(jù)完畢。

2.2Virtio 緩沖池

來賓操作系統(tǒng)(前端)驅(qū)動程序通過緩沖池與 hypervisor 交互。對于 I/O,來賓操作系統(tǒng)提供一個或多個表示請求的緩沖池。例如,您可以提供 3 個緩沖池,第一個表示 Read 請求,后面兩個表示響應(yīng)數(shù)據(jù)。該配置在內(nèi)部被表示為一個散集列表(scatter-gather),列表中的每個條目表示一個地址和一個長度。

2.3核心 API

通過 virtio_device 和 virtqueue(更常見)將來賓操作系統(tǒng)驅(qū)動程序與 hypervisor 的驅(qū)動程序鏈接起來。virtqueue 支持它自己的由 5 個函數(shù)組成的 API。您可以使用第一個函數(shù) add_buf 來向 hypervisor 提供請求。如前面所述,該請求以散集列表的形式存在。對于 add_buf,來賓操作系統(tǒng)提供用于將請求添加到隊列的 virtqueue、散集列表(地址和長度數(shù)組)、用作輸出條目(目標(biāo)是底層 hypervisor)的緩沖池數(shù)量,以及用作輸入條目(hypervisor 將為它們儲存數(shù)據(jù)并返回到來賓操作系統(tǒng))的緩沖池數(shù)量。當(dāng)通過 add_buf 向 hypervisor 發(fā)出請求時,來賓操作系統(tǒng)能夠通過 kick 函數(shù)通知 hypervisor 新的請求。為了獲得最佳的性能,來賓操作系統(tǒng)應(yīng)該在通過 kick 發(fā)出通知之前將盡可能多的緩沖池裝載到 virtqueue。

通過 get_buf 函數(shù)觸發(fā)來自 hypervisor 的響應(yīng)。來賓操作系統(tǒng)僅需調(diào)用該函數(shù)或通過提供的 virtqueue callback 函數(shù)等待通知就可以實現(xiàn)輪詢。當(dāng)來賓操作系統(tǒng)知道緩沖區(qū)可用時,調(diào)用 get_buf 返回完成的緩沖區(qū)。

virtqueue API 的最后兩個函數(shù)是 enable_cb 和 disable_cb。您可以使用這兩個函數(shù)來啟用或禁用回調(diào)進(jìn)程(通過在 virtqueue 中由 virtqueue 初始化的 callback 函數(shù))。注意,該回調(diào)函數(shù)和 hypervisor 位于獨立的地址空間中,因此調(diào)用通過一個間接的 hypervisor 來觸發(fā)(比如 kvm_hypercall)。

緩沖區(qū)的格式、順序和內(nèi)容僅對前端和后端驅(qū)動程序有意義。內(nèi)部傳輸(當(dāng)前實現(xiàn)中的連接點)僅移動緩沖區(qū),并且不知道它們的內(nèi)部表示。

三、virtio 架構(gòu)剖析

3.1整體架構(gòu)概覽

virtio 的架構(gòu)精妙而復(fù)雜,猶如一座精心設(shè)計的大廈,主要由四層構(gòu)成,每一層都肩負(fù)著獨特而重要的使命,它們相互協(xié)作,共同構(gòu)建起高效的 I/O 虛擬化橋梁。

最上層是前端驅(qū)動,它就像是虛擬機內(nèi)部的 “大管家”,運行在虛擬機之中,針對不同類型的設(shè)備,如塊設(shè)備(如磁盤)、網(wǎng)絡(luò)設(shè)備、PCI 模擬設(shè)備、balloon 驅(qū)動(用于動態(tài)管理客戶機內(nèi)存使用)和控制臺驅(qū)動等,有著不同的驅(qū)動程序,但與后端驅(qū)動交互的接口卻是統(tǒng)一的。這些前端驅(qū)動主要負(fù)責(zé)接收用戶態(tài)的請求,就像管家接收家中成員的各種需求,然后按照傳輸協(xié)議將這些請求進(jìn)行封裝,使其能夠在虛擬化環(huán)境中順利傳輸,最后寫 I/O 端口,發(fā)送一個通知到 Qemu 的后端設(shè)備,告知后端有任務(wù)需要處理。

最下層是后端處理程序,它位于宿主機的 Qemu 中,是操作硬件設(shè)備的 “執(zhí)行者”。當(dāng)它接收到前端驅(qū)動發(fā)過來的 I/O 請求后,會從接收的數(shù)據(jù)中按照傳輸協(xié)議的格式進(jìn)行解析,理解請求的具體內(nèi)容。對于網(wǎng)卡等需要與實際物理設(shè)備交互的請求,后端驅(qū)動會對物理設(shè)備進(jìn)行操作,比如向內(nèi)核協(xié)議棧發(fā)送一個網(wǎng)絡(luò)包完成虛擬機對于網(wǎng)絡(luò)的操作,從而完成請求,并且會通過中斷機制通知前端驅(qū)動,告知前端任務(wù)已完成。

中間兩層是 virtio 層和 virtio-ring 層,它們是前后端通信的關(guān)鍵紐帶。virtio 層實現(xiàn)的是虛擬隊列接口,是前后端通信的 “橋梁設(shè)計師”,它在概念上將前端驅(qū)動程序附加到后端驅(qū)動,不同類型的設(shè)備使用的虛擬隊列數(shù)量不同,例如,virtio 網(wǎng)絡(luò)驅(qū)動使用兩個虛擬隊列,一個用于接收,一個用于發(fā)送;而 virtio 塊驅(qū)動僅使用一個隊列 。虛擬隊列實際上被實現(xiàn)為跨越客戶機操作系統(tǒng)和 hypervisor 的銜接點,只要客戶機操作系統(tǒng)和 virtio 后端程序都遵循一定的標(biāo)準(zhǔn),以相互匹配的方式實現(xiàn)它,就可以實現(xiàn)高效通信。

virtio-ring 層則是這座橋梁的 “建筑工人”,它實現(xiàn)了環(huán)形緩沖區(qū)(ring buffer),用于保存前端驅(qū)動和后端處理程序執(zhí)行的信息。它可以一次性保存前端驅(qū)動的多次 I/O 請求,并且交由后端去批量處理,最后實際調(diào)用宿主機中設(shè)備驅(qū)動實現(xiàn)物理上的 I/O 操作,這樣就可以根據(jù)約定實現(xiàn)批量處理,而不是客戶機中每次 I/O 請求都需要處理一次,從而大大提高了客戶機與 hypervisor 信息交換的效率。

3.2關(guān)鍵組件解析

在 virtio 的架構(gòu)中,虛擬隊列接口和環(huán)形緩沖區(qū)是至關(guān)重要的組件,它們就像是人體的神經(jīng)系統(tǒng)和血液循環(huán)系統(tǒng),確保了數(shù)據(jù)的高效傳輸和系統(tǒng)的正常運行。

虛擬隊列接口是 virtio 實現(xiàn)前后端通信的核心機制之一,它定義了一組標(biāo)準(zhǔn)的接口,使得前端驅(qū)動和后端處理程序能夠進(jìn)行有效的交互。每個前端驅(qū)動可以根據(jù)需求使用零個或多個虛擬隊列,這些隊列就像是一條條數(shù)據(jù)傳輸?shù)?“高速公路”,不同類型的設(shè)備根據(jù)自身的特點選擇合適數(shù)量的隊列。virtio 網(wǎng)絡(luò)驅(qū)動需要同時處理數(shù)據(jù)的接收和發(fā)送,因此使用兩個虛擬隊列,一個專門用于接收數(shù)據(jù),另一個用于發(fā)送數(shù)據(jù),這樣可以提高數(shù)據(jù)處理的效率,避免接收和發(fā)送數(shù)據(jù)時的沖突。

而環(huán)形緩沖區(qū)則是虛擬隊列的具體實現(xiàn)方式,它是一段共享內(nèi)存,被劃分為三個主要部分:描述符表(Descriptor Table)、可用描述符表(Available Ring)和已用描述符表(Used Ring) 。描述符表用于存儲一些關(guān)聯(lián)的描述符,每個描述符記錄一個對 buffer 的描述,就像一個個貨物清單,詳細(xì)記錄了數(shù)據(jù)的位置、大小等信息;可用描述符表用于保存前端驅(qū)動提供給后端設(shè)備且后端設(shè)備可以使用的描述符,它就像是一個 “待處理任務(wù)清單”,后端設(shè)備可以從中獲取需要處理的數(shù)據(jù);已用描述符表用于保存后端處理程序已經(jīng)處理過并且尚未反饋給前端驅(qū)動的描述,它就像是一個 “已完成任務(wù)清單”,前端驅(qū)動可以從中了解哪些數(shù)據(jù)已經(jīng)被處理完畢。

當(dāng)虛擬機需要發(fā)送請求到后端設(shè)備時,前端驅(qū)動會將存有數(shù)據(jù)的 buffer 添加到 virtqueue 中,然后更新可用描述符表,將對應(yīng)的描述符標(biāo)記為可用,并通過寫入寄存器的方式通知后端設(shè)備,就像在 “待處理任務(wù)清單” 上添加了一項任務(wù),并通知后端工作人員。后端設(shè)備接收到通知后,從可用描述符表中讀取請求信息,根據(jù)描述符表中的信息從共享內(nèi)存中讀出數(shù)據(jù)進(jìn)行處理。處理完成后,后端設(shè)備將響應(yīng)狀態(tài)存放在已用描述符表中,并通知前端驅(qū)動,就像在 “已完成任務(wù)清單” 上記錄下完成的任務(wù),并通知前端工作人員。前端驅(qū)動從已用描述符表中得到請求完成信息,并獲取請求的數(shù)據(jù),完成一次數(shù)據(jù)傳輸?shù)倪^程。

3.3初始化

⑴前端初始化

Virtio設(shè)備遵循linux內(nèi)核通用的設(shè)備模型,bus類型為virtio_bus,對它的理解可以類似PCI設(shè)備。設(shè)備模型的實現(xiàn)主要在driver/virtio/virtio.c文件中。

  • 設(shè)備注冊
int register_virtio_device(struct virtio_device *dev)
-> dev->dev.bus = &virtio_bus;			//填寫bus類型
-> err = ida_simple_get(&virtio_index_ida, 0, 0, GFP_KERNEL);//分配一個唯一的設(shè)備index標(biāo)示
-> dev->config->reset(dev);				//重置config
-> err = device_register(&dev->dev);	//在系統(tǒng)中注冊設(shè)備
  • 驅(qū)動注冊
int register_virtio_driver(struct virtio_driver *driver)
-> driver->driver.bus = &virtio_bus; 	//填寫bus類型
->driver_register(&driver->driver);		//向系統(tǒng)中注冊driver
  • 設(shè)備匹配
virtio_bus. match = virtio_dev_match
//用于甄別總線上設(shè)備是否與virtio對應(yīng)的設(shè)備匹配,
//方法是查看設(shè)備id是否與driver中保存的id_table中的某個id匹配。
  • 設(shè)備發(fā)現(xiàn)
virtio_bus. probe = virtio_dev_probe
// virtio_dev_probe函數(shù)首先是
-> device_features = dev->config->get_features(dev);	//獲得設(shè)備的配置信息
-> // 查找device和driver共同支持的feature,設(shè)置dev->features
-> dev->config->finalize_features(dev);	//確認(rèn)需要使用的features
-> drv->probe(dev);	//調(diào)用driver的probe函數(shù),通常這個函數(shù)進(jìn)行具體設(shè)備的初始化,

例如virtio_blk驅(qū)動中用于初始化queue,創(chuàng)建磁盤設(shè)備并初始化一些必要的數(shù)據(jù)結(jié)構(gòu)

當(dāng)virtio后端模擬出virtio_blk設(shè)備后,guest os掃描到此virtio設(shè)備,然后調(diào)用virtio_pci_driver中virtio_pci_probe函數(shù)完成pci設(shè)備的啟動。

注冊一條virtio_bus,同時在virtio總線進(jìn)行注冊設(shè)備。當(dāng)virtio總線進(jìn)行注冊設(shè)備register_virtio_device,將調(diào)用virtio總線的probe函數(shù):virtio_dev_probe()。該函數(shù)遍歷驅(qū)動,找到支持驅(qū)動關(guān)聯(lián)到該設(shè)備并且調(diào)用virtio_driver probe。

virtblk_probe函數(shù)調(diào)用流程如下:

  • virtio_config_val:得到硬件上支持多少個segments(因為都是聚散IO,segment應(yīng)該是指聚散列表的最大項數(shù)),這里需要注意的是頭部和尾部各需要一個額外的segment
  • init_vq:調(diào)用init_vq函數(shù)進(jìn)行virtqueue、vring等相關(guān)的初始化設(shè)置工作。
  • alloc_disk:調(diào)用alloc_disk為此虛擬磁盤分配一個gendisk類型的對象
  • blk_init_queue:注冊queue的處理函數(shù)為do_virtblk_request
static int __devinit virtblk_probe(struct virtio_device *vdev)
{
	...
	/* 得到硬件上支持多少個segments
	   (因為都是聚散IO,這個segment應(yīng)該是指聚散列表的最大項數(shù)),	
	   這里需要注意的是頭部和尾部各需要一個額外的segment */

	err = virtio_config_val(vdev, VIRTIO_BLK_F_SEG_MAX,offsetof(struct virtio_blk_config, seg_max),&sg_elems);
	...

	/* 分配vq,調(diào)用virtio_find_single_vq(vdev, blk_done, "requests");
	   分配單個vq,名字為”request”,注冊	的通知函數(shù)是blk_done */
	err = init_vq(vblk);

	/* 調(diào)用alloc_disk為此虛擬磁盤分配一個gendisk類型的對象,
		對象指針保存在virtio_blk結(jié)構(gòu)的disk	中*/
	vblk->disk = alloc_disk(1 << PART_BITS);

	/* 分配request_queue結(jié)構(gòu),從屬于virtio-blk的gendisk結(jié)構(gòu)下
	   初始化gendisk及disk queue,注冊queue	的處理函數(shù)為do_virtblk_request,
	   其中queuedata也設(shè)置為virtio_blk結(jié)構(gòu)。*/
	q = vblk->disk->queue = blk_init_queue(do_virtblk_request, NULL);
	...

	add_disk(vblk->disk); //使設(shè)備對外生效
}

init_vq完成virtqueue和vring的分配,設(shè)置隊列的回調(diào)函數(shù),中斷處理函數(shù),流程如下:

-->init_vq
	-->virtio_find_single_vq
		-->vp_find_vqs
			-->vp_try_to_find_vqs
				-->setup_vq
					-->vring_new_virtqueue
				-->request_irq

分配vq的函數(shù)init_vq:

static int init_vq(struct virtio_blk *vblk)
{
	...
	vblk->vq = virtio_find_single_vq(vblk->vdev, blk_done, "requests");
	...
}


struct virtqueue *virtio_find_single_vq(struct virtio_device *vdev,vq_callback_t *c, const char *n)
{
	vq_callback_t *callbacks[] = { c };
	const char *names[] = { n };
		struct virtqueue *vq;
	
	/* 調(diào)用find_vqs回調(diào)函數(shù)(對應(yīng)vp_find_vqs函數(shù),
	   在virtio_pci_probe中設(shè)置)進(jìn)行具體的設(shè)置。
	   會將相應(yīng)的virtqueue對象指針存放在vqs這個臨時指針數(shù)組中 */
	int err = vdev->config->find_vqs(vdev, 1, &vq, callbacks, names);
	if (err < 0)
		return ERR_PTR(err);
	return vq;
}


static int vp_find_vqs(struct virtio_device *vdev, unsigned nvqs,
		       struct virtqueue *vqs[],
		       vq_callback_t *callbacks[],
		       const char *names[])
{
	int err;
	/* 這個函數(shù)中只是三次調(diào)用了vp_try_to_find_vqs函數(shù)來完成操作,
	   只是每次想起傳送的參數(shù)有些不一樣,該函數(shù)的最后兩個參數(shù):
	   use_msix表示是否使用MSI-X機制的中斷、per_vq_vectors表示是否對
	   每一	個virtqueue使用使用一個中斷vector */
	/* Try MSI-X with one vector per queue. */
	err = vp_try_to_find_vqs(vdev, nvqs, vqs, callbacks, names, true, true);
	if (!err)
		return 0;
	err = vp_try_to_find_vqs(vdev, nvqs, vqs, callbacks, names,true, false);
	if (!err)
		return 0;
	return vp_try_to_find_vqs(vdev, nvqs, vqs, callbacks, names,false, false);
}

Virtio設(shè)備中斷,有兩種產(chǎn)生中斷情況:

  • 當(dāng)設(shè)備的配置信息發(fā)生改變(config changed),會產(chǎn)生一個中斷(稱為change中斷),中斷處理程序需要調(diào)用相應(yīng)的處理函數(shù)(需要驅(qū)動定義)
  • 當(dāng)設(shè)備向隊列中寫入信息時,會產(chǎn)生一個中斷(稱為vq中斷),中斷處理函數(shù)需要調(diào)用相應(yīng)的隊列的回調(diào)函數(shù)(需要驅(qū)動定義)

三種中斷處理方式:

1). 不用msix中斷,則change中斷和所有vq中斷共用一個中斷irq。

中斷處理函數(shù):vp_interrupt。
vp_interrupt函數(shù)中包含了對change中斷和vq中斷的處理。

2). 使用msix中斷,但只有2個vector;一個用來對應(yīng)change中斷,一個對應(yīng)所有隊列的vq中斷。

change中斷處理函數(shù):vp_config_changed
vq中斷處理函數(shù):vp_vring_interrupt

3). 使用msix中斷,有n+1個vector;一個用來對應(yīng)change中斷,n個分別對應(yīng)n個隊列的vq中斷。每個vq一個vector。

static int vp_try_to_find_vqs(struct virtio_device *vdev, unsigned nvqs,
			      struct virtqueue *vqs[],
			      vq_callback_t *callbacks[],
			      const char *names[],
			      bool use_msix,
			      bool per_vq_vectors)
{
	struct virtio_pci_device *vp_dev = to_vp_device(vdev);
	u16 msix_vec;
	int i, err, nvectors, allocated_vectors;

	if (!use_msix) {
 	/* 不用msix,所有vq共用一個irq ,設(shè)置中斷處理函數(shù)vp_interrupt*/
		err = vp_request_intx(vdev);
	} else {
		if (per_vq_vectors) {
			nvectors = 1;
			for (i = 0; i < nvqs; ++i)
				if (callbacks[i])
					++nvectors;
		} else {
			/* Second best: one for change, shared for all vqs. */
			nvectors = 2;
		}
		/*per_vq_vectors為0,設(shè)置處理函數(shù)vp_vring_interrupt*/
		err = vp_request_msix_vectors(vdev, nvectors, per_vq_vectors);
	}
	
	for (i = 0; i < nvqs; ++i) {
		if (!callbacks[i] || !vp_dev->msix_enabled)
			msix_vec = VIRTIO_MSI_NO_VECTOR;
		else if (vp_dev->per_vq_vectors)
			msix_vec = allocated_vectors++;
		else
			msix_vec = VP_MSIX_VQ_VECTOR;
		vqs[i] = setup_vq(vdev, i, callbacks[i], names[i], msix_vec);
		...
		
		/* 如果per_vq_vectors為1,則為每個隊列指定一個vector,
		   vq中斷處理函數(shù)為vring_interrupt*/
		err = request_irq(vp_dev->msix_entries[msix_vec].vector,
				  vring_interrupt, 0,
				  vp_dev->msix_names[msix_vec],
				  vqs[i]);
	}
	return 0;
}

setup_vq完成virtqueue(主要用于數(shù)據(jù)的操作)、vring(用于數(shù)據(jù)的存放)的分配和初始化任務(wù):

static struct virtqueue *setup_vq(struct virtio_device *vdev, unsigned index, 
								  void (*callback)(struct virtqueue *vq),
								  const char *name,u16 msix_vec)
{
	struct virtqueue *vq;

	/* 寫寄存器退出guest,設(shè)置設(shè)備的隊列序號,
	對于塊設(shè)備就是0(最大只能為VIRTIO_PCI_QUEUE_MAX 64) */
	iowrite16(index, vp_dev->ioaddr + VIRTIO_PCI_QUEUE_SEL);

	/*得到硬件隊列的深度num*/
	num = ioread16(vp_dev->ioaddr + VIRTIO_PCI_QUEUE_NUM);
	...
	/* IO同步信息,如虛擬隊列地址,會調(diào)用virtio_queue_set_addr進(jìn)行處理*/
	iowrite32(virt_to_phys(info->queue) >> VIRTIO_PCI_QUEUE_ADDR_SHIFT,
		      vp_dev->ioaddr + VIRTIO_PCI_QUEUE_PFN);
	...
	/* 調(diào)用該函數(shù)分配vring_virtqueue對象,該結(jié)構(gòu)中既包含了vring、又包含了virtqueue,并且返回	virtqueue對象指針*/
	vq = vring_new_virtqueue(info->num, VIRTIO_PCI_VRING_ALIGN,
				 vdev, info->queue, vp_notify, callback, name);
	...
	return vq;
}

IO同步信息,如虛擬隊列地址,會調(diào)用virtio_queue_set_addr進(jìn)行處理:

virtio_queue_set_addr(vdev, vdev->queue_sel, addr);
--> vdev->vq[n].pa = addr;				//n=vdev->queue_sel,即同步隊列地址
--> virtqueue_init(&vdev->vq[n]);		//初始化后端的虛擬隊列
--> target_phys_addr_t pa = vq->pa;	    //主機vring虛擬首地址
--> vq->vring.desc = pa; 				//同步desc地址
--> vq->vring.avail = pa + vq->vring.num * sizeof(VRingDesc); //同步avail地址
--> vq->vring.used = vring_align(vq->vring.avail + 
								 offsetof(VRingAvail, ring[vq->vring.num]),
								 VIRTIO_PCI_VRING_ALIGN); 	  //同步used地址

其中,pa是由客戶機傳送過來的物理頁地址,在主機中就是主機的虛擬頁地址,賦值給主機中對應(yīng)vq中的vring,則同步了主客機中虛擬隊列地址,之后vring中的當(dāng)前可用緩沖描述符avail、已使用緩沖used均得到同步。

分配vring_virtqueue對象由vring_new_virtqueue函數(shù)完成:

struct virtqueue *vring_new_virtqueue(unsigned int num,							      unsigned int vring_align,
								      struct virtio_device *vdev,							      void *pages,							      void (*notify)(struct virtqueue *),							      void (*callback)(struct virtqueue *),							      const char *name)
{
	struct vring_virtqueue *vq;
	unsigned int i;

	/* We assume num is a power of 2. */
	if (num & (num - 1)) {
		dev_warn(&vdev->dev, "Bad virtqueue length %u\n", num);
		return NULL;
	}
	/* 調(diào)用vring_init函數(shù)初始化vring對象,
	   其desc、avail、used三個域瓜分了上面的
	   setup_vp函數(shù)第一步中分配的內(nèi)存頁面 */
	vring_init(&vq->vring, num, pages, vring_align);

	/*初始化virtqueue對象(注意其callback會被設(shè)置成virtblk_done函數(shù)*/
	vq->vq.callback = callback;
	vq->vq.vdev = vdev;
	vq->vq.name = name;
	vq->notify = notify;
	vq->broken = false;
	vq->last_used_idx = 0;
	vq->num_added = 0;
	list_add_tail(&vq->vq.list, &vdev->vqs);

	/* No callback? Tell other side not to bother us. */
	if (!callback)
		vq->vring.avail->flags |= VRING_AVAIL_F_NO_INTERRUPT;
	/* Put everything in free lists. */
	vq->num_free = num;
	vq->free_head = 0;
	for (i = 0; i < num-1; i++) {
		vq->vring.desc[i].next = i+1;
		vq->data[i] = NULL;
	}
	vq->data[i] = NULL;
	/*返回virtqueue對象指針*/
	return &vq->vq;
}

調(diào)用vring_init函數(shù)初始化vring對象:

static inline void vring_init(struct vring *vr, unsigned int num, void *p,
			     			  unsigned long align)
{
	vr->num = num;
	vr->desc = p;
	vr->avail = p + num*sizeof(struct vring_desc);
	vr->used = (void *)(((unsigned long)&vr->avail->ring[num] + align-1)& ~(align - 1));
}

⑵后端初始化

后端驅(qū)動的初始化流程實際是后端驅(qū)動的數(shù)據(jù)結(jié)構(gòu)進(jìn)行初始化,設(shè)置PCI設(shè)備的信息,并結(jié)合到virtio設(shè)備中,設(shè)置主機狀態(tài),配置并初始化虛擬隊列,為每個塊設(shè)備綁定一個虛擬隊列及隊列處理函數(shù),并綁定設(shè)備處理函數(shù),以處理IO請求。virtio-block后端初始化流程:

type_init(virtio_pci_register_types)
	--> type_register_static(&virtio_blk_info) // 注冊一個設(shè)備結(jié)構(gòu),為PCI子設(shè)備
		--> class_init = virtio_blk_class_init,
			--> k->init = virtio_blk_init_pci;


static int virtio_blk_init_pci(PCIDevice *pci_dev)
{
    VirtIOPCIProxy *proxy = DO_UPCAST(VirtIOPCIProxy, pci_dev, pci_dev);
    VirtIODevice *vdev;
	...
	
    vdev = virtio_blk_init(&pci_dev->qdev, &proxy->blk);
	...
    virtio_init_pci(proxy, vdev);
    
    /* make the actual value visible */
    proxy->nvectors = vdev->nvectors;
    return 0;
}

調(diào)用virtio_blk_init來初始化virtio-blk設(shè)備,virtio_blk_init代碼如下:

VirtIODevice *virtio_blk_init(DeviceState *dev, VirtIOBlkConf *blk)
{
    VirtIOBlock *s;
    static int virtio_blk_id;
	...
	/* virtio_common_init初始化一個VirtIOBlock結(jié)構(gòu),
	   這里主要是分配一個VirtIODevice	結(jié)構(gòu)并為它賦值,
	   VirtIODevice結(jié)構(gòu)主要描述IO設(shè)備的一些配置接口和屬性。
	   VirtIOBlock結(jié)構(gòu)第一個域是VirtIODevice結(jié)構(gòu),VirtIOBlock結(jié)構(gòu)
	   還包括一些其他的塊設(shè)備屬性和狀態(tài)參數(shù)。*/
    s = (VirtIOBlock *)virtio_common_init("virtio-blk", VIRTIO_ID_BLOCK,
                                          sizeof(struct virtio_blk_config),
                                          sizeof(VirtIOBlock));
	/* 對VirtIOBlock結(jié)構(gòu)中的域賦值,其中比較重要的是對一些virtio
	   通用配置接口的賦值(get_config,set_config,get_features,set_status,reset),
	   如此,virtio_blk便	有了自定義的配置。*/
    s->vdev.get_config = virtio_blk_update_config;
    s->vdev.set_config = virtio_blk_set_config;
    s->vdev.get_features = virtio_blk_get_features;
    s->vdev.set_status = virtio_blk_set_status;
    s->vdev.reset = virtio_blk_reset;
    s->bs = blk->conf.bs;
    s->conf = &blk->conf;
    s->blk = blk;
    s->rq = NULL;
    s->sector_mask = (s->conf->logical_block_size / BDRV_SECTOR_SIZE) - 1;

	/* 初始化vq,virtio_add_queue為設(shè)置vq的中vring處理的最大個數(shù)是128,
	   注冊	handle_output函數(shù)為virtio_blk_handle_output(host端處理函數(shù))*/
    s->vq = virtio_add_queue(&s->vdev, 128, virtio_blk_handle_output);

	/* qemu_add_vm_change_state_handler(virtio_blk_dma_restart_cb, s);
	   設(shè)置vm狀態(tài)改	變的處理函數(shù)為virtio_blk_dma_restart_cb*/
    qemu_add_vm_change_state_handler(virtio_blk_dma_restart_cb, s);
    s->qdev = dev;

	/* register_savevm注冊虛擬機save和load函數(shù)(熱遷移)*/
	register_savevm(dev, "virtio-blk", virtio_blk_id++, 2,
                    virtio_blk_save, virtio_blk_load, s);
	...
    return &s->vdev;
}

//初始化vq,調(diào)用virtio_add_queue:
VirtQueue *virtio_add_queue(VirtIODevice *vdev, int queue_size,
                            void (*handle_output)(VirtIODevice *, VirtQueue *))
{
	...
    vdev->vq[i].vring.num = queue_size;  //設(shè)置隊列的深度
    vdev->vq[i].handle_output = handle_output;  //注冊隊列的處理函數(shù)
    return &vdev->vq[i];
}

初始化virtio-PCI信息,分配bar,注冊接口以及接口處理函數(shù);設(shè)備綁定virtio-pci的ops,設(shè)置主機特征,調(diào)用函數(shù)virtio_init_pci來初始化virtio-blk pci相關(guān)信息:

void virtio_init_pci(VirtIOPCIProxy *proxy, VirtIODevice *vdev)
{
    uint8_t *config;
    uint32_t size; 
	...
	/* memory_region_init_io():初始化IO內(nèi)存,
	   并設(shè)置IO內(nèi)存操作和內(nèi)存讀寫函數(shù)	virtio_pci_config_ops*/
    memory_region_init_io(&proxy->bar, &virtio_pci_config_ops, proxy,"virtio-pci", size);

	/*將IO內(nèi)存綁定到PCI設(shè)備,即初始化bar,給bar注冊pci地址*/
    pci_register_bar(&proxy->pci_dev, 0, PCI_BASE_ADDRESS_SPACE_IO,
					 &proxy->bar);

    if (!kvm_has_many_ioeventfds()) {
        proxy->flags &= ~VIRTIO_PCI_FLAG_USE_IOEVENTFD;
    }

	/*綁定virtio-pci總線的ops并指向設(shè)備代理proxy*/
    virtio_bind_device(vdev, &virtio pci_bindings, proxy);
    
    proxy->host_features |= 0x1 << VIRTIO_F_NOTIFY_ON_EMPTY;
    proxy->host_features |= 0x1 << VIRTIO_F_BAD_FEATURE;
    proxy->host_features = vdev->get_features(vdev, proxy->host_features);
}

其中,virtio-pic讀寫操作為virtio_pci_config_ops:

static const MemoryRegionPortio virtio_portio[] = {
    { 0, 0x10000, 2, .write = virtio_pci_config_writew, },
	...
    { 0, 0x10000, 2, .read = virtio_pci_config_readw, },
};

在設(shè)備注冊完成后,qemu調(diào)用io_region_add進(jìn)行io端口注冊:

static void io_region_add(MemoryListener *listener,MemoryRegionSection *section)
{  
	...
    /*io端口信息初始化*/
    iorange_init(&mrio->iorange, &memory_region_iorange_ops,
                 section->offset_within_address_space, section->size);
    /*io端口注冊*/
    ioport_register(&mrio->iorange);
}

ioport_register調(diào)用register_ioport_read及register_ioport_write將io端口對應(yīng)的回調(diào)函數(shù)保存到ioport_write_table數(shù)組中:

int register_ioport_write(pio_addr_t start, int length, int size,IOPortWriteFunc *func, void *opaque)
{
	...
    for(i = start; i < start + length; ++i) {
    	/*設(shè)置對應(yīng)端口的回調(diào)函數(shù)*/
        ioport_write_table[bsize][i] = func;  
		...
   }
   return 0;
}

四、virtio 代碼深度探索

4.1數(shù)據(jù)結(jié)構(gòu)探秘

在 virtio 的代碼世界里,vring 和 virtqueue 是最為關(guān)鍵的數(shù)據(jù)結(jié)構(gòu),它們就像是代碼大廈的基石,支撐著整個 virtio 的功能實現(xiàn)。

vring 是 virtio 前端驅(qū)動和后端 Hypervisor 虛擬設(shè)備之間傳輸數(shù)據(jù)的核心載體 ,它主要由描述符表(Descriptor Table)、可用描述符表(Available Ring)和已用描述符表(Used Ring)這三個部分組成。在早期的 virtio 1.0 版本及之前,這三個部分是相互分離的,形成了所謂的 Split Virtqueue。在這種模式下,每個部分都有其特定的讀寫權(quán)限,并且通過 next 字段將多個描述符串接成描述符鏈表的形式來描述一個 IO 請求,這種方式雖然能夠?qū)崿F(xiàn)基本的數(shù)據(jù)傳輸功能,但在數(shù)據(jù)管理和處理效率上存在一定的局限性。

隨著技術(shù)的發(fā)展,virtio 1.1 版本引入了 Packed Virtqueue,它將描述符表、可用描述符表和已用描述符表合并在一起,形成了一個更加緊湊的結(jié)構(gòu)。在這種結(jié)構(gòu)中,增加了 Flag 的相關(guān)標(biāo)記值,去除了 next 字段,同時增加了 Buffer ID,對 entries 支持進(jìn)行了增強。這樣的設(shè)計使得數(shù)據(jù)管理更加高效,也更容易增加與硬件的親和性并更好地利用 Cache。就像重新規(guī)劃了倉庫的布局,使得貨物的存放和取用更加方便快捷。

而 virtqueue 則是對 vring 的進(jìn)一步封裝和管理,它包含了 vring 以及其他一些與隊列相關(guān)的信息和操作函數(shù) 。在實際運行中,Client 會把 Buffers 插入到 virtqueue 中,隊列會根據(jù)不同設(shè)備安排不同的數(shù)量。網(wǎng)絡(luò)設(shè)備通常有兩個隊列,一個用于接收數(shù)據(jù),一個用于發(fā)送數(shù)據(jù),這樣可以實現(xiàn)數(shù)據(jù)的高效處理,避免接收和發(fā)送數(shù)據(jù)時的沖突。virtqueue 還提供了一些對 vring 進(jìn)行操作的函數(shù),如 add_buf 用于將數(shù)據(jù)緩沖區(qū)添加到隊列中,get_buf 用于從隊列中獲取數(shù)據(jù)緩沖區(qū),kick 用于通知對端有新的數(shù)據(jù)到來等。這些函數(shù)就像是倉庫管理員的工具,幫助管理員高效地管理倉庫中的貨物。

4.2核心流程解讀

以網(wǎng)絡(luò)設(shè)備為例,virtio 的數(shù)據(jù)收發(fā)流程是其核心功能的具體體現(xiàn),這個流程就像是一場緊張有序的接力賽,各個環(huán)節(jié)緊密配合,確保數(shù)據(jù)的高效傳輸。

當(dāng)網(wǎng)絡(luò)設(shè)備發(fā)送數(shù)據(jù)時,前端驅(qū)動首先會通過 start_xmit 函數(shù)開始數(shù)據(jù)傳輸?shù)穆贸?。在這個函數(shù)中,會調(diào)用 xmit_skb 函數(shù)來具體處理數(shù)據(jù)的發(fā)送。xmit_skb 函數(shù)會先使用 sg_init_table 初始化 sg 列表,這個 sg 列表就像是一個貨物清單,記錄了要發(fā)送的數(shù)據(jù)的相關(guān)信息。然后,sg_set_buf 將 sg 指向特定的 buffer,skb_to_sgvec 將 socket buffer 中的數(shù)據(jù)填充到 sg 中,就像是將貨物裝載到運輸工具上。

接著,通過 virtqueue_add_outbuf 將 sg 添加到 Virtqueue 中,并更新 Avail 隊列中描述符的索引值,這一步就像是將裝滿貨物的運輸工具放入倉庫的待發(fā)貨區(qū)域,并記錄下貨物的位置信息。最后,virtqueue_notify 通知 Device,可以過來取數(shù)據(jù)了,就像是通知快遞員來取貨。

在數(shù)據(jù)接收方面,當(dāng) Qemu 收到 tap 發(fā)送過來的數(shù)據(jù)包后,會在 virtio_net_receive 函數(shù)中把數(shù)據(jù)拷貝到虛擬機的 virtio 網(wǎng)卡接收隊列 。這個過程就像是快遞員將包裹送到倉庫的接收區(qū)域。然后,會向虛擬機注入一個中斷,這樣虛擬機便感知到有網(wǎng)絡(luò)數(shù)據(jù)報文的到來。在虛擬機內(nèi)部,數(shù)據(jù)接收流程從 napi_gro_receive 函數(shù)開始,它會將接收到的數(shù)據(jù)傳輸給網(wǎng)絡(luò)層。接著,netif_receive_skb 函數(shù)會將 skb(套接字緩沖區(qū))傳遞給網(wǎng)絡(luò)層進(jìn)行處理。在驅(qū)動的 poll 方法中,會調(diào)用 napi_poll 函數(shù),具體到 virtio_net.c 中就是 virtnet_poll 函數(shù)。

在這個函數(shù)中,會調(diào)用 receive_buf 函數(shù)將接收到的數(shù)據(jù)轉(zhuǎn)換成 skb,然后根據(jù)接收類型(如 XDP_PASS、XDP_TX 等)對 virtqueue 中的數(shù)據(jù)進(jìn)行不同的處理。如果檢測到本次中斷接收數(shù)據(jù)完成,則會重新開啟中斷,等待下一次中斷接收數(shù)據(jù)。在整個過程中,還會涉及到一些其他的函數(shù)和操作,如 skb_recv_done 函數(shù)用于數(shù)據(jù)接收完成后的回調(diào),virtqueue_napi_schedule 函數(shù)用于調(diào)度 NAPI(網(wǎng)絡(luò)接口輪詢)等。這些函數(shù)和操作相互配合,確保了數(shù)據(jù)接收的高效和穩(wěn)定。

責(zé)任編輯:武曉燕 來源: 深度Linux
相關(guān)推薦

2025-07-14 02:22:00

2015-07-14 09:45:09

虛擬化

2021-01-06 09:01:05

javaclass

2021-02-14 16:49:22

Linux虛擬化Virtio

2024-03-18 09:44:02

HashMap算法Java

2021-04-30 09:46:08

虛擬化Virtio-Net云計算

2024-09-26 19:39:23

2024-09-24 10:36:29

2013-06-06 09:31:52

2014-11-12 13:22:34

2023-04-12 08:04:09

MapReduce大數(shù)據(jù)框架

2014-06-27 16:43:18

視頻會議終端華為

2025-01-24 00:00:00

JavaHotSpot虛擬機

2023-09-04 09:12:10

設(shè)計業(yè)務(wù)耦合

2018-01-17 15:15:22

虛擬化IO半虛擬化

2017-11-29 14:57:47

虛擬化內(nèi)核IO

2021-12-21 15:37:46

NFV虛擬化IO虛擬化

2010-07-26 10:01:01

虛擬化

2009-12-30 14:03:36

ADO.NET Ent
點贊
收藏

51CTO技術(shù)棧公眾號

豆国产97在线| 亚洲人成精品久久久| 国产精品嫩草影院av蜜臀| 成人性生交大片免费看视频直播 | 欧美成人精品影院| 少妇熟女视频一区二区三区| 日韩伦理精品| 中文字幕亚洲成人| 久久精品国产一区二区三区不卡| 最近国语视频在线观看免费播放| 国产精品a级| 国产亚洲精品激情久久| 天天av天天操| 欧美精选视频一区二区| 一区二区不卡在线播放| 亚洲精品二区| 日本a一级在线免费播放| 国产在线国偷精品免费看| 欧美专区福利在线| 福利所第一导航| 欧美日韩在线观看视频小说| 欧美精品一区视频| 天天摸天天舔天天操| 国产一二三在线| 一区二区三区欧美亚洲| 亚洲女人毛片| 蜜桃免费在线| 成人性生交大片| 成人免费午夜电影| 日韩乱码一区二区三区| 亚洲三级视频| 欧美极品美女视频网站在线观看免费| 国内毛片毛片毛片毛片毛片| 蜜臀91精品国产高清在线观看| 日韩美女一区二区三区| 中文字幕在线综合| 久久91导航| 欧美日韩国产影院| 日本免费a视频| 怡红院在线观看| 亚洲日本护士毛茸茸| 亚洲黄色一区二区三区| 国产69精品久久app免费版| 91在线国产观看| 国产一区二区不卡视频在线观看| 亚洲av无码乱码国产精品| 精品一区二区三区的国产在线播放| 日韩免费在线看| 日本一区二区三区精品| 国产亚洲综合精品| 7m精品福利视频导航| 国产中文字幕免费| 激情视频一区| 国内精品久久久久影院 日本资源| 久久久久久免费观看| 欧美久久99| 欧美激情一级精品国产| 久久久久久久久久久网| 尤物网精品视频| 91国产一区在线| 成人精品免费在线观看| 亚洲综合日韩| 国产精品女主播视频| 综合久久中文字幕| 精品亚洲成a人| 99re在线| 污污网站在线免费观看| 久久久午夜精品| 亚洲男人天堂2023| 久久偷拍免费视频| 国产一区二区区别| 中文字幕av日韩| 三级黄色在线观看| 牛夜精品久久久久久久99黑人| 九九久久国产精品| 国产无遮挡又黄又爽在线观看 | 国产免费成人在线| 性欧美1819sex性高清| 欧美私人免费视频| 亚洲av毛片在线观看| 国产精品jk白丝蜜臀av小说| 日韩电视剧在线观看免费网站| 精品欧美一区二区久久久| 日本精品黄色| 欧美激情一区二区三区久久久| 国产免费av一区二区| 青青国产91久久久久久 | 加勒比视频一区| 亚洲欧洲免费视频| 久久久久久久久久97| 亚洲二区精品| 国产精自产拍久久久久久| 亚洲av无码国产精品久久不卡| 91在线播放网址| 一区二区三区三区在线| 国内激情视频在线观看| 欧美日本一区二区在线观看| 好吊色视频一区二区三区| 成人嫩草影院| 亚洲3p在线观看| 中文字幕第315页| 成人av在线播放网站| 午夜精品一区二区在线观看| 日本性爱视频在线观看| 欧美亚男人的天堂| 成人在线视频免费播放| 99久久综合| 欧美有码在线观看| www.久久色| 国产人伦精品一区二区| 国产一区二区四区| 国产高清日韩| 亚洲视频精品在线| 日本中文字幕免费| 国产精品一区二区三区网站| 热re99久久精品国99热蜜月| 亚洲精品97久久中文字幕| 久久一区二区三区四区| 国产专区在线视频| 黄色成人小视频| 亚洲精品日韩在线| 国产一级一片免费播放放a| 久久精品国产秦先生| 麻豆成人小视频| 岛国毛片av在线| 777午夜精品视频在线播放| 免费在线观看你懂的| 欧美黄污视频| 成人国产精品一区二区| av网站在线免费观看| 欧美性高潮床叫视频| 成人做爰www看视频软件 | 91午夜在线观看| 国产精品一区二区三区四区在线观看 | 日本一区二区三区在线观看视频| 伊人婷婷欧美激情| 中文字幕色网站| 我不卡影院28| 国产日韩欧美中文| 五月天婷婷在线视频| 在线精品观看国产| 夫妇交换中文字幕| 日本aⅴ亚洲精品中文乱码| 欧美日韩电影一区二区三区| 天堂网在线最新版www中文网| 精品久久久久久中文字幕| 日批视频在线看| 欧美1区2区3区| 99理论电影网| 牛牛精品在线| 精品黑人一区二区三区久久| 国产在线免费视频| 成人久久18免费网站麻豆 | 久久精品日产第一区二区三区精品版| 波多野结衣中文在线| 精品区一区二区| 久久久久无码精品国产| 成人黄色网址在线观看| 妞干网在线视频观看| 日本三级久久| 日本高清+成人网在线观看| 国产视频二区在线观看| 欧美日韩视频在线观看一区二区三区 | 一级特黄色大片| 亚洲欧美乱综合| 久久无码专区国产精品s| 伊人久久婷婷| 日本10禁啪啪无遮挡免费一区二区| 午夜日韩成人影院| 久久精品精品电影网| 午夜精品一二三区| 黄色成人在线播放| 91资源在线播放| 韩国毛片一区二区三区| 欧美精品卡一卡二| 国产亚洲一区| 亚洲a中文字幕| jizzjizz中国精品麻豆| 日韩久久精品成人| 亚洲中文一区二区三区| 一区二区三区美女视频| 99久久人妻精品免费二区| 日韩国产成人精品| 成人免费看片视频在线观看| 久久综合另类图片小说| 国产精品极品在线| 污的网站在线观看| 亚洲天堂网站在线观看视频| a天堂视频在线| 岛国精品视频在线播放| 中文国语毛片高清视频| 99久久免费视频.com| 第四色婷婷基地| 亚洲成色精品| 一区二区三区四区视频在线| 91精品导航| 国产精品丝袜视频| av免费不卡| 久久精品在线视频| 青梅竹马是消防员在线| 日韩免费一区二区三区在线播放| 成人免费毛片男人用品| 亚洲黄色录像片| 女人黄色一级片| 成人aa视频在线观看| 91 视频免费观看| 久久国产欧美| av网站手机在线观看| 日韩欧美中文| 欧美日韩亚洲一区二区三区在线观看| 国产色99精品9i| 国产成人久久久| 鲁鲁在线中文| 欧美高清无遮挡| 国产写真视频在线观看| 亚洲最新在线视频| 欧美自拍偷拍第一页| 777a∨成人精品桃花网| 成人h动漫精品一区二区下载 | 中文区中文字幕免费看| 午夜精品久久久久影视| 国产大学生自拍| 国产精品灌醉下药二区| 成年人在线免费看片| 99在线精品免费| 黄色av电影网站| 国产精品一区二区三区网站| wwwwwxxxx日本| 日产欧产美韩系列久久99| 5月婷婷6月丁香| 91久久久久| 黄色激情在线视频| 欧美三级视频| 成人免费在线视频播放| 亚洲区综合中文字幕日日| 亚洲一区二区三区色| 超碰成人久久| 日韩黄色影视| 国产乱码精品一区二区三区四区| 久久久久网址| 日韩精品免费一区二区三区竹菊| 国产一区自拍视频| 成人性生交大片免费看中文视频| 91丨九色丨国产| 日本免费精品| a级国产乱理论片在线观看99| 国产视频一区二区在线播放| 成人福利网站在线观看11| 久久福利在线| 91天堂在线视频| 精品一区视频| 成人av免费看| 欧美天堂社区| 神马影院我不卡| 91欧美国产| 国产女教师bbwbbwbbw| 欧美精品麻豆| 女人天堂av手机在线| 午夜在线精品偷拍| 手机在线看福利| 精品一区免费av| 国产男女无遮挡猛进猛出| 福利91精品一区二区三区| 人妻 日韩 欧美 综合 制服| 久久这里只有精品视频网| 精品人妻一区二区三区四区| 欧美激情一区二区三区在线| 娇小11一12╳yⅹ╳毛片| 亚洲综合一区二区三区| 可以在线观看av的网站| 欧洲精品视频在线观看| 国产女人高潮的av毛片| 亚洲成人精品久久久| 嫩草在线播放| 日韩小视频网址| 538视频在线| 国产精品国内视频| 成人激情久久| 久久国产精品 国产精品| 国产精品三级| a级黄色片免费| 久久成人免费| 免费黄频在线观看| 久久综合色综合88| 国内毛片毛片毛片毛片毛片| 午夜av区久久| 在线观看黄色国产| 亚洲国产古装精品网站| 成人在线视频成人| 久久久久久久久久久久久久久久久久av | 嫩草一区二区三区| 男女啪啪的视频| 一区二区国产精品| 天天摸天天舔天天操| 91原创在线视频| 国产盗摄一区二区三区在线| 欧美性猛交视频| 精品久久久免费视频| 亚洲欧美一区二区三区四区| 中文在线免费| 国产精品美女无圣光视频| 粉嫩的18在线观看极品精品| 亚洲精品免费在线看| 亚洲深夜av| 在线观看你懂的视频| 国产精品网友自拍| 看片网址国产福利av中文字幕| 欧美一区永久视频免费观看| 激情视频在线观看免费| 欧美精品国产精品日韩精品| 欧美电影在线观看网站| 蜜桃久久影院| 激情视频一区| 爱情岛论坛亚洲自拍| 国产精品麻豆久久久| 久草视频一区二区| 亚洲国产精品成人av| 在线观看wwwxxxx| 国产欧美日韩免费| 九色成人国产蝌蚪91| 18禁网站免费无遮挡无码中文| 国产一区二区按摩在线观看| 亚洲精品成人av久久| 欧美日韩精品在线视频| 免费观看成年人视频| 欧美精品制服第一页| 久久精品超碰| 午夜老司机精品| 日本成人中文字幕在线视频| 大又大又粗又硬又爽少妇毛片 | 国产特级aaaaaa大片| 中文字幕久热精品视频在线| 东京一区二区| 久久久久久艹| 一本色道精品久久一区二区三区 | 国产欧美一区二区精品性色超碰| 日本网站在线免费观看| 精品久久久久久久久久久久包黑料 | 苍井空浴缸大战猛男120分钟| 成人91在线观看| 国产精品9191| 亚洲第一天堂av| 182在线视频观看| 精品日本一区二区| 噜噜噜躁狠狠躁狠狠精品视频| 9.1成人看片免费版| 色综合网色综合| 丁香在线视频| 国产精品三级久久久久久电影| 成人久久电影| 国产精品久久久毛片| 国产精品国产馆在线真实露脸| 一级黄在线观看| 久久亚洲精品一区| 日韩成人在线观看视频| 阿v天堂2018| www一区二区| 美女黄页在线观看| 久久精品国产69国产精品亚洲| 24小时成人在线视频| 亚洲高潮无码久久| 成人深夜在线观看| 91玉足脚交嫩脚丫在线播放| 亚洲欧美日韩国产中文| 国产成人精品一区二三区在线观看 | 国产z一区二区三区| 日韩国产欧美| 三级黄色片免费观看| 亚洲电影一区二区三区| 国产最新视频在线| 成人妇女淫片aaaa视频| 欧美日韩三级| 波多野结衣 在线| 欧美剧情电影在线观看完整版免费励志电影 | 久久九九久久九九| 亚洲专区第一页| 欧美激情a∨在线视频播放| 欧美调教网站| 九色porny自拍| 亚洲国产精品自拍| 户外极限露出调教在线视频| 91精品国产综合久久久久久久久| 欧美日韩综合| www在线观看免费视频| 欧美一区二区在线免费观看| 天堂av在线| 9l视频自拍9l视频自拍| 91亚洲精华国产精华精华液| 五月天中文字幕| 午夜精品99久久免费| 首页国产精品| 国产美女视频免费观看下载软件| 欧美日韩国产一级二级| 2020日本在线视频中文字幕| 亚洲视频电影| 91丨九色丨蝌蚪富婆spa| 国产原创中文av| 热99精品只有里视频精品| 这里只有精品在线| 91成人在线免费视频|