深入分析vhost-user网卡实现原理 —— VirtIO Features协商

news/2024/5/21 9:22:57/文章来源:https://blog.csdn.net/huang987246510/article/details/126925516

文章目录

  • 前言
  • 数据结构
    • 设备模型
      • device
        • VirtIONetPCI
        • VirtIONet
        • NICState
        • NetClientState
      • netdev
        • NetVhostUserState
        • vhost_net
        • vhost_dev
      • chardev
        • chardev
        • ChardevClass
        • SocketChardev
    • Features
      • VirtIONet
      • VirtIODevice
      • vhost_dev
      • NetVhostUserState
      • feature_bits
    • Backend
  • 流程详解
    • 启动过程
    • 网络连接
    • 网卡启动
    • Guest探测
    • 网卡停用

前言

  • 本文简要介绍vhost-user网卡设备模型,重点解释vhost-user网卡涉及的到的数据结构以及features在各个数据结构中的含义与作用。

数据结构

设备模型

  • vhost网卡的设备模型分为三层,device层作为离guest最近的一层,在guest表现为一个pci设备,netdev次之,作为device的后端为其提供网卡的通用功能,chardev离guest最远,作为netdev的后端为网卡流量转发提供一种具体通信方式。以下qemu命令行表示一张网卡:
-chardev socket,id=charnet1,path=/run/openvswitch/vhu1,server 
-netdev vhost-user,chardev=charnet1,queues=2,id=hostnet1 
-device virtio-net-pci,mrg_rxbuf=on,mq=on,vectors=6,netdev=hostnet1,id=net1,mac=52:54:00:3f:8f:56,bus=pci.0,addr=0x4
  • 三者的依赖关系如下:
    在这里插入图片描述

device

  • device设备通过如下qemu命令行参数指定,它的默认属性driver为virtio-net-pci,指定id为hostnet1的后端netdev设备:
-device virtio-net-pci,mrg_rxbuf=on,mq=on,vectors=6,netdev=hostnet1,id=net1,mac=52:54:00:3f:8f:56,bus=pci.0,addr=0x4
  • virtio net,virtio device和device继承关系如下:
    在这里插入图片描述

VirtIONetPCI

  • 网卡设备的driver是virtio-net-pci,driver的名中既有virtio-net,又有pci,可以看出这个数据结构是个复合的数据结构,的确,这个数据结构既描述了网卡在虚机内部的pci信息,又描述了网卡在qemu端的信息。
struct VirtIONetPCI {VirtIOPCIProxy parent_obj;	/* 网卡pci设备相关信息*/VirtIONet vdev;				/* 主机侧网卡设备状态 */
};

VirtIONet

  • VirtIONet数据结构在virtio-net设备实例化,virtio_net_device_realize函数中被初始化,保存网卡VirtIO数据队列,控制队列,网卡状态,网卡后端类型,网卡MAC地址,后端是否支持TSO、UFO包卸载能力,用户配置的网卡属性等。
struct VirtIONet {VirtIODevice parent_obj;uint8_t mac[ETH_ALEN];uint16_t status;VirtIONetQueue *vqs;	/* 网卡数据队列 */VirtQueue *ctrl_vq;		/* 控制队列 */NICState *nic;			/* 网卡状态 */......uint32_t has_vnet_hdr;	/* 后端为tap设备的网卡是否有vnet头,用于探测网卡支持的卸载能力 */size_t host_hdr_len;size_t guest_hdr_len;uint64_t host_features;	/* 用户配置的网卡属性,mrg_rxbuf,mq,tso,ufo */......uint8_t has_ufo;		/* 网卡后端是否支持UDP包卸载 */uint32_t mergeable_rx_bufs;	/* 是否开启接收端缓存合并 */......uint8_t vhost_started;	/* 后端为vhost的网卡是否为启动状态 */......
};

NICState

  • NICState核心数据结构是一组地址,保存NetClientState客户端地址,基于NetClientState我们可以查找到网卡设备device层的后端netdev层。
typedef struct NICState {NetClientState *ncs;NICConf *conf;void *opaque;bool peer_deleted;
} NICState;

NetClientState

  • 前面我们提到Qemu将网卡设备分为三层,NetClientState就是连接device层和netdev层的数据结构,从数据结构的名字可以想象,对于网卡后端netdev设备,device设备是作为其客户端存在,device和netdev,也可以看成是peer-to-peer的关系:
