LinuxCP插件virtio与内核vhost

news2024/11/16 21:34:38

以下为LCP创建的接口对,VPP侧为物理接口port7,映射到Linux侧的为虚拟接口hostap1,接口hostap1作为vhost的后端存在。VPP侧接口tap1为前端的virtio接口。

vpp# show lcp
itf-pair: [0] port7 tap1 hostap1 24 type tap
vdp#
vdp# show interface
          Name       Idx    State  MTU (L3/IP4/IP6/MPLS)     Counter          Count
port7                 5      up          9000/0/0/0
tap1                  9      up          9000/0/0/0     
vpp#
vpp# quit
/ #
/ # ip -d link show hostap1
24: hostap1: <NO-CARRIER,BROADCAST,MULTICAST,PROMISC,UP> mtu 9000 qdisc mq state DOWN mode DEFAULT group default qlen 1000
    tun addrgenmode eui64 numtxqueues 256 numrxqueues 256

Linux内核发送报文的流程如下:

Linux kernel(hostap1) --> virtio-input(tap1) --> ethernet-input
                                                       |
                                                       |
               port7-output <-- linux-cp-xc-ip4 <-- ip4-input
                    |
                    |
                 port7-tx

VPP LCP发送报文到Linux内核:

dpdk-input(port7) --> ethernet-input --> ip4-input-no-checksum --> ip4-lookup
                                                                       |
                                                                       |
              |--- ip4-dvr-dpo <--ip4-punt-redirect <-- ip4-punt <-- ip4-local
              |
              |
       ip4-dvr-reinject --> tap1-output --> tap1-tx --> Linux kernel(hostap1) 

以下内容分三个部分:virtio/vhost相关初始化,发送和接收流程。

一. virtio/vhost相关初始化

VPP LCP插件中函数tap_create_if创建以上用到的所有设备并进行相应的初始化。首先,打开设备文件/dev/net/tun,创建Linux内核中的tap类型设备hostap1。

tap_create_if
    tfd = open ("/dev/net/tun", O_RDWR | O_NONBLOCK); //获得描述符29
	
    ioctl(tfd=29,TUNGETFEATURES);  //特性协商:必须的特性- IFF_VNET_HDR

    ifr.ifr_flags |= IFF_TAP;
    ifr.ifr_name = "hostap1";
    ioctl (tfd, TUNSETIFF, (void *) &ifr);   //创建LInux TAP设备hostap1.
    //设置virtio网络头部大小
    ioctl (tfd, TUNSETVNETHDRSZ, sizeof (virtio_net_hdr_v1_t))

    //设置发送缓存大小
    ioctl(vif->tap_fds[i], TUNSETSNDBUF,  INT_MAX)

tun设备函数tun_chr_open处理open操作,分配结构tun_file,进行初始化,最终保存在文件结构file的成员private_data中。

在这里插入图片描述

内核函数__tun_chr_ioctl处理TUNSETIFF调用,创建网络设备。tun设备此时具有一个队列(numqueues),tun->tfiles数组大小为256,最多支持256个队列。可再打开/devnet/tun设备,创建tun_file结构,添加到tun->tfiles数组,扩充tun设备的队列数量。

__tun_chr_ioctl
    tun_set_iff(struct net *net, struct file *file, struct ifreq *ifr)
        alloc_netdev_mqs(sizeof(struct tun_struct), name, tun_setup, MAX_TAP_QUEUES=256);
        tun_attach(tun, file, false)
        register_netdevice(tun->dev)

如下图,增加了tun_struct结构。

在这里插入图片描述

其次,打开vhost-net设备文件,创建vhost网络设备。可多次打开vhost-net设备,获得多个文件描述符,对应vhost设备的多个队列。

tap_create_if
    /* open as many vhost-net fds as required and set ownership */
    num_vhost_queues = clib_max (vif->num_rxqs, vif->num_txqs);
    for (i = 0; i < num_vhost_queues; i++) {
        vfd = open ("/dev/vhost-net", O_RDWR | O_NONBLOCK);
        vec_add1 (vif->vhost_fds, vfd);
		//内核将创建vhost内核线程,名称:vhost-$pid,pid为VPP的进程ID号。
		//多队列,或者多设备的情况,会创建多个相同名称的内核线程。
        ioctl(vfd, VHOST_SET_OWNER, 0);  
    }
    ioctl(vif->vhost_fds[0], VHOST_GET_FEATURES, &vif->remote_features);  //特性需要支持VIRTIO_F_VERSION_1

