QEMU tap数据接收流程
QEMU直接从tap/tun取数据
QEMU tap数据接收步骤:
- qemu从tun取数据包
- qemu将数据包放入virtio硬件网卡。
- qemu触发中断。
- 虚拟机收到中断,从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通过qemu_net_queue_deliver将数据包发送到virtio_queue,在发送之前若delivering或!qemu_can_send_packet满足,则先将数据包加入packets队列,随后在qemu_net_queue_flush阶段将数据包发送到virtio_queue中,上图中virtio_net_receive就到达virtio虚拟硬件网卡了。

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
QEMU tap数据接收流程的更多相关文章
- atheros无线驱动之:数据接收流程
1:数据处理函数tasklet,workqueue在之前的初始化代码中的函数__ath_attach()中,有如下的代码: #ifndef ATH_SUPPORT_HTC#ifdef ADF_SUPP ...
- twemproxy接收流程探索——twemproxy代码分析正编
在这篇文章开始前,大家要做好一个小小的心理准备,由于twemproxy代码是一份优秀的c语言,为此,在twemproxy的代码中会大篇幅使用c指针.但是不论是普通类型的指针还是函数指针,都可以让我们这 ...
- twemproxy接收流程探索——剖析twemproxy代码正编
本文旨在帮助大家探索出twemproxy接收流程的代码逻辑框架,有些具体的实现需要我们在未来抽空去探索或者大家自行探索.在这篇文章开始前,大家要做好一个小小的心理准备,由于twemproxy代码是一份 ...
- stm32 usb数据接收与数据发送程序流程分析
http://blog.csdn.net/u011318735/article/details/17424349 既然学习了USB,那就必须的搞懂USB设备与USB主机数据是怎么通讯的.这里主要讲设备 ...
- Linux内核二层数据包接收流程
本文主要讲解了Linux内核二层数据包接收流程,使用的内核的版本是2.6.32.27 为了方便理解,本文采用整体流程图加伪代码的方式从内核高层面上梳理了二层数据包接收的流程,希望可以对大家有所帮助.阅 ...
- [转帖]Linux TCP/IP协议栈,数据发送接收流程,TCP协议特点
Linux TCP/IP协议栈,数据发送接收流程,TCP协议特点 http://network.51cto.com/art/201909/603780.htm 可以毫不夸张的说现如今的互联网是基于TC ...
- linux 内核网络数据包接收流程
转:https://segmentfault.com/a/1190000008836467 本文将介绍在Linux系统中,数据包是如何一步一步从网卡传到进程手中的. 如果英文没有问题,强烈建议阅读后面 ...
- 内核中用于数据接收的结构体struct msghdr(转)
内核中用于数据接收的结构体struct msghdr(转) 我们从一个实际的数据包发送的例子入手,来看看其发送的具体流程,以及过程中涉及到的相关数据结构.在我们的虚拟机上发送icmp回显请求包,pin ...
- nginx的请求接收流程(二)
在ngx_http_process_request_line函数中,解析完请求行之后,如果请求行的uri里面包含了域名部分,则将其保持在请求结构的headers_in成员的server字段,heade ...
- nginx的请求接收流程(一)
今年我们组计划写一本nginx模块开发以及原理解析方面的书,整本书是以open book的形式在网上会定时的更新,网址为http://tengine.taobao.org/book/index.htm ...
随机推荐
- 欢迎来到farter的可能是最后一个用于博客的地方【
目前先把公告栏里放一堆链接作为导航了(手机上竟然没有公告栏??? 准备从新浪博客往这里迁,整活可能也在这里? 还是复制一份公告栏的链接库吧( 歌声合成相关文章 敝个人站(好用好玩都在这) 敝渣浪博客( ...
- flutter填坑之旅(配置本地资源文件)
一.添加单个资源文件 项目下创建一个assets目录,在该目录下存放对应的资源文件(json.image.-) pubspec.yaml文件中配置资源文件路径(注意缩进要与上下文一直) assets: ...
- 第一课 使用开发者模式快速入门 Odoo 13
Odoo提供了一个快速应用开发框架,非常适合创建商业应用.这类应用通常用于保留业务记录,增删改查操作.Odoo 不仅简化了这类应用的创建,还提供了看板.日历.图表等视图的丰富组件,用于创建好看的用户界 ...
- 马拉车(manacher) & 回文自动机(PAM)
补充,PAM 的 a[0]=-1,这一点我每次写都要忘记. 读了徐安矣2023年集训队论文写的,对于差分性质和习题,我会在理解清楚之后再补充.本篇博客仅讨论前两种算法. 首先,马拉车和回文自动机都是处 ...
- Rust 语言风靡学术界
AWS 将 Rust 编译器团队负责人收入麾下的新闻让开发者们再次聚焦于这门兼具安全性与高性能的编程语言.近日,著名科学期刊 Nature 刊登了一篇文章,表明 Rust 语言也正在成为学术界最受欢迎 ...
- 为什么要学习 Markdown?究竟有什么用?
点击上方蓝字设为星标 下面开始今天的学习- 本文经授权转载自微信公众号:杰哥的 IT 之旅(ID:Jake_Internet),未经许可,禁止二次转载. 一.什么是 Markdown? Markdow ...
- LeetCode 双周赛 106(2023/06/10)两道思维题
本文已收录到 AndroidFamily,技术和职场问题,请关注公众号 [彭旭锐] 加入知识星球提问. 往期回顾:LeetCode 单周赛第 348 场 · 数位 DP 模版学会了吗? 双周赛 106 ...
- CANoe学习笔记(三):CANoe的诊断功能和cdd文件
内容: UDS诊断学习 CDD文件配置 诊断功能 一.UDS诊断学习: ①.UDS请求命令4种构成方式: SIDSID+SF(Sub-function)SID+DID(Data Identifier) ...
- Rust中的变量的声明和定义
变量的声明和定义 Rust中合法的标识符(包括变量名.函数名.triat名等)必须由数字.字母.下划线组成,而且不能以数字开头.这个和很多语言都是一样的.Rust将来也会允许其他Unicode字符作为 ...
- BeEF记录
前情提要 最近项目上常规手段遇阻,计划进行水坑钓鱼,一番搜索找到近期SolarMarker组织的手法,但是没有找到相关样本,于是就自己实现了一个类似的前端功能(水坑手法项目会持续记录学习,但目前不会放 ...