翼度科技»论坛 云主机 LINUX 查看内容

QEMU tap数据接收流程

7

主题

7

帖子

21

积分

新手上路

Rank: 1

积分
21
QEMU直接从tap/tun取数据

QEMU tap数据接收步骤:

  • qemu从tun取数据包
  • qemu将数据包放入virtio硬件网卡。
  • qemu触发中断。
  • 虚拟机收到中断,从virtio读取数据。
在qemu中步骤1(tap_read_packet)和步骤2(qemu_send_packet_async)都是在tap_send中完成的,其中步骤2是异步流程。
  1. qemu/net/tap.c
  2. static void tap_send(void *opaque)                                               
  3. {                                                                                
  4.      TAPState *s = opaque;                                                      
  5.      int size;                                                                  
  6.      int packets = 0;                                                            
  7.      while (true) {                                                               
  8.          uint8_t *buf = s->buf;                                                
  9.          uint8_t min_pkt[ETH_ZLEN];                                             
  10.          size_t min_pktsz = sizeof(min_pkt);                                                                                                                    
  11.          size = tap_read_packet(s->fd, s->buf, sizeof(s->buf));                  
  12.          if (size <= 0) {                                                      
  13.              break;                                                            
  14.          }                                                                       
  15.                  
  16.          if (s->host_vnet_hdr_len && !s->using_vnet_hdr) {                     
  17.              buf  += s->host_vnet_hdr_len;                                       
  18.              size -= s->host_vnet_hdr_len;                                       
  19.          }                                                                                                                                                
  20.          if (net_peer_needs_padding(&s->nc)) {                                   
  21.              if (eth_pad_short_frame(min_pkt, &min_pktsz, buf, size)) {         
  22.                  buf = min_pkt;                                                
  23.                  size = min_pktsz;                                               
  24.              }                                                                    
  25.          }                                                                       
  26.                                                                                  
  27.          size = qemu_send_packet_async(&s->nc, buf, size, tap_send_completed);   
  28.          if (size == 0) {                                                      
  29.              tap_read_poll(s, false);                                          
  30.              break;                                                            
  31.          } else if (size < 0) {                                                
  32.              break;                                                            
  33.          }                                                                                                                                                
  34.          /*                                                                     
  35.           * When the host keeps receiving more packets while tap_send() is      
  36.           * running we can hog the QEMU global mutex.  Limit the number of      
  37.           * packets that are processed per tap_send() callback to prevent      
  38.           * stalling the guest.                                                
  39.           */                                                                     
  40.          packets++;                                                            
  41.          if (packets >= 50) {                                                   
  42.              break;                                                            
  43.          }                                                                        
  44.      }                                                                           
  45. }                                
复制代码

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进行数据收发。
  1. /drivers/vhost/net.c:
  2. static int vhost_net_open(struct inode *inode, struct file *f)
  3. {
  4. ... ...
  5. vhost_poll_init(n->poll + VHOST_NET_VQ_TX, handle_tx_net, EPOLLOUT, dev);
  6. vhost_poll_init(n->poll + VHOST_NET_VQ_RX, handle_rx_net, EPOLLIN, dev);
  7. ... ...
  8. }
  9. static void handle_rx_net(struct vhost_work *work)
  10. {
  11.         struct vhost_net *net = container_of(work, struct vhost_net,
  12.                                              poll[VHOST_NET_VQ_RX].work);
  13.         handle_rx(net);
  14. }
复制代码
handle_rx函数中recvmsg完成从tun取数据,通过copy_to_iter将msg放入virtio_queue,最后vhost_add_used_and_signal_n实现通知机制,qemu收到数据。
  1. static void handle_rx(struct vhost_net *net)
  2. {
  3. ... ...
  4.                 err = sock->ops->recvmsg(sock, &msg,
  5.                                          sock_len, MSG_DONTWAIT | MSG_TRUNC);
  6. ... ...
  7.                 num_buffers = cpu_to_vhost16(vq, headcount);
  8.                 if (likely(mergeable) &&
  9.                     copy_to_iter(&num_buffers, sizeof num_buffers,
  10.                                  &fixup) != sizeof num_buffers) {
  11.                         vq_err(vq, "Failed num_buffers write");
  12.                         vhost_discard_vq_desc(vq, headcount);
  13.                         break;
  14.                 }
  15.                 vhost_add_used_and_signal_n(&net->dev, vq, vq->heads,
  16.                                             headcount);
  17. ... ...
  18. }