struct NetClientState {NetClientInfo *info;		/* 1 */int link_down;				/* 2 */QTAILQ_ENTRY(NetClientState) next;	NetClientState *peer;		/* 3 */......
}typedef struct NetClientInfo {NetClientDriver type;		/* 4 */......
}
  1. peer端网卡状态和具体能力信息
  2. peer端网卡是否连接正常
  3. Qemu将所有device层的网卡信息通过next字段链接到全局变量net_clients中,方便查找网卡设备
  4. 对于device持有的NetClientState,它的类型字段type必然是NET_CLIENT_DRIVER_NIC, 对于device对应的具体后端,比如本篇中介绍的vhost-user,或者是tap设备,亦或是vdpa设备,peer侧持有的NetClientState,其类型字段type各不相同。

netdev

  • netdev设备作为device的后端,默认属性driver是vhost-user,指定id为charnet1作为它的chardev,作为具体的网卡流量转发方式,命令行参数如下:
-netdev vhost-user,chardev=charnet1,queues=2,id=hostnet1

NetVhostUserState

  • NetVhostUserState数据结构有承上启下的作用,它不但将作为客户端的device和实现具体通信的chardev设备,还保存了指向vhost_net设备的地址:
typedef struct NetVhostUserState {NetClientState nc;				/* 1 */	CharBackend chr; 				/* 2 */	VHostNetState *vhost_net;		/* 3 */......uint64_t acked_features;		/* 4 */bool started;					/* 5 */
} NetVhostUserState;
  1. nc字段指向作为client的device侧
  2. vhost-user设备不会直接用socket来实现网卡通信,而是通过CharBackend来间接使用,换句话说,socket不是vhost-user通信唯一方式,vhost-user不关心具体的通信实现,而是交给CharBackend,因此这里chr指向的是一个CharBackend。
  3. vhost-user设备需要实现virtio数据面卸载slave的功能,Qemu侧需要维护相关数据结构,这个结构就是vhost_dev,vhost_net字段间接保存了其地址
  4. 当vhost_net设备判断到slave侧无法工作时,会停止vhost_net设备,释放其内存并删除其数据,在设备停止之前需要保存Guest,Qemu,Slave三者在虚机启动时协商好的virtio-net feature。为什么需要保存?因为virtio-net feature是在Guest virtio-net驱动加载时完成协商,虚机正常运行中通常不会再次协商,除非重新加载virtio-net,因此Qemu需要保留一份在slave恢复工作后重新设置该feature给slave。
  5. started字段表明vhost_net是否启动,Qemu在和前端virtio-net驱动交互过程中,需要基于这个信息判断是否需要进行某些操作,比如当Guest在设置virtio-net的状态时,Qemu在处理过程中会判断vhost_net设备是否启动,如果没有,会首先启动vhost_net设备,因此vhost_net设备正常工作是Guest能够使用virtio-net设备的前提。

vhost_net

  • 其核心字段是dev,指向具体的vhost_dev数据结构:
typedef struct vhost_net VHostNetState;
struct vhost_net {struct vhost_dev dev;......
};

vhost_dev

  • 所有vhost设备实现VirtIO数据面卸载都通过数据结构vhost_dev来实现,它包含了vhost protocol实现相关的所有信息,这里我们解释几个features字段:
struct vhost_dev {VirtIODevice *vdev;......uint64_t features;				/* 1 */uint64_t acked_features;		/* 2 */uint64_t backend_features;		/* 3 */uint64_t protocol_features;		/* 4 */......
};
  1. features, 从slave侧获取到的slave支持的VirtIO Features。
  2. acked_features,经过Guest,Qemu,Slave三者在虚机启动时协商后的VirtIO Features。
  3. backend_features,Qemu维护的Slave定义并支持的Feature集合,这是Slave Specific的,非VirtIO Features。
  4. protocol_features,Vhost 协议支持的feature,由Vhost 协议定义,非VirtIO Features.

chardev

-chardev socket,id=charnet1,path=/run/openvswitch/vhu1,server

chardev

  • TODO
struct Chardev {Object parent_obj;	/* 字符设备基类,ChardevClass与Chardev通过指向相同父类的ObjectClass建立联系 */......CharBackend *be;......int be_open;		/* 标记字符设备是否被打开 */......GSource *gsource;GMainContext *gcontext;
};

ChardevClass

  • 所有的字符设备,都可以抽象出一组相同的操作,通过ChardevClass来描述,具体的字符设备实现如socket字符设备,可以实现这组操作的子集,当Qemu操作基类字符设备时就可以调用到,对于socket字符设备来说,在char_socket_class_init中注册了socket字符设备的操作:
typedef struct ChardevClass {ObjectClass parent_class;......void (*open)(Chardev *chr, ChardevBackend *backend,			/* qmp_chardev_open_socket */bool *be_opened, Error **errp);int (*chr_write)(Chardev *s, const uint8_t *buf, int len);	/* tcp_chr_write */......int (*chr_wait_connected)(Chardev *chr, Error **errp);		/* tcp_chr_wait_connected */void (*chr_disconnect)(Chardev *chr);						/* tcp_chr_disconnect */......
} ChardevClass;

