01
vhost-user
DPDK的提出以及設計思想
隨著各種互聯(lián)網應用的不斷出現(xiàn),網絡設備以及服務器的帶寬也快速提升,從千兆到萬兆再到25G,40G,100G快速演變。
與此同時,在云計算和系統(tǒng)虛擬化技術的快速發(fā)展的推動下,虛擬機的網絡處理能力需求也逐年增強。
另外,不僅是在服務器領域,很多網絡服務也都逐漸向虛擬化,資源池化,云化的方向發(fā)展,比如路由器,交換機,防火墻,基站等常用網絡設備原本都是硬件解決方案,現(xiàn)在也逐漸向虛擬化方向發(fā)展,業(yè)界急需適合高性能網絡的軟件解決方案。
既往的基于Linux實現(xiàn)的網絡服務是基于內核的,也就是說所有數(shù)據(jù)包的發(fā)送和接收都要經過內核協(xié)議棧。在處理大流量和高并發(fā)的情況下,頻繁的硬件中斷會降低內核數(shù)據(jù)包的處理能力,同時內核空間和用戶空間之間的數(shù)據(jù)拷貝也會產生比較大的負載。
為了進一步提升網絡數(shù)據(jù)處理能力,Linux UIO (user-space drivers)技術把硬件操作映射到用戶空間,實現(xiàn)了在用戶空間直接操作網卡硬件。
這樣網絡數(shù)據(jù)包的處理就可以不經過Linux內核了,避免了高速網絡數(shù)據(jù)處理時內核中斷爆炸和大量數(shù)據(jù)復制的問題,進而讓網絡處理能力得以大幅度的提升。
繞過內核直接在用戶態(tài)處理網絡數(shù)據(jù)包,不僅可以解決內核的性能瓶頸,而且還易于開發(fā)和維護,和基于內核模塊的實現(xiàn)相比也更靈活。
另外,因為程序運行在用戶空間,即使有問題也不影響Linux內核和系統(tǒng)的其他模塊,也增強了整個系統(tǒng)的可用性。
針對高性能網絡軟件開發(fā)框架的需求,基于Linux UIO技術在應用層處理網絡數(shù)據(jù)包,這也正是IntelDPDK的主要設計思想。
DPDK應用初識
圖1Linux內核處理路徑(慢路徑)和DPDK處理路徑(快路徑)對比
如圖1所表示的就是基于Linux內核的數(shù)據(jù)處理路徑(慢路徑)和基于DPDK的用戶空間數(shù)據(jù)處理路徑(快路徑)的對比。
這里需要特別說明的是,Intel DPDK被編譯之后其實是一系列的庫文件,供應用程序調用和鏈接。基于DPDK機制開發(fā)的應用程序在編譯和鏈接DPDK的庫文件之后,就可以進行高速的網絡處理了。
這些DPDK應用都是運行在用戶空間的應用程序。通過Linux UIO的機制以及少量內核模塊(例如Intel x86平臺上需要預先加載igb_uio等內核驅動模塊),這些運行在用戶空間的DPDK應用程序能夠旁路Linux內核,直接操作物理網卡進而完成高速網絡數(shù)據(jù)的發(fā)送和接收。
截止到目前為止DPDK已經支持多種硬件體系結構,包括Intel x86,ARM,PowerPC等等,并且被網絡設備廠商和互聯(lián)網廠商廣泛接受,已經有很多基于DPDK開發(fā)的產品和服務投入到生產環(huán)境使用了。
IntelDPDK的核心技術之一是PMD (用戶態(tài)的輪詢模式驅動)。通過非中斷以及數(shù)據(jù)進出應用緩沖區(qū)內存的零拷貝機制,進一步提高發(fā)送接受數(shù)據(jù)的效率。用戶態(tài)模式的PMD驅動,去除中斷,避免內核態(tài)和用戶態(tài)內存拷貝,減少系統(tǒng)開銷,從而提升I/O吞吐能力。
我們可以通過分析DPDK中的L2fw程序,大致了解一下DPDK應用程序的基本結構。
具體的代碼可以參考dpdk-stable-18.11.11/examples/l2fwd/main.c。l2fwd是一個簡單的DPDK應用程序,可以看出在main函數(shù)中,調用rte_eal_init函數(shù)對DPDK運行環(huán)境進行初始化之后,調用l2fwd_launch_one_lcore函數(shù)在每個CPU的核上都運行l(wèi)2fwd_main_loop函數(shù)。
l2fwd_main_loop函數(shù)就是在無限循環(huán)中不斷的輪詢該CPU核綁定的網卡的所有端口,調用rte_eth_tx_buffer_flush和rte_eth_rx_burst進行數(shù)據(jù)包的發(fā)送和接收,進而再完成轉發(fā)。
被DPDK應用綁定的CPU核不再被Linux內核調度,而是被l2fwd_main_loop函數(shù)獨占。所以和原來基于Linux內核的實現(xiàn)相比,網絡數(shù)據(jù)包的處理能力得以大幅度提升。 DPDK中的PMD技術的具體實現(xiàn),可以參考dpdk-stable-18.11.11/drivers/net/e1000/igb_ethdev.c中的函數(shù)eth_igb_dev_init,PMD相關的實現(xiàn)是DPDK的內部實現(xiàn),限于篇幅,我們這里就不展開講解了。
int main(intargc,char**argv) { …… /*initEAL*/ ret=rte_eal_init(argc,argv); if(ret0) …… /*launchper-lcoreinitoneverylcore*/ rte_eal_mp_remote_launch(l2fwd_launch_one_lcore,NULL,CALL_MASTER); …… } |
staticint l2fwd_launch_one_lcore(__attribute__((unused))void*dummy) { l2fwd_main_loop(); return0; } |
/*mainprocessingloop*/ staticvoid l2fwd_main_loop(void) { structrte_mbuf*pkts_burst[MAX_PKT_BURST]; structrte_mbuf*m; intsent; unsignedlcore_id; uint64_tprev_tsc,diff_tsc,cur_tsc,timer_tsc; unsignedi,j,portid,nb_rx; structlcore_queue_conf*qconf; constuint64_tdrain_tsc=(rte_get_tsc_hz()+US_PER_S-1)/US_PER_S* BURST_TX_DRAIN_US; structrte_eth_dev_tx_buffer*buffer; prev_tsc=0; timer_tsc=0; lcore_id=rte_lcore_id(); qconf=&lcore_queue_conf[lcore_id]; if(qconf->n_rx_port==0){ RTE_LOG(INFO,L2FWD,"lcore%uhasnothingtodo ",lcore_id); return; } RTE_LOG(INFO,L2FWD,"enteringmainlooponlcore%u ",lcore_id); for(i=0;in_rx_port;i++){ portid=qconf->rx_port_list[i]; RTE_LOG(INFO,L2FWD,"--lcoreid=%uportid=%u ",lcore_id, portid); } while(!force_quit){ cur_tsc=rte_rdtsc(); /* *TXburstqueuedrain */ diff_tsc=cur_tsc-prev_tsc; if(unlikely(diff_tsc>drain_tsc)){ for(i=0;in_rx_port;i++){ portid=l2fwd_dst_ports[qconf->rx_port_list[i]]; buffer=tx_buffer[portid]; sent=rte_eth_tx_buffer_flush(portid,0,buffer); if(sent) port_statistics[portid].tx+=sent; } /*iftimerisenabled*/ if(timer_period>0){ /*advancethetimer*/ timer_tsc+=diff_tsc; /*iftimerhasreacheditstimeout*/ if(unlikely(timer_tsc>=timer_period)){ /*dothisonlyonmastercore*/ if(lcore_id==rte_get_master_lcore()){ print_stats(); /*resetthetimer*/ timer_tsc=0; } } } prev_tsc=cur_tsc; } /* *ReadpacketfromRXqueues */ for(i=0;in_rx_port;i++){ portid=qconf->rx_port_list[i]; nb_rx=rte_eth_rx_burst(portid,0, pkts_burst,MAX_PKT_BURST); port_statistics[portid].rx+=nb_rx; for(j=0;j m=pkts_burst[j]; rte_prefetch0(rte_pktmbuf_mtod(m,void*)); l2fwd_simple_forward(m,portid); } } } } |
vhost-user與DPDK
在對DPDK應用程序有了一個基本認識,并理解了基本原理和核心技術的基礎上,下面我們進入到DPDK場景下用戶態(tài)的virtio-net的實現(xiàn),也就是vhost-user的相關介紹。
DPDK場景下,virtio-net后端和virtio-net前端的關系圖,其中涉及到OVS-DPDK,QEMU,Linux內核和物理網卡硬件。我們接下來會先講一下各個模塊之間的關系,以及它們和virtio-net前端和后端的關系。
-OVS-DPDK:實現(xiàn)了三部分功能。1). 用戶空間的PMD,不斷地和物理網卡通信,發(fā)送接收數(shù)據(jù)包;2). 虛擬交換機的功能,這部分和我們平時熟悉的OVS是一樣的;3). DPDK場景下的virtio-net后端的實現(xiàn),和virtio-net前端通信。
-QEMU:是一個運行在用戶空間的多線程程序,有運行虛擬機的vCPU線程,有處理中斷的KVM線程和eventfd。不同之處在于:在DPDK場景下,virtio-net前端是運行在虛擬機線程內的DPDK應用程序。這時,虛擬機的網絡接口的類型是vhost-user,它會基于UNIX domain socket和virtio-net后端(實現(xiàn)在OVS-DPDK中)通信。
可以看出,在DPDK場景下,virtio-net前端和后端的實現(xiàn)都在DPDK代碼中。
在運行態(tài)的時候,就像圖2所顯示的那樣,virtio-net前端是QEMU虛擬機內運行的DPDK應用程序,virtio-net后端運行在OVS-DPDK中。
兩者都運行在用戶態(tài),通過UNIX domain socket進行通信。vhost-user是將virtio-net驅動在用戶態(tài)的實現(xiàn)。
下面我們結合結合DPDK 18.11的代碼,針對virtio設備的初始化,發(fā)送數(shù)據(jù)的處理流程,做進一步的分析和講解。
設備發(fā)現(xiàn)和初始化
在QEMU虛擬機中運行DPDK應用時:virtio-net前端的初始化的具體實現(xiàn)是在dpdk-stable-18.11.11/drivers/net/virtio/virtio_ethdev.c文件中,其中rte_virtio_pmd是驅動的主要數(shù)據(jù)結構。
QEMU中的虛擬機運行DPDK應用程序的時候,在指定vhost-user端口的時候,會注冊rte_virtio_pmd驅動程序。驅動中的probe函數(shù)注冊為eth_virtio_pci_probe函數(shù)。DPDK驅動程序這樣的寫法和Linux內核中的PCI網絡驅動是一樣的,比較容易理解。
函數(shù)eth_virtio_dev_init會調用virtio_init_device函數(shù),其中可以看到我們之前介紹過的復合virtio規(guī)范的協(xié)商特性位(feature bits),配置MTU等網卡相關的配置,調用virtio_alloc_queues配置虛擬隊列等操作。
staticstructrte_pci_driverrte_virtio_pmd={ .driver={ .name="net_virtio", }, .id_table=pci_id_virtio_map, .drv_flags=0, .probe=eth_virtio_pci_probe, .remove=eth_virtio_pci_remove, }; RTE_INIT(rte_virtio_pmd_init) { rte_eal_iopl_init(); rte_pci_register(&rte_virtio_pmd); } |
staticinteth_virtio_pci_probe(structrte_pci_driver*pci_drv__rte_unused, structrte_pci_device*pci_dev) { if(rte_eal_iopl_init()!=0){ PMD_INIT_LOG(ERR,"IOPLcallfailed-cannotusevirtioPMD"); return1; } /*virtiopmdskipsprobeifdeviceneedstoworkinvdpamode*/ if(vdpa_mode_selected(pci_dev->device.devargs)) return1; returnrte_eth_dev_pci_generic_probe(pci_dev,sizeof(structvirtio_hw),eth_virtio_dev_init); } |
/*resetdeviceandrenegotiatefeaturesifneeded*/ staticint virtio_init_device(structrte_eth_dev*eth_dev,uint64_treq_features) { structvirtio_hw*hw=eth_dev->data->dev_private; structvirtio_net_config*config; structvirtio_net_configlocal_config; structrte_pci_device*pci_dev=NULL; intret; /*Resetthedevicealthoughnotnecessaryatstartup*/ vtpci_reset(hw); if(hw->vqs){ virtio_dev_free_mbufs(eth_dev); virtio_free_queues(hw); } /*Tellthehostwe'venoticedthisdevice.*/ vtpci_set_status(hw,VIRTIO_CONFIG_STATUS_ACK); /*Tellthehostwe'veknownhowtodrivethedevice.*/ vtpci_set_status(hw,VIRTIO_CONFIG_STATUS_DRIVER); if(virtio_negotiate_features(hw,req_features)0) return-1; if(!hw->virtio_user_dev){ pci_dev=RTE_ETH_DEV_TO_PCI(eth_dev); rte_eth_copy_pci_info(eth_dev,pci_dev); } /*IfhostdoesnotsupportbothstatusandMSI-XthendisableLSC*/ if(vtpci_with_feature(hw,VIRTIO_NET_F_STATUS)&& hw->use_msix!=VIRTIO_MSIX_NONE) eth_dev->data->dev_flags|=RTE_ETH_DEV_INTR_LSC; else eth_dev->data->dev_flags&=~RTE_ETH_DEV_INTR_LSC; /*Settinguprx_headersizeforthedevice*/ if(vtpci_with_feature(hw,VIRTIO_NET_F_MRG_RXBUF)|| vtpci_with_feature(hw,VIRTIO_F_VERSION_1)) hw->vtnet_hdr_size=sizeof(structvirtio_net_hdr_mrg_rxbuf); else hw->vtnet_hdr_size=sizeof(structvirtio_net_hdr); /*CopythepermanentMACaddressto:virtio_hw*/ virtio_get_hwaddr(hw); ether_addr_copy((structether_addr*)hw->mac_addr, ð_dev->data->mac_addrs[0]); PMD_INIT_LOG(DEBUG, "PORTMAC:%02X:%02X:%02X:%02X:%02X:%02X", hw->mac_addr[0],hw->mac_addr[1],hw->mac_addr[2], hw->mac_addr[3],hw->mac_addr[4],hw->mac_addr[5]); if(vtpci_with_feature(hw,VIRTIO_NET_F_CTRL_VQ)){ config=&local_config; vtpci_read_dev_config(hw, offsetof(structvirtio_net_config,mac), &config->mac,sizeof(config->mac)); if(vtpci_with_feature(hw,VIRTIO_NET_F_STATUS)){ vtpci_read_dev_config(hw, offsetof(structvirtio_net_config,status), &config->status,sizeof(config->status)); }else{ PMD_INIT_LOG(DEBUG, "VIRTIO_NET_F_STATUSisnotsupported"); config->status=0; } if(vtpci_with_feature(hw,VIRTIO_NET_F_MQ)){ vtpci_read_dev_config(hw, offsetof(structvirtio_net_config,max_virtqueue_pairs), &config->max_virtqueue_pairs, sizeof(config->max_virtqueue_pairs)); }else{ PMD_INIT_LOG(DEBUG, "VIRTIO_NET_F_MQisnotsupported"); config->max_virtqueue_pairs=1; } hw->max_queue_pairs=config->max_virtqueue_pairs; if(vtpci_with_feature(hw,VIRTIO_NET_F_MTU)){ vtpci_read_dev_config(hw, offsetof(structvirtio_net_config,mtu), &config->mtu, sizeof(config->mtu)); /* *MTUvaluehasalreadybeencheckedatnegotiation *time,butcheckagainincaseithaschangedsince *then,whichshouldnothappen. */ if(config->mtu PMD_INIT_LOG(ERR,"invalidmaxMTUvalue(%u)", config->mtu); return-1; } hw->max_mtu=config->mtu; /*SetinitialMTUtomaximumonesupportedbyvhost*/ eth_dev->data->mtu=config->mtu; }else{ hw->max_mtu=VIRTIO_MAX_RX_PKTLEN-ETHER_HDR_LEN- VLAN_TAG_LEN-hw->vtnet_hdr_size; } PMD_INIT_LOG(DEBUG,"config->max_virtqueue_pairs=%d", config->max_virtqueue_pairs); PMD_INIT_LOG(DEBUG,"config->status=%d",config->status); PMD_INIT_LOG(DEBUG, "PORTMAC:%02X:%02X:%02X:%02X:%02X:%02X", config->mac[0],config->mac[1], config->mac[2],config->mac[3], config->mac[4],config->mac[5]); }else{ PMD_INIT_LOG(DEBUG,"config->max_virtqueue_pairs=1"); hw->max_queue_pairs=1; hw->max_mtu=VIRTIO_MAX_RX_PKTLEN-ETHER_HDR_LEN- VLAN_TAG_LEN-hw->vtnet_hdr_size; } ret=virtio_alloc_queues(eth_dev); if(ret0) returnret; if(eth_dev->data->dev_conf.intr_conf.rxq){ if(virtio_configure_intr(eth_dev)0)?{ PMD_INIT_LOG(ERR,"failedtoconfigureinterrupt"); virtio_free_queues(hw); return-1; } } vtpci_reinit_complete(hw); if(pci_dev) PMD_INIT_LOG(DEBUG,"port%dvendorID=0x%xdeviceID=0x%x", eth_dev->data->port_id,pci_dev->id.vendor_id, pci_dev->id.device_id); return0; } |
在OVS-DPDK中添加一個vhost-user網絡接口時:OVS-DPDK會調用rte_vhost_driver_register函數(shù),首先根據(jù)傳入參數(shù)path文件路徑創(chuàng)建UNIX domain socket,用于后續(xù)virtio-net前端(QEMU虛擬機中的DPDK應用程序)和virtio-net后端(OVS-DPDK應用程序)的通信。
這部分相關的源碼在dpdk-stable-18.11.11/lib/librte_vhost/socket.c的第824行,主要的函數(shù)是rte_vhost_driver_register。
/* *Registeranewvhost-usersocket;herewecouldactasserver *(thedefaultcase),orclient(whenRTE_VHOST_USER_CLIENT)flag *isset. */ int rte_vhost_driver_register(constchar*path,uint64_tflags) { intret=-1; structvhost_user_socket*vsocket; if(!path) return-1; pthread_mutex_lock(&vhost_user.mutex); if(vhost_user.vsocket_cnt==MAX_VHOST_SOCKET){ RTE_LOG(ERR,VHOST_CONFIG, "error:thenumberofvhostsocketsreachesmaximum "); gotoout; } vsocket=malloc(sizeof(structvhost_user_socket)); if(!vsocket) gotoout; memset(vsocket,0,sizeof(structvhost_user_socket)); vsocket->path=strdup(path); if(vsocket->path==NULL){ RTE_LOG(ERR,VHOST_CONFIG, "error:failedtocopysocketpathstring "); vhost_user_socket_mem_free(vsocket); gotoout; } TAILQ_INIT(&vsocket->conn_list); ret=pthread_mutex_init(&vsocket->conn_mutex,NULL); if(ret){ RTE_LOG(ERR,VHOST_CONFIG, "error:failedtoinitconnectionmutex "); gotoout_free; } vsocket->vdpa_dev_id=-1; vsocket->dequeue_zero_copy=flags&RTE_VHOST_USER_DEQUEUE_ZERO_COPY; if(vsocket->dequeue_zero_copy&& (flags&RTE_VHOST_USER_IOMMU_SUPPORT)){ RTE_LOG(ERR,VHOST_CONFIG, "error:enablingdequeuezerocopyandIOMMUfeatures" "simultaneouslyisnotsupported "); gotoout_mutex; } /* *Setthesupportedfeaturescorrectlyforthebuiltinvhost-user *netdriver. * *Applicationsknownothingaboutfeaturesthebuiltinvirtionet *driver(virtio_net.c)supports,thusit'snotpossibleforthem *toinvokerte_vhost_driver_set_features().Toworkaroundit,here *wesetitunconditionally.Iftheapplicationwanttoimplement *anothervhost-userdriver(saySCSI),itshouldcallthe *rte_vhost_driver_set_features(),whichwilloverwritefollowing *twovalues. */ vsocket->use_builtin_virtio_net=true; vsocket->supported_features=VIRTIO_NET_SUPPORTED_FEATURES; vsocket->features=VIRTIO_NET_SUPPORTED_FEATURES; vsocket->protocol_features=VHOST_USER_PROTOCOL_FEATURES; /* *Dequeuezerocopycan'tassuredescriptorsreturnedinorder. *Also,itrequiresthattheguestmemoryispopulated,whichis *notcompatiblewithpostcopy. */ if(vsocket->dequeue_zero_copy){ if((flags&RTE_VHOST_USER_CLIENT)!=0) RTE_LOG(WARNING,VHOST_CONFIG, "zerocopymaybeincompatiblewithvhostclientmode "); vsocket->supported_features&=~(1ULL< vsocket->features&=~(1ULL< RTE_LOG(INFO,VHOST_CONFIG, "Dequeuezerocopyrequested,disablingpostcopysupport "); vsocket->protocol_features&= ~(1ULL< } if(!(flags&RTE_VHOST_USER_IOMMU_SUPPORT)){ vsocket->supported_features&=~(1ULL< vsocket->features&=~(1ULL< } if(!(flags&RTE_VHOST_USER_POSTCOPY_SUPPORT)){ vsocket->protocol_features&= ~(1ULL< }else{ } if((flags&RTE_VHOST_USER_CLIENT)!=0){ vsocket->reconnect=!(flags&RTE_VHOST_USER_NO_RECONNECT); if(vsocket->reconnect&&reconn_tid==0){ if(vhost_user_reconnect_init()!=0) gotoout_mutex; } }else{ vsocket->is_server=true; } ret=create_unix_socket(vsocket); if(ret0)?{ gotoout_mutex; } vhost_user.vsockets[vhost_user.vsocket_cnt++]=vsocket; pthread_mutex_unlock(&vhost_user.mutex); returnret; } |
發(fā)送數(shù)據(jù)處理過程
vhost-user前端:在vhost-user接口創(chuàng)建之后,會調用rte_vhost_driver_start函數(shù):首先根據(jù)UNIX domainsocket的文件路徑path找到對應的socket,然后會調用函數(shù)創(chuàng)建監(jiān)聽線程,
這個線程的處理函數(shù)是fdset_event_dispatch會去監(jiān)聽vhost_user.fdset指定的socket fd的讀寫操作,進而實現(xiàn)和vhost-user后端的通信。這部分的實現(xiàn)代碼在:dpdk-stable-18.11.11/lib/librte_vhost/socket.c的第1094行。
intrte_vhost_driver_start(constchar*path) { structvhost_user_socket*vsocket; staticpthread_tfdset_tid; pthread_mutex_lock(&vhost_user.mutex); vsocket=find_vhost_user_socket(path); pthread_mutex_unlock(&vhost_user.mutex); if(!vsocket) return-1; if(fdset_tid==0){ /** *createapipewhichwillbewaitedbypollandnotifiedto *rebuildthewaitlistofpoll. */ if(fdset_pipe_init(&vhost_user.fdset)0)?{ RTE_LOG(ERR,VHOST_CONFIG, "failedtocreatepipeforvhostfdset "); return-1; } intret=rte_ctrl_thread_create(&fdset_tid, "vhost-events",NULL,fdset_event_dispatch, &vhost_user.fdset); if(ret!=0){ RTE_LOG(ERR,VHOST_CONFIG, "failedtocreatefdsethandlingthread"); fdset_pipe_uninit(&vhost_user.fdset); return-1; } } if(vsocket->is_server) returnvhost_user_start_server(vsocket); else returnvhost_user_start_client(vsocket); } |
vhost-user后端:當vhost-user前端(QEMU虛擬機的DPDK應用程序)向UNIX domain socket寫入事件的時候,之前所說的那個監(jiān)聽線程會捕獲到這個事件,調用vhost_user_read_cb函數(shù)進行處理,最終會調用vhost_user_msg_handler函數(shù),根據(jù)不同的消息類型做不同的處理。
這部分的實現(xiàn)代碼在:dpdk-stable-18.11.11/lib/librte_vhost/socket.c的第293行。
staticvoid vhost_user_read_cb(intconnfd,void*dat,int*remove) { structvhost_user_connection*conn=dat; structvhost_user_socket*vsocket=conn->vsocket; intret; ret=vhost_user_msg_handler(conn->vid,connfd); if(ret0)?{ structvirtio_net*dev=get_device(conn->vid); close(connfd); *remove=1; if(dev) vhost_destroy_device_notify(dev); if(vsocket->notify_ops->destroy_connection) vsocket->notify_ops->destroy_connection(conn->vid); vhost_destroy_device(conn->vid); if(vsocket->reconnect){ create_unix_socket(vsocket); vhost_user_start_client(vsocket); } pthread_mutex_lock(&vsocket->conn_mutex); TAILQ_REMOVE(&vsocket->conn_list,conn,next); pthread_mutex_unlock(&vsocket->conn_mutex); free(conn); } } |
收發(fā)數(shù)據(jù)包的函數(shù)分別是eth_vhost_rx和eth_vhost_tx。代碼的具體實現(xiàn)在dpdk-stable-18.11.11/drivers/net/vhost/rte_eth_vhost.c中。
函數(shù)eth_dev_vhost_create用來創(chuàng)建vhost-user的后端設備的時候,會注冊發(fā)送和接收數(shù)據(jù)的回調函數(shù)為eth_vhost_rx和eth_vhost_tx。
在DPDK中,發(fā)送和接收數(shù)據(jù)包的代碼都是在DPDK中實現(xiàn)的,代碼的可讀性比較好。只是特別要注意的是同樣的DPDK代碼,運行的位置不一樣:前端是在QEMU虛擬機中運行的DPDK應用程序,后端運行在OVS-DPDK中。
staticint eth_dev_vhost_create(structrte_vdev_device*dev,char*iface_name, int16_tqueues,constunsignedintnuma_node,uint64_tflags) { constchar*name=rte_vdev_device_name(dev); structrte_eth_dev_data*data; structpmd_internal*internal=NULL; structrte_eth_dev*eth_dev=NULL; structether_addr*eth_addr=NULL; structrte_vhost_vring_state*vring_state=NULL; structinternal_list*list=NULL; VHOST_LOG(INFO,"CreatingVHOST-USERbackendonnumasocket%u ", numa_node); list=rte_zmalloc_socket(name,sizeof(*list),0,numa_node); if(list==NULL) gotoerror; /*reserveanethdeventry*/ eth_dev=rte_eth_vdev_allocate(dev,sizeof(*internal)); if(eth_dev==NULL) gotoerror; data=eth_dev->data; eth_addr=rte_zmalloc_socket(name,sizeof(*eth_addr),0,numa_node); if(eth_addr==NULL) gotoerror; data->mac_addrs=eth_addr; *eth_addr=base_eth_addr; eth_addr->addr_bytes[5]=eth_dev->data->port_id; vring_state=rte_zmalloc_socket(name, sizeof(*vring_state),0,numa_node); if(vring_state==NULL) gotoerror; /*nowputitalltogether *-storequeuedataininternal, *-pointeth_dev_datatointernals *-andpointeth_devstructuretoneweth_dev_datastructure */ internal=eth_dev->data->dev_private; internal->dev_name=strdup(name); if(internal->dev_name==NULL) gotoerror; internal->iface_name=rte_malloc_socket(name,strlen(iface_name)+1, 0,numa_node); if(internal->iface_name==NULL) gotoerror; strcpy(internal->iface_name,iface_name); list->eth_dev=eth_dev; pthread_mutex_lock(&internal_list_lock); TAILQ_INSERT_TAIL(&internal_list,list,next); pthread_mutex_unlock(&internal_list_lock); rte_spinlock_init(&vring_state->lock); vring_states[eth_dev->data->port_id]=vring_state; data->nb_rx_queues=queues; data->nb_tx_queues=queues; internal->max_queues=queues; internal->vid=-1; data->dev_link=pmd_link; data->dev_flags=RTE_ETH_DEV_INTR_LSC; eth_dev->dev_ops=&ops; /*finallyassignrxandtxops*/ eth_dev->rx_pkt_burst=eth_vhost_rx; eth_dev->tx_pkt_burst=eth_vhost_tx; if(rte_vhost_driver_register(iface_name,flags)) gotoerror; if(rte_vhost_driver_callback_register(iface_name,&vhost_ops)0)?{ VHOST_LOG(ERR,"Can'tregistercallbacks "); gotoerror; } if(rte_vhost_driver_start(iface_name)0)?{ VHOST_LOG(ERR,"Failedtostartdriverfor%s ", iface_name); gotoerror; } rte_eth_dev_probing_finish(eth_dev); return0; error: if(internal){ rte_free(internal->iface_name); free(internal->dev_name); } rte_free(vring_state); rte_eth_dev_release_port(eth_dev); rte_free(list); return-1; } |
02
vDPA
DPDK場景下的vhost-user是virtio-net在用戶態(tài)的實現(xiàn)。和內核的實現(xiàn)相比,提高了網絡數(shù)據(jù)的處理能力。但畢竟還是純軟件層面的實現(xiàn),在性能上肯定比不過硬件層面的實現(xiàn)。
為了進一步提升性能,Intel還推出了vDPA (vHost Data Path Acceleration) 的硬件解決方案,直接讓網卡與虛擬機內的virtio虛擬隊列交互,把數(shù)據(jù)包DMA到虛擬機的緩存內,在支持了virtio標準的基礎上實現(xiàn)了真正意義上的零拷貝。
在DPDK 18.05之后的版本中,已經開始支持vDPA的特性了。vDPA這部分的實現(xiàn)主要是在網卡中,相當于把virtio后端實現(xiàn)在網卡中了。
所以,我們這里只關注一下virtio前端和vDPA設備之間的關聯(lián)。這部分實現(xiàn)的相關代碼在dpdk-stable-18.11.11/examples/vdpa/main.c,第143行start_vdpa函數(shù)中的rte_vhost_driver_attach_vdpa_device函數(shù)中實現(xiàn)了vhost-user的vsocket數(shù)據(jù)結構和vDPA設備ID的關聯(lián)。
staticint start_vdpa(structvdpa_port*vport) { intret; char*socket_path=vport->ifname; intdid=vport->did; if(client_mode) vport->flags|=RTE_VHOST_USER_CLIENT; if(access(socket_path,F_OK)!=-1&&!client_mode){ RTE_LOG(ERR,VDPA, "%sexists,pleaseremoveitorspecifyanotherfileandtryagain. ", socket_path); return-1; } ret=rte_vhost_driver_register(socket_path,vport->flags); if(ret!=0) rte_exit(EXIT_FAILURE, "registerdriverfailed:%s ", socket_path); ret=rte_vhost_driver_callback_register(socket_path, &vdpa_sample_devops); if(ret!=0) rte_exit(EXIT_FAILURE, "registerdriveropsfailed:%s ", socket_path); ret=rte_vhost_driver_attach_vdpa_device(socket_path,did); if(ret!=0) rte_exit(EXIT_FAILURE, "attachvdpadevicefailed:%s ", socket_path); if(rte_vhost_driver_start(socket_path)0) rte_exit(EXIT_FAILURE, "startvhostdriverfailed:%s ", socket_path); return0; } |
int rte_vhost_driver_attach_vdpa_device(constchar*path,intdid) { structvhost_user_socket*vsocket; if(rte_vdpa_get_device(did)==NULL||path==NULL) return-1; pthread_mutex_lock(&vhost_user.mutex); vsocket=find_vhost_user_socket(path); if(vsocket) vsocket->vdpa_dev_id=did; pthread_mutex_unlock(&vhost_user.mutex); returnvsocket?0:-1; } |
原文標題:virtio技術的演進和發(fā)展 (2/2)
文章出處:【微信公眾號:Linux閱碼場】歡迎添加關注!文章轉載請注明出處。
責任編輯:haq
-
Linux
+關注
關注
87文章
11329瀏覽量
209971 -
服務器
+關注
關注
12文章
9256瀏覽量
85765 -
虛擬機
+關注
關注
1文章
919瀏覽量
28325
原文標題:virtio技術的演進和發(fā)展 (2/2)
文章出處:【微信號:LinuxDev,微信公眾號:Linux閱碼場】歡迎添加關注!文章轉載請注明出處。
發(fā)布評論請先 登錄
相關推薦
評論