QEMU tap数据接收流程

这篇具有很好参考价值的文章主要介绍了QEMU tap数据接收流程。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

QEMU直接从tap/tun取数据

QEMU tap数据接收步骤:

  1. qemu从tun取数据包
  2. qemu将数据包放入virtio硬件网卡。
  3. qemu触发中断。
  4. 虚拟机收到中断,从virtio读取数据。

在qemu中步骤1(tap_read_packet)和步骤2(qemu_send_packet_async)都是在tap_send中完成的,其中步骤2是异步流程。

qemu/net/tap.c
static void tap_send(void *opaque)                                               
 {                                                                                
     TAPState *s = opaque;                                                       
     int size;                                                                   
     int packets = 0;                                                             
     while (true) {                                                               
         uint8_t *buf = s->buf;                                                 
         uint8_t min_pkt[ETH_ZLEN];                                             
         size_t min_pktsz = sizeof(min_pkt);                                                                                                                    
         size = tap_read_packet(s->fd, s->buf, sizeof(s->buf));                   
         if (size <= 0) {                                                       
             break;                                                             
         }                                                                       
		 
         if (s->host_vnet_hdr_len && !s->using_vnet_hdr) {                      
             buf  += s->host_vnet_hdr_len;                                       
             size -= s->host_vnet_hdr_len;                                       
         }                                                                                                                                                 
         if (net_peer_needs_padding(&s->nc)) {                                   
             if (eth_pad_short_frame(min_pkt, &min_pktsz, buf, size)) {         
                 buf = min_pkt;                                                 
                 size = min_pktsz;                                               
             }                                                                    
         }                                                                       
                                                                                 
         size = qemu_send_packet_async(&s->nc, buf, size, tap_send_completed);   
         if (size == 0) {                                                       
             tap_read_poll(s, false);                                           
             break;                                                             
         } else if (size < 0) {                                                 
             break;                                                             
         }                                                                                                                                                
         /*                                                                     
          * When the host keeps receiving more packets while tap_send() is      
          * running we can hog the QEMU global mutex.  Limit the number of       
          * packets that are processed per tap_send() callback to prevent       
          * stalling the guest.                                                 
          */                                                                      
         packets++;                                                             
         if (packets >= 50) {                                                   
             break;                                                             
         }                                                                        
     }                                                                            
 }                                

QEMU tap数据接收流程

qemu通过qemu_net_queue_deliver将数据包发送到virtio_queue,在发送之前若delivering或!qemu_can_send_packet满足,则先将数据包加入packets队列,随后在qemu_net_queue_flush阶段将数据包发送到virtio_queue中,上图中virtio_net_receive就到达virtio虚拟硬件网卡了。

QEMU tap数据接收流程

QEMU通过vhost-net从tap/tun取数据

vhost-net驱动加载时会生成/dev/vhost-net设备。
qemu-kvm启动时会open设备/dev/vhost-net,将调用vhost_net_open完成这个过程,vhost_net_open会进行handle_tx_net、handle_rx_net poll函数的初始化。
handle_tx_net、handle_rx_net最终会调用tun_recvmsg、tun_sendmsg进行数据收发。

/drivers/vhost/net.c:
static int vhost_net_open(struct inode *inode, struct file *f)
{
... ...
vhost_poll_init(n->poll + VHOST_NET_VQ_TX, handle_tx_net, EPOLLOUT, dev);
vhost_poll_init(n->poll + VHOST_NET_VQ_RX, handle_rx_net, EPOLLIN, dev);
... ...
}

static void handle_rx_net(struct vhost_work *work)
{
	struct vhost_net *net = container_of(work, struct vhost_net,
					     poll[VHOST_NET_VQ_RX].work);
	handle_rx(net);
}

handle_rx函数中recvmsg完成从tun取数据,通过copy_to_iter将msg放入virtio_queue,最后vhost_add_used_and_signal_n实现通知机制,qemu收到数据。

static void handle_rx(struct vhost_net *net)
{
... ...
		err = sock->ops->recvmsg(sock, &msg,
					 sock_len, MSG_DONTWAIT | MSG_TRUNC);
... ...
		num_buffers = cpu_to_vhost16(vq, headcount);
		if (likely(mergeable) &&
		    copy_to_iter(&num_buffers, sizeof num_buffers,
				 &fixup) != sizeof num_buffers) {
			vq_err(vq, "Failed num_buffers write");
			vhost_discard_vq_desc(vq, headcount);
			break;
		}
		vhost_add_used_and_signal_n(&net->dev, vq, vq->heads,
					    headcount);
... ...
}

vhost_net通过vhost_worker内核线程进行工作队列的调度用于完成poll,vhost_worker内核线程是qemu通过vhost_dev_ioctl VHOST_SET_OWNER时创建的。

drivers/vhost/vhost.c:
static int vhost_poll_wakeup(wait_queue_entry_t *wait, unsigned mode, int sync,
			     void *key)
{
	struct vhost_poll *poll = container_of(wait, struct vhost_poll, wait);

	if (!(key_to_poll(key) & poll->mask))
		return 0;

	vhost_poll_queue(poll);
	return 0;
}