SocketChardev

  • chardev字符设备支持多种通信方式,比如采用socket时是ChardevSocket,采用udp时是ChardevUdp,这些设备都是chardev字符设备,即它们的基类都是字符设备。
struct SocketChardev {Chardev parent;QIOChannel *ioc; /* Client I/O channel */QIOChannelSocket *sioc; /* Client master channel */......SocketAddress *addr;	/* socket设备地址 */......
}

Features

  • Qemu在实现vhost-user设备时涉及到很多features,定义在不同的结构体中,我们逐一介绍并解释其意义:

VirtIONet

  • VirtIONet中存放命令行设置的网卡属性:
struct VirtIONet {......uint64_t host_features;
}

VirtIODevice

  • 根据VirtIO规范,所有VirtIO设备后端提供features,作为device features,前端驱动读取该features后选择自己支持的feature,设置到device作为最终协商的features。
struct VirtIODevice
{......uint64_t guest_features;	/* 1 */uint64_t host_features;		/* 2 */uint64_t backend_features;	/* 3 */
};
  1. guest_features,virtio driver按照规范在与host协商时,从pci空间读取的device features中,选择自己支持的features设置到后端,后端在设置完features后(VIRTIO_PCI_COMMON_GF),保存其值到guest_features。相关函数:virtio_set_features
  2. host_features,提供给guest的device features集合,前端virtio driver读取pci空间(VIRTIO_PCI_COMMON_DF)时会返回该值,guest的features只能是device features的子集。它将用户配置并保存在VirtIONet host_features的属性作为输入,再根据VirtIONet关联的peer设备的能力,手动增删部分features,计算出最终features集合,存放到VirtIODevice host_features中。相关函数:virtio_net_get_features
  3. backend_features,如果virtio设备是virtio网卡,且virtio网卡为tap,vhost或者vdpa,数据面会被卸载,该字段存放的是Qemu获取的从slave获取得到的features。相关函数:virtio_net_get_features

vhost_dev

struct vhost_dev {......uint64_t features;			/* 1 */uint64_t acked_features;	/* 2 */uint64_t backend_features;	/* 3 */uint64_t protocol_features;	/* 4 */
}
  1. 从slave侧通过vhost协议命令字VHOST_USER_GET_FEATURES获取的features。相关函数:vhost_dev_init
  2. Guest,Qemu,Slave协商后最终达成的features。相关函数:virtio_net_set_features
  3. Slave侧定义并支持的features。相关函数:vhost_user_backend_init
  4. Slave侧定义的vhost protocol features。相关函数:vhost_user_backend_init

NetVhostUserState

  • 当Slave侧无法工作时,Qemu侧需要标记vhost_dev设备停止使用,清空数据结构中保存的信息,此时唯一需要保存的是前端virtio 驱动初始化时协商得到的virtio features,acked_features。以便在vhost_dev设备恢复后作为初始值。NetVhostUserState中的acked_features作保存vhost_dev中的acked_features之用。
typedef struct NetVhostUserState {NetClientState nc;......uint64_t acked_features;
} NetVhostUserState;

feature_bits

  • Qemu在响应前端设置的features时,会将结果放到acked_features中,当vhost_dev启动时设置给slave,此操作的前提是slave必须支持这些features,Qemu将vhost-user设备slave侧支持的features硬编码为user_feature_bits,将vhost-net设备slave侧支持的features硬编码为kernel_feature_bits,如下所示。当Qemu在保存前端设置的features时,如果是vhost-user网卡,只允许存在于user_feature_bits中的features被设置,vhost-net网卡亦然。
/* Features supported by others. */
static const int user_feature_bits[] = {VIRTIO_F_NOTIFY_ON_EMPTY,VIRTIO_RING_F_INDIRECT_DESC,VIRTIO_RING_F_EVENT_IDX,VIRTIO_F_ANY_LAYOUT,VIRTIO_F_VERSION_1,VIRTIO_NET_F_CSUM,VIRTIO_NET_F_GUEST_CSUM,VIRTIO_NET_F_GSO,VIRTIO_NET_F_GUEST_TSO4,VIRTIO_NET_F_GUEST_TSO6,VIRTIO_NET_F_GUEST_ECN,VIRTIO_NET_F_GUEST_UFO,VIRTIO_NET_F_HOST_TSO4,VIRTIO_NET_F_HOST_TSO6,VIRTIO_NET_F_HOST_ECN,VIRTIO_NET_F_HOST_UFO,VIRTIO_NET_F_MRG_RXBUF,VIRTIO_NET_F_MTU,VIRTIO_F_IOMMU_PLATFORM,VIRTIO_F_RING_PACKED,VIRTIO_NET_F_RSS,VIRTIO_NET_F_HASH_REPORT,/* This bit implies RARP isn't sent by QEMU out of band */VIRTIO_NET_F_GUEST_ANNOUNCE,VIRTIO_NET_F_MQ,VHOST_INVALID_FEATURE_BIT
};/* Features supported by host kernel. */
static const int kernel_feature_bits[] = {VIRTIO_F_NOTIFY_ON_EMPTY,VIRTIO_RING_F_INDIRECT_DESC,VIRTIO_RING_F_EVENT_IDX,VIRTIO_NET_F_MRG_RXBUF,VIRTIO_F_VERSION_1,VIRTIO_NET_F_MTU,VIRTIO_F_IOMMU_PLATFORM,VIRTIO_F_RING_PACKED,VIRTIO_NET_F_HASH_REPORT,VHOST_INVALID_FEATURE_BIT
};

