1.2 --> DPDK 的网卡绑定、解绑过程解析

首先,在回顾一下 DPDK 的技术框架,如下图所示,
在这里插入图片描述
PMD 是 DPDK 在用户态实现的网卡驱动程序,实际上还是依赖于内核 UIO Framework 内核模块,UIO Framework 是内核提供的用户态驱动程序框架, 而 IGB_UIO 是 DPDK 用于与 UIO Framework 交换的内核模块,通过 IGB_UIO 来 bind 指定的 PCI 网卡设备给到用户态的 PMD 使用。 IGB_UIO 借助 UIO Framwork 技术来截获中断,并重设中断回调行为,从而绕过内核协议栈后续的处理流程。

  1. IGB_UIO 主要负责什么内容 ?
    (1.1) igb_uio 内核模块功能之一、注册一个 PCI 设备。通过 DPDK 提供的 Python 脚本 dpdk-devbind 来完成, 当执行 dpdk-devbind 来 bind 网卡时,会通过 sysfs 与内核交互、让内核使用指定的驱动程序(e.g. igb_uio) 来绑定网卡。
    在 linux 中设备与驱动绑定方法有两种:
    a、 配置设备, 让设备选择驱动,向 /sys/bus/pci/devices/{pci id}/driver_override 写入驱动的名称。
    b、 配置驱动,让其支持新的 PCI 设备,向 /sys/bus/pci/drivers/igb_uio/new_id 写入要bind的网卡设备的 PCI ID (e.g. 8086 10f5, 格式:设备厂商号 设备号)。
    这两种方式都会促使驱动程序 bind 新的网卡设备,而 DPDK 使用的 第二种 方式。
    (1.2) igb_uio 内核模块另一个主要功能是、让用户态的 PMD 网卡驱动程序与 UIO 及格线交互:
    a、 调用 igbuio_setup_bars,设置 uio_info的 uio_mem 、 uio_port 和其他成员;
    b、 调用 uio_register_device, 注册 UIO 设备;
    c、打开 UIO 设备并注册中断;
    d、调用 uio_event_notify, 将注册的 UIO 设备的 “内存空间” 映射到用户态的应用空间;其 mmap 函数为uio_mmap, 至此、UIO 就可以让 PMD 驱动程序在用户态应用程序访问设备的大部分资源。
    e、应用层 UIO 初始化,同时,DPDK 还需要把 PCI 设备的 BAR 以上到应用层; 在 pci_uio_map_resource 函数中、会调用pci_uio_map_resource_by_index 做资源映射。
    f、在 PMD 驱动程序中, DPDK 应用程序会调用 rte_eth_rx_burst 读取数据报文; 如果网卡接收 Buffer 的描述符表示已经完成一个报文的接收(如有 E1000_RXD_STAT_DD标志),则 rte_mbuf_raw_alloc 一个 mbuf 进行处理。

  2. IGB_UIO 是如何注册PCI 设备的 ?
    Linux 中的 pci 设备
    在这里插入图片描述
    config: PCI 设备的配置空间,二进制,可读写;
    device: PCI Device ID ,只读、PCI 的设备标识;
    vendor:PCI vendor ID, 比如、intel 厂家编号 0x8086 ;
    driver: 为 PCI 设备的驱动目录的软连接,真正的目录位于 /sys/bus/pci/drivers/ 下,由上图可见当前这个 PCI 设备使用的是 igb_uio 驱动。
    enable: 设备是否正常使能,可读写;
    irq : 被分配的中断号,只读;
    local_cpulist: 这个网卡的内存空间位于和同处于一个 NUMA 节点上的 CPUs 由哪些,只读;协助 NUMA 亲和性的实现;
    local_cpu: 和 local_cpulist 的作用一样,是以掩码的方式给出,只读;
    numa_node: 说明 PCI 设备属于哪一个 NUMA Node, 只读;
    resource: PCI 设备的 BAR 记录,只读;
    resource0 … N:某一个 PCI BAR 空间,二进制,只读;可进行 mmap() 映射,如果用户进程需要操作 PCI 设备、就必须通过 mmap() 这个 resource0 … N 。
    sriov_numfs: 用于设定 SR-IOV 网卡的 VF 数量。
    sriov_totalvfs:作用与sriov_numfs 相同,设定这个PCI 设备共可以申请多少个 VF 。
    subsystem_device: PCI 子系统设备 ID , 只读;
    subsystem_vendor: PCI 子系统生成商 ID , 只读。

  3. IGB_UIO 是如何获得 PCI 的 Memory BAR ?
    程序要操作一个外设、首先需要找到他的寄存器并对其进行配置,而找到寄存器的前提、是拿到外设的基地址,即:通过 " 基地址 + 寄存器偏移量 " 就能找到寄存器所在的地址,然后可以配置了。