内核函数vhost_net_open分配vhost_net结构,进行相应初始化,最终保存到文件结构file的成员private_data中。

     vhost_dev_init   初始化vhost_net->dev结构
              vhost_poll_init 初始化vhost_net->vqs[0/1].vq.poll
      vhost_poll_init初始化vhost_net->poll[0/1]
     file->private_data = vhost_net.

如下为分配的vhost_net结构,其具有接收/发送两套队列结构vhost_net_virtqueue:
在这里插入图片描述
接下来,LCP进行发送和接收vring环的初始化。

tap_create_if
  for (i = 0; i < num_vhost_queues; i++) {
    if (i < vif->num_rxqs && (
	   args->error = virtio_vring_init (vm, vif, RX_QUEUE (i), args->rx_ring_sz)))
      goto error;

    if (i < vif->num_txqs && (
	   args->error = virtio_vring_init (vm, vif, TX_QUEUE (i), args->tx_ring_sz)))
      goto error;

发送和接收环使用相同的数据结构virtio_vring_t。如下为初始化的vif->rxq_vrings结构。vring->queue_id 标识rx和tx队列的索引,其中偶数为rx队列,奇数为tx队列。queue_id的最低1位对应于内核中vhost驱动中的vhost_net_virtqueue的索引:VHOST_NET_VQ_RX=0, VHOST_NET_VQ_TX=1。

在这里插入图片描述

如下为初始化的vif->txq_vrings结构。对于发送换,没有分配call_fd。

在这里插入图片描述
根据当前环境的配置情况(两个VPP线程:主线程和工作线程),tap_create_if初始化了3个队列,其中2个发送:vif->txq_vrings[2];一个接收:vif->rxq_vrings[1]。不同配置,发送和接收队列不相同。以下将所有队列的信息同步到内核的vhost-net驱动中。

以下为将vring数量同步到内核vhost驱动。

VHOST_SET_VRING_NUM(描述符30/31,TX/RX)
      描述符30对应RX和TX两个队列;描述符31仅有一个TX队列。
      vhost_net<30>->vqs[VHOST_NET_VQ_TX/VHOST_NET_VQ_RX].vq.num = 256/256;
      vhost_net<31>->vqs[VHOST_NET_VQ_TX].vq.num = 256;

将接收/发送vring的三个环结构desc/avail/used地址同步给内核vhost驱动。

VHOST_SET_VRING_ADDR
       描述符30对应两个队列id:0,1;描述符31对应一个队列id:2。
       vif->rxq_vrings[0].queue_id == 0
       vif->txq_vrings[0].queue_id == 1
       vif->txq_vrings[1].queue_id == 2

       addr.flags = 0;
       addr.desc_user_addr = pointer_to_uword (rxq/txq_vring->desc);
       addr.avail_user_addr = pointer_to_uword (rxq/txq_vring->avail);
       addr.used_user_addr = pointer_to_uword (rxq/txq_vring->used);

       将vif接口三个vring分配的desc/avail/used地址下发到内核vhost。
       vhost_net<30>->vqs[VHOST_NET_VQ_TX/VHOST_NET_VQ_RX].vq.<desc/avail/used> = txq/rxq_vring->desc/avail/used;
       vhost_net<31>->vqs[VHOST_NET_VQ_TX].vq.<desc/avail/used> = txq_vring->desc/avail/used;

以下将创建的call_fd和kick_fd同步给内核vhost驱动。

VHOST_SET_VRING_CALL
    tap_create_if中为发送vif->rxq_vrings[0]创建了call_fd和kick_fd,描述符分别为32和33。

    vhost_net<30>->vqs[VHOST_NET_VQ_RX].vq.call_ctx.ctx = eventfd_ctx_fdget(32)
    vhost_net<30>->vqs[VHOST_NET_VQ_RX].vq.kick = eventfd_fget(33)
             vhost_poll_start(&vq->poll, vq->kick);  内核vhost开始监听kick描述符。
        vhost_net<30/31>->vqs[VHOST_NET_VQ_TX].vq.call_ctx.ctx = NULL/NULL;

VHOST_SET_VRING_KICK
    vif->txq_vrings[0/1]两个发送vring不接收内核中断,没有创建call_fd(等于-1),创建的kick_fd描述符分别为34和35

    vhost_net<30>->vqs[VHOST_NET_VQ_TX].vq.kick = eventfd_fget(34)
            vhost_poll_start(&vq->poll, vq->kick);
    vhost_net<31>->vqs[VHOST_NET_VQ_TX].vq.kick = eventfd_fget(35)
            vhost_poll_start(&vq->poll, vq->kick);

以下将vhost_net与tap设备关联起来。vhost_net与tap设备建立了两个关联:a) vhost_net子结构保存了tap设备描述符;b) vhost_net的poll挂载在tap设备的等待队列上。