Backend

  • backend意指vhost-user网卡的backend,即slave或dpdk,这里我们只介绍一个数据结构vhost_user_socket
struct vhost_user_socket {......char *path;				/* 1 */bool is_server;			/* 2*//** The "supported_features" indicates the feature bits the* vhost driver supports. The "features" indicates the feature* bits after the rte_vhost_driver_features_disable/enable().* It is also the final feature bits used for vhost-user* features negotiation.*/uint64_t supported_features;	/* 3 */uint64_t features;				/* 4 */uint64_t protocol_features;		/* 5 */......
}
  1. path,vhost-user网卡的socket路径
  2. is_server,标记dpdk侧是服务端还是客户端,当dpdk为客户端时会周期性的连接Qemu,当dpdk为服务端是会等到Qemu来连接,客户端和服务端只是建立连接的发起者不同,但无论哪种方式vhost协议的协商流程都相同,都是Qemu侧发起。
  3. supported_features,dpdk vhost库默认支持的virtio features。
  4. features,最终提供给Qemu的virtio features。
  5. protocol_features,dpdk支持的vhost protocol features。

流程详解

  • 这里我们用一张图表示vhost-user网卡初始化,features协商的架构与流程,如下:
    请添加图片描述
  • 图示说明参考左上角,特别说明:
  1. 红色数字表示features相关时序,并非整个vhost-user设备生命周期时序
  2. 灰色文档枚举出部分dpdk侧支持的virtio features和vhost protocol features,不一定完整
  • 下面的流程主要围绕vhost-user网卡生命周期中features协商相关过程进行介绍,整个过程需要参考上述图片

启动过程

  • Qemu在启动虚机时,如果配置有vhost-user网卡,在设备初始化时会做如下三个工作:
  1. 字符设备初始化
  2. vhost-user网卡设备初始化
  3. virtio-net设备初始化
  • 以上流程在virtio网络Data Plane卸载原理 —— vhost协议协商流程中有详细介绍,这里不再赘述。

网络连接

  • Qemu在启动过程的设备实例化阶段,会初始化chardev设备,然后初始化netdev设备,在初始化netdev时会检查与slave端的连接是否建立,如果没有则会等待连接建立,然后注册连接到达时的回调接口,初始化netdev设备并等待连接的调用栈如下:
qemu_initqemu_create_late_backendsnet_init_clients/* 针对每个网卡设备调用初始化函数net_init_netdev */qemu_opts_foreach(qemu_find_opts("netdev"),net_init_netdev, NULL, errp)) net_client_initnet_client_init1/* 根据网卡类型匹配初始化函数列表,调用对应的初始化函数* 这里是vhost-user类型,调用net_init_vhost_user函数 */net_client_init_fun[netdev->type](netdev, netdev->id, peer, errp)	<=> net_init_vhost_usernet_vhost_user_initdo {qemu_chr_fe_wait_connected} while (!s->started);qemu_chr_wait_connectedcc->chr_wait_connected	<=> tcp_chr_wait_connected
  • 等待连接的方式随Qemu使能vhost-user网卡的模式而有所差别,Qemu模式为server时,会被动等待slave连接,为client时则主动连接slave,这里我们分析server模式的流程:
