• 博客园logo
  • 会员
  • 周边
  • 新闻
  • 博问
  • 闪存
  • 赞助商
  • YouClaw
    • 搜索
      所有博客
    • 搜索
      当前博客
  • 写随笔 我的博客 短消息 简洁模式
    用户头像
    我的博客 我的园子 账号设置 会员中心 简洁模式 ... 退出登录
    注册 登录
杏子的成长日记
博客园    首页    新随笔    联系   管理    订阅  订阅

基于DPDK用户空间的Virtio网络设备驱动设计

目录
  • 1 系统总框架
    • dpdk
      • 1.概念
      • 2.优点
    • 虚拟化
    • 半虚拟化Virtio
      • 1.基本模型
      • 2.Virtio&&vhost
        • virtqueue
        • virtio发展历程
          • 1. 最早的架构
          • 2.内核态vhost
          • 3.用户态vhost
  • 2 Linux网络栈接收数据
    • 硬中断和软中断
      • 什么是硬中断和软中断
      • 为什么要有软中断?
      • Linux的解决方法
      • 软中断
      • 软中断子系统
      • 软中断触发的步骤
    • Linux 网络栈接收数据
      • 收包过程
      • 网卡驱动初始化
        • pci_driver->probe()
        • NAPI
      • 网卡收包
          • DPDK的收发包:
    • sk_buff(skb)
      • 生成一个skb的过程
        • 1.alloc_skb(size) 分配skb
        • 2.skb_reserve(m) 初始定位
        • 3.拷贝应用层数据
        • 4.设置传输层头部 skb_push
        • 5.设置IP层头部
        • 6.设置以太网头部
        • 7.在应用数据后追加padding skb_put
      • skb的分段和分页
        • 分页:使用非线性数据区
        • 分段:主要指IP分段的实现
  • 3 收包
    • 总流程

1 系统总框架

dpdk

1.概念

数据平面开发工具包,由用于加速在各种CPU架构上运行的数据包处理的库组成

2.优点

基于传统内核的数据传输的弊端:

1.大量的数据包带来频繁的中断
2.频繁的中断带来频繁的上下文切换,带来上下文切换的开销
3.cache miss
4.内核态和用户态的切换
5.局部性失效
6.将容易出错的代码放到用户空间,避免内核崩溃

DPDK在性能上的优化
“旁路内核”

  • UIO+mmap实现零拷贝
  • UIO+PMD减少中断和CPU上下文切换 驱动屏蔽了硬件发出中断,然后在用户态采用主动轮询的方式。
  • HugePages 减少TLB miss

虚拟化

虚拟化是指在一台物理计算机上创建多个虚拟计算环境,每个虚拟环境都可以运行独立的操作系统和应用程序

虚拟化主要分为CPU虚拟化、内存虚拟化和IO虚拟化,本项目主要关注IO虚拟化。
目前,IO虚拟化主要有IO全虚拟化、IO半虚拟化和IO透传三种方式

  • IO虚拟化:宿主机KVM截获客户机对IO设备的访问请求,然后用软件QEMU模拟硬件。 无需考虑底层硬件,不需要修改操作系统。但是效率较低。
  • IO半虚拟化:让客户机感知到“自己是虚拟机”
    好处:全虚拟化IO对模拟设的IO访问都会造成VM-exit,将控制权交给宿主机,这样会带来上下文切换、控制权转移带来的开销和延迟
    实现:客户机驱动(前端)<-->宿主机驱动(后端)
  • IO透传:1.直接将物理设备分配给虚拟机使用,性能最高,但需要物理设备支持;2.这也带来了安全风险、资源的灵活性降低;3.直接绕过了宿主机,不能对其进行防火墙、负载均衡等处理

半虚拟化Virtio

参考:https://tinylab.org/virtio-intro/
https://www.redhat.com/en/blog/introduction-virtio-networking-and-vhost-net
深入浅出DPDK

Virtio-net后端驱动经历了从virtio-net后端,到内核态vhost-net,到用户态vhost-user的过程

1.基本模型

KVM:为程序提供虚拟化硬件的内核模块

  • 基于内核的虚拟机。允许Linux充当虚拟机的管理工具,主机可以运行多个隔离的guest环境。
    QEMU:利用KVM模拟整个系统的运行环境
  • 一种托管虚拟机监视器,通过仿真为虚拟机提供不同的硬件和设备模型。客户机通过qemu命令行界面CLI执行。
    Libvirt: 为外部管理工具提供虚拟机管理能力
  • 可将XML格式的配置文件转换为CLI调用的接口。 还提供了一个管理守护进程,来配置子进程,例如qemu。

2.Virtio&&vhost

虚拟网络的实现主要分为控制平面和数据平面

实现:客户机驱动(前端,virtio net)<--虚拟队列(virtqueue)-->宿主机驱动(后端 vhost user)

virtqueue

连接虚拟机前端驱动和宿主机后端驱动的链路

descriptor table:实际要传输的数据
available ring、used ring:记录前端和后端处理描述符的进度