在这里插入图片描述

VPP virtio信息与内核vhost同步之后,内核结构如下,变化主要体现在vhost_virtqueue结构中。

在这里插入图片描述

二. Linux vhost发送报文到VPP的virtio接口

tun设备发送函数如下,将报文添加到tun_files对应套接口的接收队列上(sk_receive_queue),唤醒等待队列中的wait项,这里有之前注册的vhost_net->poll[RX/TX].wait,发送和接口的wait都注册在这里。

tun_net_xmit(struct sk_buff *skb, struct net_device *dev)
        struct tun_struct *tun = netdev_priv(dev);
        int txq = skb->queue_mapping;
        struct tun_file *tfile;

        tfile = rcu_dereference(tun->tfiles[txq]);
        skb_queue_tail(&tfile->socket.sk->sk_receive_queue, skb);

        wake_up_interruptible_poll(&tfile->wq.wait, POLLIN | POLLRDNORM | POLLRDBAND);

对于POLLIN/POLLOUT,处理程序统一为 vhost_poll_wakeup。这里为POLLIN事件,对应上vhost_net->poll[VHOST_NET_VQ_RX].wait。调用其vhost_work_queue将work添加到vhost_dev设备的work_list链表,唤醒内核处理线程(vhost-$pid)。

vhost_poll_queue(vhost_net->poll[VHOST_NET_VQ_RX])
         vhost_work_queue(poll->dev, &poll->work);
                 list_add_tail(&work->node, &dev->work_list);
                 wake_up_process(dev->worker);
内核处理线程,这里work的处理函数为handle_rx_net->handle_rx。
vhost_worker(void *data)
          work->fn(work);

这里实际处理函数为handle_rx。

handle_rx
     struct vhost_net_virtqueue *nvq = &net->vqs[VHOST_NET_VQ_RX];
     struct vhost_virtqueue *vq = &nvq->vq;
     struct msghdr msg = { .msg_iov = vq->iov,}
     vhost_disable_notify(&net->dev, vq);    //禁止linux-cp插件的kick操作

     struct socket *sock = vq->private_data;  (tun设备描述符对应的套接口)
            get_rx_bufs
                 vhost_get_vq_desc  返回descriptor的索引
                       __get_user(ring_head,  &vq->avail->ring[last_avail_idx % vq->num]) //第一个可用描述符的索引。
                       __copy_from_user(&desc, vq->desc + i, sizeof desc);    // 将索引对应的描述符结构内容拷贝到desc中(struct vring_desc)。

                      //将描述符中指定的缓存地址和长度转成内核iov结构
                      translate_desc(vq, vhost64_to_cpu(vq, desc.addr), vhost32_to_cpu(vq, desc.len), iov + iov_count,  )
                      vq->last_avail_idx++;   /* On success, increment avail index. */

            //get_rx_bufs函数返回值为vring_used_elem结构的heads,其成员id为描述符索引,len为描述符缓存大小,另外返回headcount为heads的数量。
            heads[headcount].id = cpu_to_vhost32(vq, d);
            heads[headcount].len = cpu_to_vhost32(vq, len);
            return headcount;
      至此,根据描述符内容填充完整了msghdr结构的iov,调用recvmsg结构tun设备的数据。
      msg.msg_iovlen = in;
      err = sock->ops->recvmsg(NULL, sock, &msg, sock_len, MSG_DONTWAIT | MSG_TRUNC);   //tun_recvmsg
      vhost_add_used_and_signal_n(&net->dev, vq, vq->heads,   headcount);   //通知linux-cp的virtio设备数据准备完毕。