tcp_chr_wait_connectedwhile (s->state != TCP_CHARDEV_STATE_CONNECTED) {if (s->is_listen) {tcp_chr_accept_server_sync(chr);qio_net_listener_wait_clientg_main_loop_run(loop);
  • 从上可以看到,Qemu作为server时会一直等待客户端连接,连接建立之后,Qemu会注册IO到达的回调函数如下:
net_vhost_user_initdo {qemu_chr_fe_wait_connected(&s->chr, &err);qemu_chr_fe_set_handlers(&s->chr, NULL, NULL,net_vhost_user_event, NULL, nc0->name, NULL,true);} while (!s->started);
  • 这里的net_vhost_user_event就是连接到达的回调函数,首次启动时slave端发起连接后,此函数就会被调用,在虚机运行过程中,如果连接断开或者重连,net_vhost_user_event也会被调用,vhost-user网卡的启动和停用都在这个回调中实现。

网卡启动

  • vhost-user网卡的启动在slave发起连接后触发,如下:
net_vhost_user_eventswitch (event) {case CHR_EVENT_OPENED:vhost_user_start(queues, ncs, s->vhost_user)vhost_net_init		
  • 仔细分析网卡启动时vhost_dev的初始化流程:
struct vhost_net *vhost_net_init(VhostNetOptions *options)
{int r;/* 判断是否为vhost-net网卡,vhost-net网卡的数据面由kernel卸载,因此backend为kernel */bool backend_kernel = options->backend_type == VHOST_BACKEND_TYPE_KERNEL;/* 为vhost_net结构体分配内存 */struct vhost_net *net = g_new0(struct vhost_net, 1);	if (backend_kernel) {/* 如果是vhost-net网卡,获取backend的fd,通常就是tap设备的fd */r = vhost_net_get_fd(options->net_backend);/* TODO */net->dev.backend_features = qemu_has_vnet_hdr(options->net_backend)? 0 : (1ULL << VHOST_NET_F_VIRTIO_NET_HDR);/* 设置backend文件描述符 */net->backend = r;net->dev.protocol_features = 0;} else {/* 初始化各features字段 */net->dev.backend_features = 0;net->dev.protocol_features = 0;net->backend = -1;/* vhost-user needs vq_index to initiate a specific queue pair */net->dev.vq_index = net->nc->queue_index * net->dev.nvqs;}/* 初始化vhost_dev结构体,核心动作是启动vhost_dev设备,完成vhost protocol协商 */r = vhost_dev_init(&net->dev, options->opaque,options->backend_type, options->busyloop_timeout,&local_err);/* 从NetVhostUserState获取acked_features* 作为vhost_dev acked_features字段的初始值 *//* Set sane init value. Override when guest acks. */if (net->nc->info->type == NET_CLIENT_DRIVER_VHOST_USER) {features = vhost_user_get_acked_features(net->nc);if (~net->dev.features & features) {fprintf(stderr, "vhost lacks feature mask %" PRIu64" for backend\n",(uint64_t)(~net->dev.features & features));goto fail;}}/* 初始化vhost_dev.acked_features字段 */vhost_net_ack_features(net, features);
}
  • 两种场景会导致网卡启动,一是虚机首次启动,slave端连接vhost-user的socket路径后,二是slave断开连接后的重连。针对第一种场景,从注释我们可以了解到,vhost_dev.acked_features的初始值来自NetVhostUserState.acked_features,目的是保证其有效性,之后当Guest设置features时会覆盖acked_features。针对第二种场景,NetVhostUserState.acked_features存放的值是vhost_dev停止后保存的值,用于恢复vhost_dev原有的acked_features。这里为什么有两个数据结构来保存同一个acked_features?原因是这两个结构中acked_features的功能不一样,NetVhostUserState中的acked_features是用于备份vhost_dev中的acked_features,vhost_dev停止时会清空数据结构信息,启动时在vhost协议协商过程中会重新加载vhost_dev中的各个字段,唯有acked_features字段不行,因为它需要Guest virtio driver协商,这个时机只在虚机启动或者virtio driver重新加载时才会出现,因此acked_features需要保存留作恢复vhost_dev.acked_features之用。
  • 仔细分析vhost_dev_init中features相关逻辑:
int vhost_dev_init(struct vhost_dev *hdev, void *opaque,VhostBackendType backend_type, uint32_t busyloop_timeout,Error **errp)
{uint64_t features;vhost_set_backend_type(hdev, backend_type);					/* 1 */dev->vhost_ops = &user_ops;hdev->vhost_ops->vhost_backend_init(hdev, opaque, errp);	/* 2 */hdev->vhost_ops->vhost_get_features(hdev, &features);		/* 3 */hdev->features = features;									/* 4 */......
}
  1. 根据backend类型注册具体的vhost协议操作接口,对于vhost-user设备,注册的接口为user_ops
  2. 调用user_ops的接口vhost_user_backend_init初始化backend。
  3. 调用user_ops的接口vhost_user_get_features获取dpdk侧支持的virtio features
  4. 保存获取的virtio features,自此我们确认vhost_dev.features中保存的是slave侧获取到的virtio features
  • vhost_user_backend_init中会初始化backend_features以及protocol_features:
#define VIRTIO_F_BAD_FEATURE		30#define VHOST_USER_F_PROTOCOL_FEATURES 30static int vhost_user_backend_init(struct vhost_dev *dev, void *opaque,Error **errp)
{uint64_t features, protocol_features;/* 发送vhost协议VHOST_USER_GET_FEATURES命令字获取dpdk侧支持的features */vhost_user_get_features(dev, &features);/* 检查dpdk的是否支持VHOST_USER_F_PROTOCOL_FEATURES */if (virtio_has_feature(features, VHOST_USER_F_PROTOCOL_FEATURES)) {/* 如果支持,将VHOST_USER_F_PROTOCOL_FEATURES设置为后端的features */dev->backend_features |= 1ULL << VHOST_USER_F_PROTOCOL_FEATURES;/* 发送VHOST_USER_GET_PROTOCOL_FEATURES命令字获取protocol features */vhost_user_get_u64(dev, VHOST_USER_GET_PROTOCOL_FEATURES,&protocol_features);/* 保存VHOST_USER_GET_PROTOCOL_FEATURES features到protocol_features */dev->protocol_features =protocol_features & VHOST_USER_PROTOCOL_FEATURE_MASK;......}.....
}
  • 从上面可以看出,backend_features和virtio features毫无关系,它的作用是保存vhost-user backend定义的features,对于dpdk来说只定义了一个features,就是VHOST_USER_F_PROTOCOL_FEATURES。这里还有一个小trick,dpdk将VHOST_USER_F_PROTOCOL_FEATURESfeature定义为bit 30,这个bit位在virtio规范里被作为保留位,Host置位该bit表示协商失败,Guest永远不会读取并设置该bit位,因此该位被dpdk赋予新的含义,即是否支持VHOST_USER_F_PROTOCOL_FEATURES
  • 如果backend支持VHOST_USER_F_PROTOCOL_FEATURES,则还需要进一步调用vhost协议命令字获取dpdk支持的protocol features并保存。
  • 再次总结网卡启动过程涉及到的feature作用如下:
  1. features:Qemu侧保存的dpdk侧支持的所有features,该值从dpdk侧通过vhost协议查询得到。可以是virtio features,也可以是自定义的features。自定义的features目前只有一个VHOST_USER_F_PROTOCOL_FEATURES,它的bit位不能与virtio features bit位冲突。
  2. acked_features:Qemu侧保存的vhost-user网卡使能的virtio features,这个值只能是virtio features,并且只能是user_feature_bits的其中之一。
  3. backend_features :Qemu侧保存的backend支持的features,dpdk只实现了一个,即VHOST_USER_F_PROTOCOL_FEATURES。与virtio规范定义的features无关。
  4. protocol_features:Qemu侧保存dpdk所支持的所有protocol features,通过vhost协议查询得到。与virtio规范定义的features无关。

Guest探测

  • vhost-user网卡的feature协商时机并不是dpdk连接Qemu的ChardevSocket时,而取决于Guest virtio driver初始化virtio-net设备的顺序,摘抄virtio规范中要求的设备初始化顺序如下:
The driver MUST follow this sequence to initialize a device:
1. Reset the device.
2. Set the ACKNOWLEDGE status bit: the guest OS has noticed the device.
3. Set the DRIVER status bit: the guest OS knows how to drive the device.
4. Read device feature bits, and write the subset of feature bits understood by the OS and driver to the
device. During this step the driver MAY read (but MUST NOT write) the device-specific configuration
fields to check that it can support the device before accepting it.
5. Set the FEATURES_OK status bit. The driver MUST NOT accept new feature bits after this step.
6. Re-read device status to ensure the FEATURES_OK bit is still set: otherwise, the device does not
support our subset of features and the device is unusable.
7. Perform device-specific setup, including discovery of virtqueues for the device, optional per-bus setup,
reading and possibly writing the device’s virtio configuration space, and population of virtqueues.
8. Set the DRIVER_OK status bit. At this point the device is “live”.
  • 第4步,virtio driver读取设备提供的features,device(Qemu)返回VirtIODevice中的host_features,driver选择其中支持的features设置到device,Qemu侧触发如下流程:
virtio_pci_common_writeswitch (addr) {case VIRTIO_PCI_COMMON_GF:virtio_set_featuresvirtio_set_features_nocheck
  • 单独分析virtio_set_features_nocheck函数:
static int virtio_set_features_nocheck(VirtIODevice *vdev, uint64_t val)
{VirtioDeviceClass *k = VIRTIO_DEVICE_GET_CLASS(vdev);bool bad = (val & ~(vdev->host_features)) != 0;		/* 1 */val &= vdev->host_features;							/* 2 */if (k->set_features) {k->set_features(vdev, val); <=> virtio_net_set_features}vdev->guest_features = val;							/* 3 */return bad ? -1 : 0;
}
  1. 如果guest设置的feature不在device提供的feature中, 返回错误
  2. 将guest设置但host不具备的feature对应bit位去掉
  3. 保存guest设置的feature到guest_features
  • virtio_net_set_features函数最终调用vhost_ack_features,保存Guest设置的feature到acked_features,以备设置features到slave:
vhost_ack_features(&net->dev, vhost_net_get_feature_bits(net), features);void vhost_net_ack_features(struct vhost_net *net, uint64_t features){/* 将backend_features作为向slave设置feature的初始值 */net->dev.acked_features = net->dev.backend_features;/* guest设置的feature保存到acked_features中 */vhost_ack_features(&net->dev, vhost_net_get_feature_bits(net), features);}
  • 遍历slave支持的所有feature user_feature_bits,逐一比较guest是否使能,如果使能则将acked_features对应bit置位:
void vhost_ack_features(struct vhost_dev *hdev, const int *feature_bits,uint64_t features)
{const int *bit = feature_bits;while (*bit != VHOST_INVALID_FEATURE_BIT) {uint64_t bit_mask = (1ULL << *bit);if (features & bit_mask) {hdev->acked_features |= bit_mask;}bit++;}
}
  • 自此我们知道Guest设置的feature信息两个地方有记录,一是VirtIODevice.guest_features,这里保存的feature经过了host_features过滤,二是vhost_dev.acked_features,这里保存的feature不只经过host_feature过滤,还经过user_feature_bits的过滤,只保留了user_feature_bits支持的feature,通常情况下两者是相同的。除此之外,可以确认Guest设置feature后Qemu会保存,但不会立即同步设置给slave。那什么时候设置呢?这是在virtio 规范定义的设备初始化的第5步,设置device状态时。如下:
virtio_pci_common_writeswitch (addr) {case VIRTIO_PCI_COMMON_STATUS:virtio_set_status(vdev, val & 0xFF);k->set_status	<=> virtio_net_set_status

网卡停用

  • TODO

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.luyixian.cn/news_show_18636.aspx

如若内容造成侵权/违法违规/事实不符,请联系dt猫网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【Docker Desktop】Neo4j

1、下载neo4j并启动 docker run --rm --name testneo4j -p 7474:7474 -p 7687:7687 -d -v C:/Users/ASUS/Desktop/neo4j/data:/data -v C:/Users/ASUS/Desktop/neo4j/logs:/logs -v C:/Users/ASUS/Desktop/neo4j/import:/var/lib/neo4j/import -v C:/Users/ASUS/Desktop/neo4j…

Mysql出现问题:慢查询日志失效解决方案

❤️作者主页&#xff1a;小虚竹 ❤️作者简介&#xff1a;大家好,我是小虚竹。Java领域优质创作者&#x1f3c6;&#xff0c;CSDN博客专家&#x1f3c6;&#xff0c;华为云享专家&#x1f3c6;&#xff0c;掘金年度人气作者&#x1f3c6;&#xff0c;阿里云专家博主&#x1f3…

【C++入门】学习使用二维数组基本知识及用法详解

&#x1f9db;‍♂️iecne个人主页&#xff1a;&#xff1a;iecne的学习日志 &#x1f4a1;每天关注iecne的作品&#xff0c;一起进步 &#x1f4aa;一起学习&#xff0c;必看iecne &#x1f433;希望大家多多支持&#x1f970;一起进步呀&#xff01; 文章目录一.定义方式1.1…

深度学习进阶-自然语言处理-04-基于计数的方法(上)

语料库&#xff1a;大量的文本数据 语料库通常采用树的结构&#xff0c;我们这里的与料理假定没有添加任何标签&#xff0c;在实际项目中&#xff0c;一般都会给文本数据添加标签&#xff08;如&#xff0c;词性&#xff09; 1 语料库的预处理 步骤一:创建单词列表 #样本文章…

JavaScript学习笔记:Navigator

<!DOCTYPE html> <html><head><meta charset"utf-8"><title></title><script>// BOM// 浏览器对象模型// BOM可以使我们通过js来操作浏览器// 在BOM中为我们提供了一组对象&#xff0c;用来完成对浏览器的操作// BOM对象/…

基本控件案例集锦(下)

基本控件案例集锦&#xff08;下&#xff09; DatePicker&#xff0c;TimePicker&#xff0c;AlertDialoig 1.DatePicker 1.1简介 1.简介&#xff1a; Android为用户提供显示日期与时间的控件DatePicker和TimePicker。 日期选择控件(DatePicker)主要功能是向用户提供包含年…

JS面向对象之构造函数和原型(2)

文章目录1.构造函数1-1.使用构造函数面向对象1-2.构造函数里面的原型prototype1-3.对象原型__proto__1-4.constractor构造函数1-5.构造函数、实例、原型对象三者之间的关系1.构造函数 1-1.使用构造函数面向对象 ES6之前对象的公共部分&#xff0c;不是使用类解决的&#xff0…

沉睡者IT - [短视频运营] 抖音短视频成SEO新风口

下面会从以下5个企业关心的问题来聊聊&#xff0c;如果有什么不明白的地方可以评论交流。 一、什么是SEO? 二、为什么要做抖音SEO? ​三、哪些企业适合做抖音SEO? 四、抖音SEO怎么做? 五、影响抖音排名的因素有哪些? 一、什么是SEO? SEO是“搜索引擎优化”英文单词首字母…

C++ 函数学习笔记

前言&#xff1a;主要是自己学习过程的积累笔记&#xff0c;所以跳跃性比较强&#xff0c;建议先自学后拿来作为复习用。 文章目录1 函数基础2 参数传递2.1 const 形参和实参2.2 引用形参2.3 数组形参2.4 数组引用形参2.5 多维数组形参2.6 可变形参3 返回类型和 return 语句3.1…

ASP.NET Core--依赖注入

文章目录依赖注入什么是依赖注入什么是依赖什么是注入依赖注入解决的问题.Net Core DI替换默认服务容器依赖注入 什么是依赖注入 什么是依赖 Rely类 public class Rely {public Task Test(string testMessage){Console.WriteLine(testMessage);return Task.FromResult(0);}…

【QT学习】如何绘制圆角窗口?

文章目录前言一、实现效果二、基础知识1.QBitmap类2.QPainter类3.setMask函数三、实现代码总结前言 在使用QT创建窗口时&#xff0c;所创建出来的默认窗口都是矩形的。当我们隐藏默认标题栏&#xff0c;想自己绘制自定义的标题栏时&#xff0c;就会发现矩形的窗口过于棱角分明。…

加深印象篇之Servlet

环境配置 需要配置web.xml文件,如图所示: userServlet:是指那个继承自HttpServlet的.java文件的名称 index.jsp:是指那个随意一个.jsp文件名称即可 要使用到Servlet相关类,需要提前将相关导包导入到pom.xml文件中 相关文件的编写 1、JDBCUtils.java文件(存储连接数据库的…

(附源码)计算机毕业设计ssm大数据学院图书管理系统

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

web前端面试题附答案016-怎么让顶部轮播图渲染的更快?

一、为什么强调轮播图&#xff1f; 很多时候我们强调用户体验&#xff0c;而这里更多时候我们更强调完美的首屏体验&#xff0c;而现在几乎每个网站顶部第一个大模块就是轮播图。轮播图占得区域最大&#xff0c;图片质量也更高&#xff0c;几乎一张图片的面积&#xff0c;体积就…

【C语言】结构体字节对齐

目录 前言 一、什么是字节对齐&#xff1f; 二、结构体字节对齐 三、#pragma pack()的使用 总结 结语 封面 前言 本教程可能会用到一点汇编的知识&#xff0c;看不懂没关系&#xff0c;知道是那个意思就行了。使用的工具是vs2010。 一、什么是字节对齐&#xff1f; 字节…

【易购管理系统】商品列表

我们来写一下商品管理界面 在Goods.vue中 <template><div><!-- 1.搜索区域 --><div class"header"></div><!-- 2.表格区域展示视图数据 --><div class"wrapper"></div><!-- 3.分页 --></div&g…

数据分析3-pandas

文章目录pandaspandas常用数据类型1.Series的创建pandas读取外部数据pandas读取数据库DataFrame基础字典列表排序索引loc与iloc字符串离散化数据的合并pandas pandas常用数据类型 Series一维数据&#xff0c;带标签数组 DataFrame 二维&#xff0c;Series容器 import pandas …

【ElasticSearch】(分组统计,自动补全,数据同步)

分组统计&#xff0c;自动补全&#xff0c;数据同步1.分组统计1-1.聚合为桶1-2.桶内度量2.RestAPI结果条件过滤数据同步安装MQ声明交换机、队列发送MQ消息接收MQ消息搭建集群修改系统配置集群状态监控1.分组统计 桶(bucket): 桶的作用&#xff0c;是按照某种方式对数据进行分…

关苏哲-洞察问题本质,解决工作难题

高效管理者的三大技能 问题界定的6个问题 1.你所需要解决的问题是什么&#xff1f; 2.你为什么需要解决这个问题&#xff1f; 3.你期待的理想结果是什么&#xff1f; 4.这个问题包括哪些子问题&#xff1f; 5.你曾经尝试过哪些解决方式&#xff1f;其他人呢&#xff1f; 6.这个…