void vhost_work_init(struct vhost_work *work, vhost_work_fn_t fn)
{
	clear_bit(VHOST_WORK_QUEUED, &work->flags);
	work->fn = fn;
}
EXPORT_SYMBOL_GPL(vhost_work_init);

/* Init poll structure */
void vhost_poll_init(struct vhost_poll *poll, vhost_work_fn_t fn,
		     __poll_t mask, struct vhost_dev *dev)
{
	init_waitqueue_func_entry(&poll->wait, vhost_poll_wakeup);
	init_poll_funcptr(&poll->table, vhost_poll_func);
	poll->mask = mask;
	poll->dev = dev;
	poll->wqh = NULL;

	vhost_work_init(&poll->work, fn);
}
EXPORT_SYMBOL_GPL(vhost_poll_init);

static int vhost_worker(void *data)
{
... ...
	for (;;) {
	... ...
		node = llist_del_all(&dev->work_list);
		if (!node)
			schedule();

		node = llist_reverse_order(node);
		/* make sure flag is seen after deletion */
		smp_wmb();
		llist_for_each_entry_safe(work, work_next, node, node) {
			clear_bit(VHOST_WORK_QUEUED, &work->flags);
			__set_current_state(TASK_RUNNING);
			work->fn(work);
			if (need_resched())
				schedule();
		}
	... ...
	}
... ...
}

long vhost_dev_set_owner(struct vhost_dev *dev)
{
... ...
	worker = kthread_create(vhost_worker, dev, "vhost-%d", current->pid);
... ...
}

long vhost_dev_ioctl(struct vhost_dev *d, unsigned int ioctl, void __user *argp)
{
... ...
	if (ioctl == VHOST_SET_OWNER) {
		r = vhost_dev_set_owner(d);
		goto done;
	}
... ...
}
drivers/vhost/net.c:
static long vhost_net_ioctl(struct file *f, unsigned int ioctl,
			    unsigned long arg)
{
... ...
	switch (ioctl) {
... ...
	case VHOST_RESET_OWNER:
		return vhost_net_reset_owner(n);
	case VHOST_SET_OWNER:
		return vhost_net_set_owner(n);
	default:
		mutex_lock(&n->dev.mutex);
		r = vhost_dev_ioctl(&n->dev, ioctl, argp);
		if (r == -ENOIOCTLCMD)
			r = vhost_vring_ioctl(&n->dev, ioctl, argp);
		else
			vhost_net_flush(n);
		mutex_unlock(&n->dev.mutex);
		return r;
	}
}

static long vhost_net_set_owner(struct vhost_net *n)
{
... ...
	r = vhost_dev_set_owner(&n->dev);
... ...
	return r;
}

static const struct file_operations vhost_net_fops = {
	.owner          = THIS_MODULE,
	.release        = vhost_net_release,
	.read_iter      = vhost_net_chr_read_iter,
	.write_iter     = vhost_net_chr_write_iter,
	.poll           = vhost_net_chr_poll,
	.unlocked_ioctl = vhost_net_ioctl,
#ifdef CONFIG_COMPAT
	.compat_ioctl   = vhost_net_compat_ioctl,
#endif
	.open           = vhost_net_open,
	.llseek		= noop_llseek,
};

static struct miscdevice vhost_net_misc = {
	.minor = VHOST_NET_MINOR,
	.name = "vhost-net",
	.fops = &vhost_net_fops,
};

static int vhost_net_init(void)
{
	if (experimental_zcopytx)
		vhost_net_enable_zcopy(VHOST_NET_VQ_TX);
	return misc_register(&vhost_net_misc);
}

主机vhost驱动加载时调用vhost_net_init注册一个MISC驱动,生成/dev/vhost-net的设备文件。
主机qemu-kvm启动时调用open对应的vhost_net_open做主要创建队列和收发函数的挂载,接着调用ioctl启动内核线程vhost,做收发包的处理。
主机qemu通过ioctl配置kvm模块,主要设置通信方式,因为主机vhost和virtio只进行报文的传输,kvm进行提醒。
虚拟机virtio模块注册,生成虚拟机的网络设备,配置中断和NAPI。
虚拟机发包流程如下:
直接从应用层走协议栈最后调用发送接口ndo_start_xmit对应的start_xmit,将报文放入发送队列,vp_notify通知kvm。
kvm通过vmx_handle_exit一系列调用到wake_up_process唤醒vhost线程。
vhost模块的线程激活并且拿到报文,在通过之前绑定的发送接口handle_tx_kick进行发送,调用虚拟网卡的tun_sendmsg最终到netif_rx接口进入主机内核协议栈。
虚拟机收包流程如下:
tap设备的ndo_start_xmit对应的tun_net_xmit最终调用到wake_up_process激活vhost线程,调用handle_rx_kick,将报文放入接收队列。
通过一系列的调用到kvm模块的接口kvm_vcpu_kick,向qemu虚拟机注入中断。
虚拟机virtio模块中断调用接口vp_interrupt,调用virtnet_poll,再调用到netif_receive_skb进入虚拟机的协议栈。
资料来源:https://blog.csdn.net/qq_20817327/article/details/106838029文章来源地址https://www.toymoban.com/news/detail-681902.html