函数vhost_add_used_and_signal_n通知linux-cp的virtio设备,数据准备完毕。

vhost_add_used_and_signal_n(&net->dev, vq, vq->heads,   headcount);  
     vhost_add_used_n(vq, heads, count);
     vhost_signal(dev, vq);

__vhost_add_used_n
     start = vq->last_used_idx % vq->num;
     used = vq->used->ring + start;

     __put_user(heads[0].id, &used->id)
     __put_user(heads[0].len, &used->len)
     vq->last_used_idx += count
     __put_user(cpu_to_vhost16(vq, vq->last_used_idx), &vq->used->idx)


vhost_signal(struct vhost_dev *dev, struct vhost_virtqueue *vq)
    vhost_notify
    eventfd_signal(vq->call_ctx, 1);

如下,内核vhost_virtqueue结构的变化。

在这里插入图片描述

VPP中函数virtio_input_node作为输入型节点处理接收到的报文。

virtio_input_node
     virtio_device_input_inline
            virtio_device_input_gso_inline    //接收处理报文
            virtio_refill_vring_split                  //重新填充接收描述符,当消耗的描述符数量超过总量1/8时,进行重新填充。

由于Linux内核将used->idx设置为1,vring记录的last_used_idx为0,表明内核使用了一个描述符。以下取出此描述符对应的vlib_buffer_t,进行处理。

virtio_device_input_gso_inline
      n_left = vring->used->idx - vring->last_used_idx;  
      slot = vring->used->ring[vring->last_used_idx & 255].id ;   //取出内核使用的vlib_buffer_t索引
       len = vring->used->ring[vring->last_used_idx & 255].len - hdr_sz;  //减去virtio头部长度,得到报文的实际长度。

       bi0 = vring->buffers[slot];
       vlib_buffer_t *b0 = vlib_get_buffer (vm, bi0);   //得到报文数据所在的vlib_buffer_t,开始对报文进行处理。


       vring->desc_in_use--;
       vring->last_used_idx++;   //由于接收到一个报文,消耗了一个描述符,desc_in_use变为255,last_used_idx增加为1。

处理完成之后,virtio接口vif的rxq_vrings变化如下:

在这里插入图片描述

三. VPP virtio接口发送报文到Linux内核

virtio接口的发送函数virtio_interface_tx_inline如下,如同与上一节,这里设计到的vring都是指vif结构中的txq_vring。这里主要是获取发送描述符,并填充发送数据。

virtio_interface_tx_inline
    virtio_interface_tx_split_gso_inline
        add_buffer_to_slot
        virtio_kick

add_buffer_to_slot
    vring_desc_t *d = &vring->desc[vring->desc_next]; //获得可用的发送描述符
    d.addr = pointer_to_uword (vlib_buffer_get_current (b))) - hdr_sz;   //vlib_buffer结构数据地址,减去virtio头部长度
    d.len = b->current_length + hdr_sz;  //数据长度加上virtio头部长度

    vring->buffers[vring->desc_next] = bi;   //保存待发送vlib_buffer_t的索引bi。
    vring->avail->ring[vring->avail->idx & mask] = vring->desc_next;

发送之后txq_vring结构变化如下:
在这里插入图片描述
发送第一个报文的变化对比如下:
在这里插入图片描述

内核函数handle_tx_kick调用handle_tx接收VPP virtio接口发送来的数据,发送给tap接口。