下图是 PCI 总线设备的配置空间,属于 PCI Bus 的基本规范。其中、基地址 (Base Address Registers , BAR ) 是最重要的部分,在 0x0010 ~0x0028 这 24 bytes 中,分布着 6 个 PCI BAR ,PCI 设备配置空间的信息,在系统启动时,就以及被解析完成了,并以文件系统的方式、供用户态程序读取。
在这里插入图片描述
我们在此以 PCI 网卡 intel 82599 设备为例,说明 BAR 空间的内容作用,
在这里插入图片描述
(1) Memory BAR: 内存 BAR ,这块空间位于的内存空间、这点内存空间在通过 mmap() 映射后,用户进程即可以直接访问的空间。
(2). I/O BAR : IO BAR 空间位于IO空间,用户进程对其的访问、不能向 Memory BAR 那样 mmap() 映射后、即可直接访问。 在 X86 体系架构下、外设是进行独立编址的,所以使用 in、out 专用IO指令访问。
(3). MSI-X BAR : 这个 BAR 空间注意是用来配置 MSI-X 中断向量。
PCI 的配置空间内容,就如下图所示:
在这里插入图片描述
我们再看一款 PCI 网卡 I350 设备的 BAR 空间分布, 此网卡支持 32 bit 和 64 bit 两种模式,datasheet 说明如下:

在这里插入图片描述

PCI 设备 BAR 内容 是 CPU 与 外设之间交互信息依据,此部分内容可以参考《 PCIe 设备驱动详解 》 内容部分描述。

  1. IGB_UIO 注册 UIO 设备、挂载驱动过程 ( 网卡绑定 )
    igb_uio.ko 内核模块注册一个 PCI 设备,igbuio_pci_driver 对应保存的 PCI 设备信息的 id_table 指针初始化为空的,这样再内核注册此 PCI 设备时, 就会找不到匹配的设备, 也就不会调用 igb_uio 驱动中的 probe() 来探测 PCI 设备,只是单纯的在 /sys 目录下创建了 igb_uio 具体的目录。
    所以,实际上加载 igb_uio,ko 模块到内核时, igb_uio 的 probe 探测不到 PCI 设备的,而是在执行 Python 脚本 dpdk_nic_bind.py 的时候完成。

(5.1) 获取脚本执行参数指定的网卡eth1 设备的 PCI 信息,实际执行指令 lspci -Dvmmn 查看,注意关注 slot、Vendor ID 和 Device ID 信息。

Slot: 0000:06:00.1
Class: 0200
Vendor: 8086
Device: 1521
SVendor: 15d9
SDevice: 1521
Rev: 01

(5.2) unbind 网卡设备之前的 igb 模块, 将 Step 1 中获取到的 eth1 对应的 slot 信息 0000:06:00.1 值写入 igb 驱动的 unbind 文件。
e.g. echo 0000:06:00.1 > /sys/bus/pci/drivers/igb/unbind , 从内核diamond分享此动作,就是将 igb 模块信息和 该 PCI 设备关联。