到了这里,关于QEMU tap数据接收流程的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 模型数据处理-数据放入 session和@ModelAttribute 实现 prepare 方法详细讲解

    😀前言 本文详细讲解了模型数据处理-数据放入 session和@ModelAttribute 实现 prepare 方法详细讲解 🏠个人主页:尘觉主页 🧑个人简介:大家好,我是尘觉,希望我的文章可以帮助到大家,您的满意是我的动力😉😉 在csdn获奖荣誉: 🏆csdn城市之星2名 ⁣⁣⁣⁣ ⁣⁣⁣⁣ ⁣⁣⁣⁣

    2024年02月12日
    浏览(34)
  • unity开发Android,unity直接打开其他apk,并传参数;以及接收参数的方法

    一,获取参数 要在Unity中实现Android端打开另一个应用程序并传递参数,你可以使用Android的Intent机制。  在需要启动另一个应用程序的地方调用这个方法。例如,你可以在按钮点击事件中调用它: 二,unity c#获取参数 Unity中开发的应用程序被Android的另一个应用程序传递参数时

    2024年01月21日
    浏览(41)
  • 安卓广播发送接收流程

    本文基于Andorid 11。 动态注册广播接收器,参数:BroadcastReceiver, IntentFilter。 静态注册广播,安卓8以后除几个特殊的广播外,静态注册方式只能接收显示广播。 查看动态注册广播流程: 1.2 ContextImpl.registerReceiverInternal 传入IIntentReceiver对象作为参数,调用AMS.registerReceiverWithFea

    2024年04月17日
    浏览(47)
  • 一百八十二、大数据离线数仓完整流程——步骤一、用Kettle从Kafka、MySQL等数据源采集数据然后写入HDFS

    经过6个月的奋斗,项目的离线数仓部分终于可以上线了,因此整理一下离线数仓的整个流程,既是大家提供一个案例经验,也是对自己近半年的工作进行一个总结。 项目行业属于交通行业,因此数据具有很多交通行业的特征,比如转向比数据就是统计车辆左转、右转、直行

    2024年02月07日
    浏览(50)
  • 一百八十六、大数据离线数仓完整流程——步骤五、在Hive的DWS层建动态分区表并动态加载数据

    经过6个月的奋斗,项目的离线数仓部分终于可以上线了,因此整理一下离线数仓的整个流程,既是大家提供一个案例经验,也是对自己近半年的工作进行一个总结。 1、Hive的DWS层建库建表语句 --如果不存在则创建hurys_dc_dws数据库 create database if not exists hurys_dc_dws; --使用hurys_

    2024年02月07日
    浏览(45)
  • Android InputEventReceiver事件接收流程分析

    本文基于Android 12。 InputEvent经过inputflinger读取后,通过Inputchannel发送到Java层的InputEventReceiver对象,输入事件和View的状态强相关,事件发送需要确定当前的焦点App,焦点Window(View),事件接收者是谁,所以InputEventReceiver对象也在View的创建流程中被初始化,ViewRootImpl中通过In

    2024年02月02日
    浏览(42)
  • ExpressLRS开源之接收机固件编译烧录步骤

    ExpressLRS是航模上目前比较流行的开源发射机和接收机开源代码之一。 其目的旨在提供最好的完全开放、高刷新率的无线电控制链路,同时以低延迟保持该速率下的最大可实现范围,在900MHz和2.4GHz频率下对硬件提供大量支持。 这个也是笔者一直使用的RC控制链路。从无人机的

    2024年02月10日
    浏览(47)
  • 分享一次性能测试过程,5个步骤直接起飞!

    在企业中完成性能测试项目是一个挑战性强、技术含量高的任务。本文将分享一个公司完成高性能游戏系统的性能测试过程,展示 如何完成一次成功的性能测试项目 。 项目背景: 这是一家游戏公司,推出了一款新的游戏软件,系统要求高性能、高并发、高可用,为确保用户

    2024年02月11日
    浏览(36)
  • 一百八十七、大数据离线数仓完整流程——步骤六、在ClickHouse的ADS层建表并用Kettle同步Hive中DWS层的结果数据

    经过6个月的奋斗,项目的离线数仓部分终于可以上线了,因此整理一下离线数仓的整个流程,既是大家提供一个案例经验,也是对自己近半年的工作进行一个总结。 1、ClickHouse的ADS层建库建表语句 --如果不存在则创建hurys_dc_ads数据库 create database if not exists hurys_dc_ads; --使用

    2024年02月07日
    浏览(49)
  • 【NACK】视频rtp包接收及nack触发流程走读

    这里大神分析很很透彻了:原文地址:WebRTC中NACK的处理流程 - 资料 - 音视频开发中文网 - 构建全国最权威的音视频技术交流分享论坛 简书大神的分析更进一步:飞奔的蜗牛rancho

    2024年02月08日
    浏览(44)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包