复制代码
vhost_net通过vhost_worker内核线程进行工作队列的调度用于完成poll,vhost_worker内核线程是qemu通过vhost_dev_ioctl   VHOST_SET_OWNER时创建的。
  1. drivers/vhost/vhost.c:
  2. static int vhost_poll_wakeup(wait_queue_entry_t *wait, unsigned mode, int sync,
  3.                              void *key)
  4. {
  5.         struct vhost_poll *poll = container_of(wait, struct vhost_poll, wait);
  6.         if (!(key_to_poll(key) & poll->mask))
  7.                 return 0;
  8.         vhost_poll_queue(poll);
  9.         return 0;
  10. }
  11. void vhost_work_init(struct vhost_work *work, vhost_work_fn_t fn)
  12. {
  13.         clear_bit(VHOST_WORK_QUEUED, &work->flags);
  14.         work->fn = fn;
  15. }
  16. EXPORT_SYMBOL_GPL(vhost_work_init);
  17. /* Init poll structure */
  18. void vhost_poll_init(struct vhost_poll *poll, vhost_work_fn_t fn,
  19.                      __poll_t mask, struct vhost_dev *dev)
  20. {
  21.         init_waitqueue_func_entry(&poll->wait, vhost_poll_wakeup);
  22.         init_poll_funcptr(&poll->table, vhost_poll_func);
  23.         poll->mask = mask;
  24.         poll->dev = dev;
  25.         poll->wqh = NULL;
  26.         vhost_work_init(&poll->work, fn);
  27. }
  28. EXPORT_SYMBOL_GPL(vhost_poll_init);
  29. static int vhost_worker(void *data)
  30. {
  31. ... ...
  32.         for (;;) {
  33.         ... ...
  34.                 node = llist_del_all(&dev->work_list);
  35.                 if (!node)
  36.                         schedule();
  37.                 node = llist_reverse_order(node);
  38.                 /* make sure flag is seen after deletion */
  39.                 smp_wmb();
  40.                 llist_for_each_entry_safe(work, work_next, node, node) {
  41.                         clear_bit(VHOST_WORK_QUEUED, &work->flags);
  42.                         __set_current_state(TASK_RUNNING);
  43.                         work->fn(work);
  44.                         if (need_resched())
  45.                                 schedule();
  46.                 }
  47.         ... ...
  48.         }
  49. ... ...
  50. }
  51. long vhost_dev_set_owner(struct vhost_dev *dev)
  52. {
  53. ... ...
  54.         worker = kthread_create(vhost_worker, dev, "vhost-%d", current->pid);
  55. ... ...
  56. }
  57. long vhost_dev_ioctl(struct vhost_dev *d, unsigned int ioctl, void __user *argp)
  58. {
  59. ... ...
  60.         if (ioctl == VHOST_SET_OWNER) {
  61.                 r = vhost_dev_set_owner(d);
  62.                 goto done;
  63.         }
  64. ... ...
  65. }
复制代码
  1. drivers/vhost/net.c:
  2. static long vhost_net_ioctl(struct file *f, unsigned int ioctl,
  3.                             unsigned long arg)
  4. {
  5. ... ...
  6.         switch (ioctl) {
  7. ... ...
  8.         case VHOST_RESET_OWNER:
  9.                 return vhost_net_reset_owner(n);
  10.         case VHOST_SET_OWNER:
  11.                 return vhost_net_set_owner(n);
  12.         default:
  13.                 mutex_lock(&n->dev.mutex);
  14.                 r = vhost_dev_ioctl(&n->dev, ioctl, argp);
  15.                 if (r == -ENOIOCTLCMD)
  16.                         r = vhost_vring_ioctl(&n->dev, ioctl, argp);
  17.                 else
  18.                         vhost_net_flush(n);
  19.                 mutex_unlock(&n->dev.mutex);
  20.                 return r;
  21.         }
  22. }
  23. static long vhost_net_set_owner(struct vhost_net *n)
  24. {
  25. ... ...
  26.         r = vhost_dev_set_owner(&n->dev);
  27. ... ...
  28.         return r;
  29. }
  30. static const struct file_operations vhost_net_fops = {
  31.         .owner          = THIS_MODULE,
  32.         .release        = vhost_net_release,
  33.         .read_iter      = vhost_net_chr_read_iter,
  34.         .write_iter     = vhost_net_chr_write_iter,
  35.         .poll           = vhost_net_chr_poll,
  36.         .unlocked_ioctl = vhost_net_ioctl,
  37. #ifdef CONFIG_COMPAT
  38.         .compat_ioctl   = vhost_net_compat_ioctl,
  39. #endif
  40.         .open           = vhost_net_open,
  41.         .llseek                = noop_llseek,
  42. };
  43. static struct miscdevice vhost_net_misc = {
  44.         .minor = VHOST_NET_MINOR,
  45.         .name = "vhost-net",
  46.         .fops = &vhost_net_fops,
  47. };
  48. static int vhost_net_init(void)
  49. {
  50.         if (experimental_zcopytx)
  51.                 vhost_net_enable_zcopy(VHOST_NET_VQ_TX);
  52.         return misc_register(&vhost_net_misc);
  53. }
复制代码


主机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.cnblogs.com/forwards/p/17514221.html
免责声明:由于采集信息均来自互联网,如果侵犯了您的权益,请联系我们【E-Mail:cb@itdo.tech】 我们会及时删除侵权内容,谢谢合作!

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x

举报 回复 使用道具