handle_tx_kick
    handle_tx
        vhost_net_virtqueue *nvq = &net->vqs[VHOST_NET_VQ_TX];
        struct msghdr msg = { .msg_iov = vq->iov };
    
        vhost_net_tx_get_vq_desc(net, vq, vq->iov, ARRAY_SIZE(vq->iov), &out, &in)
             vhost_get_vq_desc
                      head = vq->avail->ring[vq->last_avail_idx % vq->num];  //获得可用的发送描述符的索引。
                      translate_desc函数将描述符中的缓存地址和长度转换为内核iovec结构
         s = move_iovec_hdr(vq->iov, nvq->hdr, hdr_size, out);   //virtio头部数据保存到nvq->hdr, 去掉vq->iov中的virtio头部数据,
         msg.msg_iovlen = out;        //发送描述符的数量
         sock->ops->sendmsg(NULL, sock, &msg, len);      //tun_sendmsg
         vhost_add_used_and_signal(&net->dev, vq, head, 0);

内核vhost_virtqueue结构变化如下,

在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

QT登录界面

1.效果图 2.代码 #include "widget.h" #include "ui_widget.h" #include <QApplication> #include <QWidget> #include <QtWidgets>Widget::Widget(QWidget *parent): QMainWindow(parent), ui(new Ui::Widget) {ui->setupUi(this);…

【数据结构二叉树OJ系列】5、相同的树和另一个树

目录 一、相同的树 二、另一个树的子树 一、相同的树 题述&#xff1a; 给定二叉树&#xff0c;检验他们是否相同。 如果两个树在结构上相同&#xff0c;并且节点具有相同的值&#xff0c;则认为他们是相同的。 示例1&#xff1a; 题中已给&#xff1a; struct TreeNode {i…

黑马大数据学习笔记0-环境配置

目录 设置VMware网络CentOS操作系统三台虚拟机系统配置&#xff1a;主机名、固定IP、SSH免密登录配置主机名映射配置SSH免密登录创建hadoop用户并配置免密登录JDK1.8环境部署防火墙、SELinux、时间同步设置快照 视频p3-p5 https://www.bilibili.com/video/BV1WY4y197g7?p3 设…

tomcat第1章 tomcat介绍、安装、部署项目

一、前言 我们在写javaweb程序的时候有没有考虑如果没有tomcat&#xff0c;我们怎么给客户端返回响应数据&#xff1f;既然能返回响应数据&#xff0c;为什么还要使用tomcat&#xff1f; 什么是tomcat&#xff0c;以及tomcat历史版本发展情况&#xff0c;每个版本servlet规范…

如何与ChatGPT愉快地聊天

原文链接&#xff1a;https://mp.weixin.qq.com/s/ui-O4CnT_W51_zqW4krtcQ 人工智能的发展已经走到了一个新的阶段&#xff0c;在这个阶段&#xff0c;人工智能可以像人一样与我们进行深度的文本交互。其中&#xff0c;OpenAI的ChatGPT是一个具有代表性的模型。然而&#xff0…

JavaFX学习:MVC模式中的PropertyValueFactory

PropertyValueFactory类是“TableColumn cell value factory”,绑定创建列表中的项。示例如下&#xff1a; TableColumn<Person,String> firstNameCol new TableColumn<Person,String>("First Name");firstNameCol.setCellValueFactory(new PropertyVal…

Blender基础入门(3):复杂建模技巧

文章目录 我个人的Blender专栏前言基础属性设置选择循环选择&#xff1a;Alt左键透视选择锁定物体编辑模式123快捷键按下/&#xff08;右侧Shift左边&#xff0c;<>按键右边&#xff09;&#xff0c;锁定物体先在物体模式选择物体&#xff0c;再到编辑模式就只会选择该物…

淘宝订单拉取更新历史状态~需求

&#x1f4da;目录 订单接口api需求问题解决 Map<String,TaobaoOrder> 订单接口api 可自行查询官网文档&#xff0c;点击进入 需求 通过接口中has_next 标识判断该时间断是否还有下一页数据,直到该值数据为false时,表面该时间范围内的订单数据获取完成. 拉取完成后需要对…

Maven工程开发中的继承与聚合

1. 聚合工程概念 设置一个空的maven工程&#xff0c;工程里面只有pom文件&#xff0c;另外将这个工程的打包方式设置为pom。 在聚合工程里面添加聚合工程里面管理的模块 2.聚合总结 3.继承 例如下面02工程继承上面的01工程&#xff0c;在02工程的pom文件中要配置要继承的父工…

分组统计--Pandas