(5.3) bind 网卡设备到新的 igb_uio 模块, 将 eth1 的 Vendor 和 Device ID 信息写入 igb_uio 驱动的 new_id 文件。
e.g. echo 0x8086 0x1521 > /sys/bus/pci/drivers/igb_uio/new_id 。

当执行 bind 网卡时、通过 sysfs 与内核交换,通知内核查找 platform 驱动程序来匹配指定网卡设备,此过程即触发内核进行驱动程序匹配更新过程。

用户空间通过修改 sysfs 中的文件来修改设备属性值,上述 new_id、driver_override、bind、unbind 文件对应 igb_uio 驱动的各属性值,new_id 主要就是将 PCI 加入值驱动动态设备 ID 列表中 ( 平台 PCI bus 总线链表中),供驱动设备 probe 时 使用。
在这里插入图片描述
igb_uio.ko 模块驱动程序 probe 函数、所做工作如上图所示,具体内容描述如下:
a、 在操作任何 PCI 设备的寄存器之前, 需要调用 pci_enable_device 函数启动设备,此函数功能:
a.1、 wake up the device if it was in suspended state;
a.2、 allocate I/O and memory regions of the device ( if BOIS did not)
a.3、allocate an IRQ ( if BOIS did not)
b、pci_set_master 设置 PCI COMMAND 寄存器 bus master 位, 启动 DMA 功能。
c、igb_setup_bars 将 BAR 空间导入 uio_info 结构,供用户态 PMD 使用。
d、pci_set_dma_mask 和 pci_set_consistent_dma_mask 设置 DMA 地址掩码位,因为 PCI 设备时有寻址限制的,所以需要标识那些位的地址可以使用。
e、uio_register_device 注册 uioX 设备,此处会创建 /dev/uiox 设备文件供用户空间使用。
f、pci_set_drvdata 设置私有数据。
在完成上述步骤之后、igb_uio 内核模块的 probe 函数执行了,也就意味着内核为此网卡匹配到驱动程序。

(5.4) 记录设备的资源
igb_uio 驱动会遍历该 PCI 设备的 BAR 空间,把存储器空间 IORESOURCE_MEM 的 BAR 内容、物理地址、大小等信息保存到 uio_info 结构的 mem 数组中, 把寄存器空间 IORESOURCE_IO 的 BAR 内容,将其物理地址、大小等信息保存到 uio_info 结构的 port 数组中。
在这里插入图片描述
用户态可以获取到 uio_info 信息,可以获取网卡相关数据。

(5.5) 注册中断函数 irqhandler
UIO Framework 在内核态实现中断处理相关的初始化工作,如 igbuio_pci_probe 的代码片段:

* fill uio infos */  
udev->info.name = "igb_uio"; 
udev->info.version = "0.1"; 
udev->info.handler = igbuio_pci_irqhandler; 
udev->info.irqcontrol = igbuio_pci_irqcontrol;          

注册的 uio 设备名为 igb_uio , 内核态中断处理函数为 igbuio_pci_irqhandler ,中断控制函数 igbuio_pci_irgcontrol 。