传统双指针法的弊端:只能顺序处理,前面的描述符处理完之前,后面的只能等待
virtio虚拟队列(virtqueue):

设备使用流程:

  • 驱动向设备提供数据缓冲区(virtio net driver填充空buffer)
    1.把数据缓冲区的地址、长度等信息保存到descriptor table
    2.把指向descriptor的指针添加到avail ring
    3.更新avail ring的头部指针
    4.将virtqueue编号写入queue notify寄存器,通知设备
  • 设备使用和归还数据缓冲区
    1.设备使用缓冲区数据(读取或者写入)
    2.把使用过的descriptor table的头指针添加到used ring
    3.更新used ring头部指针
    4.根据是否开启MSI-X中断,用不同的中断方式通知驱动。

virtio发展历程

1. 最早的架构

通信流程:

1.虚拟机中 virtio-net driver 通过虚拟 PCIE 总线感知到 QEMU 模拟的 virtio-net device,驱动初始化,两者建立控制通道,协商基本能力,虚拟机分配 vring 并与 QEMU 共享。
2.网络发包,虚拟机更新 vring,并通知 KVM, KVM 再通知 QEMU,QEMU 处理待发送的报文
3.网络收包,QEMU 收到报文,填充 vring,通过 KVM 向虚拟机触发虚拟中断,虚拟机完成收包

  • 缺点:上下文切换多,报文收发需要在虚拟机/KVM/QEMU 三者间上下文切换
    virtio 最初的实现形式,数据平面需要经过 QEMU,效率比较低。然而数据平面的核心操作就是 vring 的通知和前后端填充/提取 vring 的操作。所以,控制平面将 vring 协商好之后,数据平面不需要经过 QEMU.因此,virtio 数据平面可以脱离 QEMU,被‘卸载’到内核或者用户态。
2.内核态vhost
  • virtio spec:定义宿主机和虚拟机之间如何创建控制
  • vhost:通过将virtio的数据通道卸载到其他地方,提高性能。

数据平面由kernel直接接管。

3.用户态vhost

IO处理模块放在用户态,1.后端通过轮询的方式减少vm-exit带来的开销,2.用户态进程内存共享DPDK,再用户态直接操作网卡,收发数据
优点:1.利用共享内存的技术,减少了virtio-net和vhost之间数据传输的成本;2.IO处理模块全部放在用户态,减轻了Linux内核的工作负担

2 Linux网络栈接收数据

Linux 网络栈接收数据(RX):原理及内核实现(2022) (arthurchiao.art)

硬中断和软中断

什么是硬中断和软中断

根据实现的方式的不同,分为了硬中断和软中断:

  • 由 CPU 硬件实现的中断机制,就属于硬中断,比如中断、异常、INT指令都是由 CPU 实现的中断机制。
  • 由软件实现实现的中断机制,就属于软中断。比如 Linux 实现的软中断守护进程。

硬中断和软中断区别在于,谁去检测中断事件:

  • 硬中断就是 CPU 在每一个指令周期的最后,都会留一个 CPU 周期去查看是否有中断,如果有,就把中断号取出,去「中断向量表」中寻找中断处理程序,然后跳过去执行中断处理程序。
  • 软中断就是有一个单独的守护进程,不断轮询一组标志位,如果哪个标志位有值了,那去这个标志位对应的「软中断向量表数组」的相应位置,找到软中断处理函数,然后跳过去执行软中断处理函数。

为什么要有软中断?

硬中断的资源十分宝贵,在处理一个硬中断时,通常会临时关闭其他的中断,在当前的中断处理程序执行完之前就无法处理其他的中断了。

而网络包的收发过程时比较耗时的,如果站着硬中断函数不反悔,会影响其他硬中断的响应,例如鼠标、键盘的操作

Linux的解决方法

Linux 系统为了解决中断处理程序执行过长和中断丢失的问题,将中断过程分成了两个阶段,分别是「上半部和下半部分」(延后中断处理)。

  • 上半部用来快速处理中断,主要负责处理跟硬件紧密相关或者时间敏感的事情。
  • 下半部用来延迟处理上半部未完成的工作,一般由「内核守护进程」来完成,相当于将中断处理程序比较复杂的逻辑解耦出来了,并通过异步的方式来处理这些复杂的逻辑。

网卡收到网络包后,通过 DMA 方式将接收到的数据写入内存,将网络包的数据写入到内存后,下一步就需要通知内核来处理,于是网卡会触发一个硬中断,内核就会调用网卡的中断处理程序来处理该事件,这个事件的处理会分成上半部和下半部:

  • 上部分由硬中断处理程序处理,要做的事情很少,做完后就会发起了一次软中断,然后就结束了。这里发起的软中断,并不是向CPU 发送中断信号,而是将软中断标记数组中的某一个位置标记一下。
  • 下半部由软中断处理程序处理,内核守护进程会不断轮询软中断标记数组,看哪个位置被标记为 1 了,接着就去软中断向量表里,寻找这个标志位对应的处理程序,然后执行软中断处理程序处理,其主要是需要从内存中找到网络数据,再按照网络协议栈,对网络数据进行逐层解析和处理,最后把数据送给应用程序。