1.groupby 1.1 函数功能 先对数据进行分组&#xff0c;然后在每个分组上运用聚合函数、转换函数 1.2 函数语法 DataFrame.groupby(byNone, axis0, levelNone, as_indexTrue, sortTrue, group_keysTrue, observedFalse, dropnaTrue)1.3 函数参数 参数含义by分组依据axis沿着…

【电子学会】2023年05月图形化一级 -- 找食物

找食物 1. 准备工作 &#xff08;1&#xff09;添加背景&#xff1a;Jungle&#xff1b; &#xff08;2&#xff09;删除小猫角色&#xff0c;添加角色&#xff1a;Dog2、Donut&#xff1b; 2. 功能实现 &#xff08;1&#xff09;点击绿旗&#xff0c;小狗的初始位置在舞…

打家劫舍(力扣)动态规划 JAVA

你是一个专业的小偷&#xff0c;计划偷窃沿街的房屋。每间房内都藏有一定的现金&#xff0c;影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统&#xff0c;如果两间相邻的房屋在同一晚上被小偷闯入&#xff0c;系统会自动报警。 给定一个代表每个房屋存放金额的非…

从C语言到C++_25(树的十道OJ题)力扣:606+102+107+236+426+105+106+144+94+145

目录 606. 根据二叉树创建字符串 - 力扣&#xff08;LeetCode&#xff09; 解析代码&#xff1a; 102. 二叉树的层序遍历 - 力扣&#xff08;LeetCode&#xff09; 解析代码&#xff1a; 107. 二叉树的层序遍历 II - 力扣&#xff08;LeetCode&#xff09; 解析代码&…

倪海厦针灸大成学习笔记

学习倪海厦老师人纪针灸课程的学习笔记&#xff0c;比较粗糙&#xff0c;不一定准确&#xff0c;分享给大家&#xff0c;大家发现笔记中的错误&#xff0c;欢迎反馈&#xff01; Word文档链接 有需要的欢迎私信

VSCode控制台乱码解决

如果你试过很多方法依然解决不了乱码问题&#xff0c;不妨试试这个 设置完成后重启电脑 开机后查看一下当前的编码 打开VSCode试一下 不得不吐槽一下MS。。。。。。

虚幻引擎程序化资源生成框架PCG 之 UPCGBlueprintElement源码笔记(二)数据流

PCG节点处理的是数据流&#xff0c;也就是点云&#xff0c;点云到底是啥&#xff1f;笼统地说就是一个个携带着信息的点组成的集合。但是在具体是使用过程中&#xff0c;我们还得了解这些”携带着信息的点“是如何被层层包装起来的。本文中老王就和大家一边拆解源代码一边做实验…

以太网数据链路层相关技术(六)

目录 一、概述 二、MAC地址 2.1 概述 2.2 MAC地址的意义 三、共享介质型网络与非共享介质网络 四、VLAN技术 一、概述 在各设备之间的数据传输时&#xff0c;物理层和数据链路层是必不可少的。其中&#xff0c;物理层的通信媒介包括双绞线电缆、同轴电缆、光纤、电波以及…

Springcloud基础(4)-Ribbon负载均衡

负载均衡 1. Ribbon简单描述2. 在SpringCloud中查看相关处理源码3. ribbon的默认策略&#xff0c;懒加载3. 实操中的相关问题 1. Ribbon简单描述 Spring Cloud Ribbon 是一套基于 Netflix Ribbon 实现的客户端负载均衡和服务调用工具。Ribbon是Netflix发布的开源项目&#xff0…

MySQL第四天作业 单表查询和多表查询

单表查询 创建表 CREATE TABLE emp (empno int(4) NOT NULL,ename varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,job varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,mgr int(4) NULL DEFAULT NULL,hiredate d…

【电子学会】2023年05月图形化一级 -- 舞蹈演出

舞蹈演出 1. 准备工作 &#xff08;1&#xff09;删除小猫角色&#xff1b; &#xff08;2&#xff09;添加角色Ballerina&#xff0c;为角色添加声音Bossa Nova&#xff1b; &#xff08;3&#xff09;添加背景Theater和Concert。 2. 功能实现 &#xff08;1&#xff09…