switch (igbuio_intr_mode_preferred) {
    
     
	case RTE_INTR_MODE_MSIX:  
		msix_entry.entry =0; 
		if (pci_enable_msix(dev,&msix_entry,1)==0) {
    
                                                                            
			udev->info.irq =msix_entry.vector; 
			udev->mode =RTE_INTR_MODE_MSIX; 
			break; 
		}  

	case RTE_INTR_MODE_LEGACY:  
		if (pci_intx_mask_supported(dev)) {
    
     
			udev->info.irq_flags =IRQF_SHARED; 
			udev->info.irq =dev->irq; 
			udev->mode =RTE_INTR_MODE_LEGACY; 
			break; 
		}

变量 igbuio_intr_mode_preferred 表示中断的模式,它由 igb_uio 驱动的参数 intr_mode 决定, 有 MSI-X 中断和 LEGACY 中断两种模式,默认为 MSI-X 中断模式。
<1> 如果时 MSI-X 中断模式,则调用 pci_enable_msix 函数向 PCI 子系统、申请分配一个 MSI-X 中断,若分配成功就会初始化 uio_info 的 irq 为申请到的中断号。
<2> 如果是传统的 intx 中断模式,则调用 pci_intx_mask_supported 函数读取 PCI 配置空间,检查是否支持 intx 中断。

(5.6) 创建 uioX 设备

idev->owner = owner; 
idev->info = info; 

init_waitqueue_head(&idev->wait); 
atomic_set(&idev->event, 0);

idev->dev =device_create(&uio_class,parent,
						 MKDEV(uio_major, idev->minor),
						 idev, 
						 "uio%d",
						 idev->minor); 

ret =uio_dev_add_attributes(idev); 
info->uio_dev =idev; 

if (info->irq &&(info->irq !=UIO_IRQ_CUSTOM)) {
    
     
	ret =devm_request_irq(idev->dev,info->irq,uio_interrupt,                                                      
	info->irq_flags,info->name,idev); 
}  

<1> 初始化 uio_device 结构体指针 idev , 主要是包括等待队列 wait、中断事件计数 event、 次设备号 minor 等。
<2> 在 /dev 模块下创建 uio 设备, 设备名为 uio%d, %d为此设备号 minor 。

$ ls -l /dev/uio*
crw------- 1 root root 243, 0 58 00:18 /dev/uio0

<3> 接着调用 uio_dev_add_attributes 函数在 /sys/class/uio/uioX/ 目录下创建 maps 和 portio 接口。内核创建设备时、根据 uio_info 的 mem内容,为每个寄存器类型的 BAR 创建一个目录。

[root@c-dev ~]# ls -l /sys/class/uio/uio0/maps/map0/
总用量 0
-r--r--r-- 1 root root 4096 58 00:19 addr
-r--r--r-- 1 root root 4096 58 00:19 name
-r--r--r-- 1 root root 4096 58 00:19 offset
-r--r--r-- 1 root root 4096 58 00:19 size
[root@c-dev ~]# ls -l /sys/class/uio/uio0/maps/map1/
总用量 0
-r--r--r-- 1 root root 4096 58 00:19 addr
-r--r--r-- 1 root root 4096 58 00:19 name
-r--r--r-- 1 root root 4096 58 00:19 offset
-r--r--r-- 1 root root 4096 58 00:19 size

<4>、 中断函数 uio_interrupt 如下:

static irqreturn_t uio_interrupt(intirq,void *dev_id) 
{
    
     
    struct uio_device *idev =(struct uio_device *)dev_id;                                                           
    irqreturn_t ret =idev->info->handler(irq,idev->info); 
    
    if (ret==IRQ_HANDLED) 
        uio_event_notify(idev->info); 
    return ret; 
} 

此函数首先调用在 igb_uio 驱动中、设置的中断处理函数 igbuio_pci_irqhandler 来检查中断是否为此设备的中断,如果是就返回 IRQ_HANDLED 、说明此中断是本设备的需要处理,接着调用函数 uio_event_notify 唤醒等待队列 wait 上进程、该进程负责处理该中断。

  • 总结
    <1> igb_uio 负责创建 uio 设备、并加载 igb_uio 驱动,负责将原先被内核驱动接管的网卡转移到 igb_uio 驱动,以此来屏蔽掉原生内核驱动和内核协议栈;
    <2> igb_uio 负责一个桥梁作用、衔接网卡中断信号和用户态网卡驱动;内核态的 igb_uio.ko 接收到中断后、通过事件方式通知用户态的等待进程 uio_event_notify(idev->info) ;
    在这里插入图片描述
    参考连接:
    https://doc.dpdk.org/guides/
    https://doc.dpdk.org/guides/linux_gsg/
    https://is-cloud.blog.csdn.net/article/details/106007926

猜你喜欢

转载自blog.csdn.net/weixin_38387929/article/details/115095108
1.2