所以,软中断的作用就是承接原本硬中断处理程序比较复杂且耗时的工作,让硬中断的中断处理函数的逻辑尽可能的简单,从而提高系统的中断响应速度。

Linux 中的三种推迟中断(deferred interrupts):

  • softirq
  • tasklet
  • workqueue

软中断

软中断子系统

软中断是一个内核子系统:

  1. 每个 CPU 上会初始化一个 ksoftirqd 内核线程,负责处理各种类型的 softirq 中断事件
  2. 软中断事件的 handler 提前注册到 softirq 子系统, 注册方式 open_softirq(softirq_id, handler)

软中断触发的步骤

1.open_softirq()注册软中断处理函数

2.raise_softirq()将一个软中断进行标记

3.当内核调度器调用ksoftirqd线程的时候,处理所有标记的软中断,执行对应的软中断梳理函数softirq handler

Linux 网络栈接收数据

收包过程

1.内核初始化驱动,其中包括注册poll方法

2.网卡收到数据,通过DMA将数据包拷贝到ring buffer当中

3.如果此时NAPI没有在执行,发出硬件中断,通知系统收到了一个包

4.触发软中断,内核调度到软中断处理线程ksoftirqd

5.软中断处理的时候调用NAPI的poll()从ring buffer收包,并以skb的形式交给上层协议栈处理。

网卡驱动初始化

pci_driver->probe()

内核启动过程中,会通过PCI ID来依次识别PCI设备,然后为设备寻找合适的驱动。每个PCI驱动都注册了一个probe方法。

NAPI

内核有一种称为 NAPI(New API)的机制,允许网卡注册自己的 poll() 方法,执行这个方法就会从相应的网卡收包。

网卡收包

网卡是没有处理的(除了近年来出现的只能网卡)。所以到达网卡的数据如果没有进程或者线程来接收,只能被丢弃

收包方式:

  • 100%轮询:给网卡预留专门的
  • 硬件中断
DPDK的收发包:

1.起初是100%轮询,收发包完全不用任何中断

2.混合中断和轮询的方式:刚开始是轮询,连续多次收到包的个数为0时,应用程序设置了简单的策略决定什么时候进入休眠模式。进入休眠模式以后,如果再次有包收到,产生中断,再次唤醒收包线程,开始轮询地区去收包

sk_buff(skb)

生成一个skb的过程

要想形成一个最终的数据包,即以太帧(不考虑其它的链路层)。要进行以下的操作:
1.分配一个skb结构体
2.分配数据包的数据区

(填充数据载荷、TCP头部、IP头部、MAC头部,但每次填充具体内容之前,都要先将数据区的起始位置进行重定位(skb->data))
3.在skb数据区定位应用层起始位置
4.拷贝数据到应用层(假设应用层协议没有在socket接口之上被封装)
5.在skb数据区定位传输层起始位置
6.设置传输层头部字段
7.在skb数据区定位IP层起始位置
8.设置IP层头部字段
9.在skb数据区定位以太层起始位置
10.设置以太头部字段

四个重要的指针:head,data,tail,end

1.alloc_skb(size) 分配skb

包括申请skb缓冲区和分配skb结构体

img

2.skb_reserve(m) 初始定位

为头部预留空间 data指针和tail指针往后移动m

3.拷贝应用层数据

1.skb_push(n) 将data指针前移n

2.拷贝应用层数据

4.设置传输层头部 skb_push

5.设置IP层头部

6.设置以太网头部

7.在应用数据后追加padding skb_put

headroom和tailroom:对齐原则需要进行填充、前导码、纠错码、附加信息

skb的分段和分页

分页:使用非线性数据区

非线性区的含义是包含在sk_buff中的数据长度超过了线性数据区所能容纳的界限(一般为一页)。
分页的前提:DMA支持分散-聚集操作
sk_buff数据的总长度存储在len域,非线性数据的长度存储在sk_buff的data_len域。

skb_shared_info: skb_frag_t frags[MAX_SKB_PAGES]做多64kb

分段:主要指IP分段的实现

当一个数据报过长,需要分为多个,形成一个链表
skb_shared_info中:struct sk_buff *frag_list

3 收包

总流程

step1. virtio-net driver填充空buffer:向rxq virtqueue中的descriptor table填充空的descriptor,并更新avail ring的索引
step2. dpdk收包:vhost-user从host侧收包,首先为dpdk的mte_buf从avail ring的可用索引指向的descriptor table找到足够的空间,然后将mbuf中的数据填充进virtqueue的descriptor中,更新used ring的索引,并通知virio-net由有数据到来
step3. kernel收包:virtio-net利用used ring,得到对应的descriptor idx,然后将descriptor中存储的缓冲读取出来,并组装成skb,交付给协议栈

posted on 2023-05-19 10:36  杏子的成长日记  阅读(874)  评论(0)    收藏  举报
刷新页面返回顶部
博客园  ©  2004-2026
浙公网安备 33010602011771号 浙ICP备2021040463号-3