本文基于DPDK-16.07.2、Linux 4.4.2分析总结

1 简介

1.1 什么是UIO技术
UIO(Userspace I/O)是运行在用户空间的I/O技术,Linux系统中一般的驱动设备都是运行在内核空间,而在用户空间用应用程序调用即可,而UIO则是将驱动的很少一部分运行在内核空间,而在用户空间实现驱动的绝大多数功能,使用UIO可以避免设备的驱动程序需要随着内核的更新而更新的问题。

1.2 为什么出现了UIO
硬件设备可以根据功能分为网络设备,块设备,字符设备,或者根据与CPU相连的方式分为PCI设备,USB设备等。它们被不同的内核子系统支持。这些标准的设备的驱动编写较为容易而且容易维护。很容易加入主内核源码树。但是,又有很多设备难以划分到这些子系统中,比如I/O卡,现场总线接口或者定制的FPGA。通常这些非标准设备的驱动被实现为字符驱动。这些驱动使用了很多内核内部函数和宏。而这些内部函数和宏是变化的。这样驱动的编写者必须编写一个完全的内核驱动,而且一直维护这些代码。而且这些驱动进不了主内核源码。于是就出现了用户空间I/O框架(Userspace I/O framework)。

1.3 UIO是怎么工作的
一个设备驱动的主要任务有两个:

  1. 存取设备的内存
  2. 处理设备产生的中断
    对于第一个任务,UIO 核心实现了mmap()可以处理物理内存(physical memory),逻辑内存(logical memory), 虚拟内存(virtual memory)。UIO驱动的编写是就不需要再考虑这些繁琐的细节。
    第二个任务,对于设备中断的应答必须在内核空间进行。所以在内核空间有一小部分代码用来应答中断和禁止中断,但是其余的工作全部留给用户空间处理。如果用户空间要等待一个设备中断,它只需要简单的阻塞在对 /dev/uioX的read()操作上。当设备产生中断时,read()操作立即返回。UIO也实现了poll()系统调用,可以使用 select()来等待中断的发生。select()有一个超时参数可以用来实现有限时间内等待中断。对设备的控制还可以通过/sys/class/uio下的各个文件的读写来完成。注册的uio设备将会出现在该目录下。假如uio设备是uio0那么映射的设备内存文件出现在/sys/class/uio/uio0/maps/mapX,对该文件的读写就是对设备内存的读写。

1.4 UIO框架分层
uio代码相关的可以分为三个部分:内核uio框架及内核内部函数,uio内核驱动部分,uio用户驱动部分。
1)内核uio框架通过配置内核选项CONFIG_UIO=y使能Userspace I/O drivers,在内核初始化时会调用uio_init创建uio_class;
2)igb_uio内核驱动通过编译运行igb_uio.ko加载并注册一个pci设备,但是igbuio_pci_driver对应的保存pci设备信息的id_table指针为空,这样在内核注册此pci设备时,会找不到匹配的设备,就不会调用igb_uio驱动中的探测probe函数uio用户态驱动,在运行dpdk提供的Python脚本dpdk-devbind.py绑定网卡设备后才会执行其probe函数;
3)uio用户态驱动则是在dpdk实例程序(如l2fwd)初始化EAL环境抽象层时才会进行驱动与设备匹配加载。

从图中可以看出,用户空间下的驱动程序比运行在内核空间的驱动要多得多,UIO框架下运行在内核空间的驱动程序所做的工作很简单,常做的只有两个:分配和记录设备需要的资源和注册uio设备,和必须在内核空间实现的小部分中断应答函数。

2 内核UIO框架总结

2.1 UIO的内核使能及初始化
配置内核UIO相关选项使能,内核编译相关代码并在内核启动时初始化UIO内核核心框架;




执行流程:申请字符设备号(alloc_chrdev_region),设备(cdev_alloc),并注册到系统中(cdev_add),注册uio_class到系统中(class_register),创建“/sys/class/uio”,此时该目录为空,在insmod igb_uio.ko后且运行python脚本绑定网卡后此目录下才有内容,见3.2.2。

2.2 UIO重要数据结构


uio核心部分是一个名为"uio"的字符设备(下文称为“uio核心字符设备“)。用户驱动的内核部分(igb_uio.ko)使用uio_register_device向uio核心部分注册uio设备。uio核心的任务就是管理好这些注册的uio设备。这些uio设备使用的数据结构是uio_device。而这些设备属性,比如name, open(), release()等操作都放在了uio_info结构中,用户使用 uio_register_device注册这些驱动之前要设置好uio_info。
uio核心字符设备注册duio_open ,uio_fasync, uio_release, uio_poll, uio_read ,uio_write中除了完成相关的维护工作外,还调用了注册在uio_info中的相关方法。比如,在 uio_open中调用了uio_info中注册的open方法。
在uio_init()—init_uio_class()—uio_major_init()中完成uio核心字符设备操作函数挂载(cdev->ops = &uio_fops;)

uio核心和uio设备之间的关系图如下:

2.3 UIO设备注册接口
在用户驱动的内核部分igbuio_pci_probe()调用uio_register_device()(内部调用了__uio_register_device)注册uio设备时,在__uio_register_device中调用了uio_get_minor函数,在uio_get_minor函数中,利用idr机制(idr_get_new)建立了次设备号(整数ID)和uio_device类型指针之间的联系。而uio_device指针指向了代表注册的uio设备的内核结构。device_create()调用完毕后在 /sys/class/uio/下就会出现代表uio设备的uioX文件夹,其中X为uio设备的次设备号。


执行流程:申请uio_device指针(devm_kzalloc),初始化设备等待队列(init_waitqueue_head),清空中断事件计数器(&idev->event, 0),映射次设备号(uio_get_minor),创建设备并关联uio_class(device_create),初始化设备中断(uio_interrupt);

2.4 UIO设备开启接口
在uio_open中先取得了设备的次设备号(iminor(inode)),再次利用idr机制提供的方法(idr_find)取得了对应的uio_device类型的指针。并且把该指针保存在了uio_listener结构中,以方便以后使用。

执行流程:根据次设备号获取设备指针(idr_find),分配并初始化uio_listener结构,调用内部open函数。

2.4 UIO设备中断注册
在__uio_register_device中,为uio设备注册了统一的中断处理函数uio_interrupt,在该函数中调用了uio设备自己提供的中断处理函数handler(uio_info结构中)。并调用了uio_event_notify函数对uio设备的中断事件计数器增一, 通知各个读进程“有数据可读”。每个uio设备的硬件中断处理函数都是单独注册的。



对于每一个注册的uio设备(uio_device)都关联一个这样的结构。它的作用就是跟踪每个uio设备(uio_device)的中断事件计数器值。在用户空间进行文件打开操作(open)时,与uio设备关联的uio_listener结构就被分配(uio_open中),指向它的指针被保存在filep指针的private_data字段以供其他操作使用。

uio_poll 操作判断是否有数据可读的依据就是 listener中的中断事件计数值(event_count)和uio设备中的中断事件计数器值不一致(前者小于后者)。因为listener的值除了在执行文件打开操作时被置为被赋值外,只在uio_read操作中被更新为uio设备的中断事件计数器值。
如果用户空间要等待一个设备中断,它只需要简单的阻塞在对 /dev/uioX的read()操作上(对应内核uio_read)。当设备产生中断时,read()操作立即返回。UIO也实现了poll()系统调用(对应内核uio_poll),可以使用 select()来等待中断的发生。select()有一个超时参数可以用来实现有限时间内等待中断。

3 UIO内核驱动部分总结

3.1 UIO内核驱动编译
DPDK在编译生成igb_uio.ko时需要依赖于内核开发包kernel-devel,主要是调用内核接口函数(pci_enable_device、uio_register_device等)和头文件等,如此更近一步说明此部分驱动属于内核态驱动,实际上就是调用UIO内核核心框架的接口函数;编译时确认下ls /usr/src/kernels/是否有软件包,有软链接/lib/modules/如下:


编译和运行平台不一致时insmod igb_uio.ko时会出现报错,可将运行平台内核源码拷贝到/usr/src/kernels/2.6.32-573.el6.x86_64/重新编译运行。

3.2 UIO内核驱动加载

3.2.1 Insmod igb_uio.ko执行
Igb_uio驱动主要做的就是注册一个pci设备。但是igbuio_pci_driver对应的保存pci设备信息的id_table指针为空,这样在内核注册此pci设备时,会找不到匹配的设备,就不会调用igb_uio驱动中的探测probe函数(对应igb_uio的igbuio_pci_probe()不会被调用到),只会在/sys/bus/pci/drivers/目录下创建Igb_uio相应的目录。


dpdk的uio实现也是按模块加载先赋值intr_mode中断模式,然后初始化一个pci_driver结构体,在igbuio_pci_init_module函数中直接调用linux提供的pci注册API接口函数pci_register_driver(&igbuio_pci_driver);

3.2.2 Python脚本dpdk-devbind.py执行
dpdk提供了一个Python脚本dpdk-devbind.py,可以查看、绑定网卡到igb_uio模块,由于当前防火墙没有python解释器,所以需要单独交叉编译,方法参见《防火墙交叉编译python解释器的方法.txt》;
1)使用方法如下:

2)使用Python脚本dpdk-devbind.py -s查看当前设备状态,当前设备有四块网卡,有一块网卡已被内核驱动,另外三块网卡未驱动,如下:

3)使用Python脚本dpdk-devbind.py --bind=igb_uio eth0/0000:04:00.1
将eth0网卡绑定到igb_uio模块,已驱动的网卡会先卸载驱动(ifconfig eth0消失了),然后重新bind到igb_uio模块。

这时dmesg就会看到igb_uio模块的probe函数执行了(id_table不为空了),也就是意味着扫描到了匹配的pci设备,同时生成/dev/uioX设备(X为次设备号),此时/sys/class/uio/目录下已产生于/dev/uioX设备对应的内容。


3.2.3 Python脚本dpdk-devbind.py解析
经过分析dpdk_nic_bind.py,此脚本文件主要做了以下几步:
1)获取参数指定的网卡eth0的设备信息。使用lspci –Dvmmn查看。

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

可以查看到slot槽位信息、厂商号vendor ID、设备号device ID等信息。
脚本相关内容如下:

2)unbind之前的igb模块。
将前面获取到的eth0对应的slot信息0000:04:00.0值写入igb的unbind文件,卸载内核驱动。

从内核代码分析此unbind的动作就是将igb模块信息和此pci设备Dev去关联。将dev->driver指针置为空,这个很重要。在内核处理pci设备注册的函数中,就算驱动的vendor ID和device ID与设备的都匹配上了,如果此设备的dev->driver指针不为空,也不会调用probe函数的。

3)bind新的igb_uio模块
将eth0设备的vendor和device ID信息写入igb_uio的new_id文件。

内核中处理此步的函数为store_new_id,此函数中是将写入的vendor和device存入到此driver,也就是igb_uio的id_table,然后以此与PCI上的设备进行匹配,这个时候肯定会匹配成功,然后调用igb_uio模块的probe函数(igbuio_pci_probe)进行初始化动作。

3.2.4 igbuio_pci_probe执行
UIO的内核驱动注册与其他驱动类似,通过调用linux提供的uio API接口进行注册,在注册之前所做的主要工作如下:
1)分配一个DPDK封装的UIO设备数据结构,包括了uio_info:


2)使能PCI设备及设置掩码:

3)填充uio_info结构体的信息,主要包括内存大小、类型等信息的填充:

4)映射UIO设备PCI资源空间(PCI设备的物理地址及大小),调用函数为igbuio_setup_bars()—igbuio_setup_bars()—igbuio_pci_setup_iomem(),并且填充uio_info结构体的内存信息:


5)根据填充uio_info结构体的信息注册UIO设备:

填充uio_info结构体的信息,主要包括内存大小、类型、私有结构和中断处理函数等信息。调用UIO内核核心框架的接口函数__uio_register_device()(见2.3 UIO设备注册接口),将uio_info注册到内核中。注册后在/sys/class/uio/uioX,其中X是我们注册的第几个uio设备,比如uio0,在该文件夹下的map/map0会有我们刚才填充的一些信息,包括addr、name、size、offset,其中addr保存的是设备的物理地址,size保存的是地址的大小,这些在用户态会将其读出,并mmap至用户态进程空间,这样用户态便可直接操作设备的内存空间。

6)最后就是注册中断了,初始化中断的中断号、中断模式、中断标志等,并初始化uio_info的handler字段,那么在产生中断时,注册的中断处理函数将会被调用。如果有实际的硬件设备,那么irq应该是硬件设备实际使用的中断号。



这里再看下UIO内核核心框架中注册的中断处理函数uio_interrupt(对应2.4 UIO设备中断注册)。

此函数首先调用igb_uio驱动中设置的中断处理函数igbuio_pci_irqhandler来检查中断是不是此设备的中断,如果是就返回IRQ_HANDLED表示需要处理,接着调用函数uio_event_notify来唤醒等待队列wait上进程来处理中断事宜。

4 UIO用户驱动加载

网卡驱动模型一般包含三层,即PCI总线设备、网卡设备以及网卡设备的私有数据结构,即将设备的共性一层层的抽象,PCI总线设备包含网卡设备,网卡设备又包含其私有数据结构。在DPDK中,首先会注册设备驱动,然后查找当前系统有哪些PCI设备,并通过PCI_ID为PCI设备找到对应的驱动,最后调用驱动初始化设备。

4.1网卡驱动注册
以e1000网卡驱动为例说明。
网卡驱动的注册使用了一种奇技淫巧的方法,使用GCC attribute扩展属性的constructor属性,使得网卡驱动的注册在程序MAIN函数之前就执行了。


其中PMD_REGISTER_DRIVER()宏的定义如下:


使用attribute的constructor属性,在MAIN函数执行前(4.3 初始化PCI驱动时需要遍历使用),就执行rte_eal_driver_register()函数,将pmd_igb_drv驱动挂到全局dev_driver_list链表上,其他的网卡驱动也是使用相同的方式挂到该链表上;

4.2扫描当前系统PCI设备

以为l2fwd的main.c为例,main()调用rte_eal_init()—>rte_eal_pci_init()函数,查找当前系统中有哪些网卡,分别是什么类型,并将它们挂到全局链表pci_device_list上。
1)首先初始化全局链表pci_driver_list、pci_device_list。用于挂载PCI驱动及PCI设备。

2)pci_scan()通过读取/sys/bus/pci/devices/目录下的信息,扫描当前系统的PCI设备,并初始化struct rte_pci_device数据结构,并按照PCI地址从大到小的顺序挂在到pci_device_list链表上,调用关系如下:

Main()----rte_eal_init()----rte_eal_pci_init()----rte_eal_pci_scan()----
pci_scan_one()
----parse_pci_addr_format()----获取并赋值bus、dev、func等信息eal_parse_sysfs_value()----获取并赋值vendor id、device id 等信息pci_parse_sysfs_resource()----获取phys_addr、end_addr、len等信息



在/sys/bus/pci/devices/下显示的PCI设备信息和lspci相同;

vendor文件:获取PCI_ID.vendor_id。
device文件:获取PCI_ID.device_id。
subsystem_vendor文件:获取PCI_ID.subsystem_vendor_id。
subsystem_device文件:获取PCI_ID.subsystem_device_id。
numa_node文件:获取PCI设备属于哪个CPU socket。
resource文件:获取PCI设备的在地址总线上的物理地址,以及物理地址空间的大小,记录在struct rte_pci_resouce数据结构中。

4.3 PCI驱动注册
调用rte_eal_init()—>rte_eal_dev_init()函数,遍历dev_driver_list链表,执行网卡驱动对应的init的回调函数,注册PCI驱动。



这里就用到了之前初始化的dev_driver_list链表:使用attribute的constructor属性,在MAIN函数执行前,就执行rte_eal_driver_register()函数,将pmd_igb_drv驱动挂到全局dev_driver_list链表上。其他的网卡驱动也是使用相同的方式挂到该链表上,此处遍历链表回调各自的init的函数;
以e1000网卡为例,执行的init回调函数就是rte_igb_pmd_init()函数。


rte_eth_driver_register()主要是指定PCI设备的初始化钩子函数devinit,以及注册PCI驱动,将PCI驱动挂到pci_driver_list全局链表上。


其中,rte_igb_pmd数据结构如下,主要是初始化name、id_table等,指定e1000网卡的初始化钩子函数是eth_igb_dev_init()。

其中的pci_id_igb_map保存的是设备厂商的id和设备id;会在后面的驱动和设备匹配时用到;这是e1000网卡的驱动,所以它的私有数据.dev_private_size = sizeof(struct e1000_adapter),保存的是千兆的私有结构体信息;


4.4网卡初始化
调用rte_eal_init()—>rte_eal_pci_probe()函数,遍历pci_device_list和pci_driver_list链表,根据PCI_ID,将pci_device与pci_driver绑定,并调用pci_driver的init回调函数rte_eth_dev_init(),初始化PCI设备。


遍历pci_device_list链表,其中再嵌套遍历pci_driver_list链表;

在rte_eal_pci_probe_one_driver()函数中,判断driver与device是否匹配,若匹配则回调驱动的devinit钩子函数初始化网卡设备;

4.4.1 driver与device的match
在rte_eal_pci_probe_one_driver()函数中,判断driver与device是否匹配:

主要是通过比对PCI_ID的vendor_id、device_id、subsystem_vendor_id、subsystem_device_id四个字段判断pci设备和pci驱动是否匹配。

4.4.2 map resource的资源映射
PCI设备和PCI驱动匹配后,调用rte_eal_pci_map_device()函数为该PCI设备创建map resource。具体如下:
a、首先pci_get_uio_dev读取/sys/bus/pci/devices/PCI设备目录下的uio目录,获取uio设备的ID,该ID就是uio目录名最后几位的数字。当igb_uio模块与网卡设备绑定的时候,会在/sys/bus/pci/devices/对应的PCI设备目录下创建uio目录。如果启动参数中指定了OPT_CREATE_UIO_DEV_NUM,会在/dev目录下创建对应uio设备的设备文件。


b、初始化PCI设备的中断句柄,/* ID为0或1,即uio0或uio1*/。


c、读取/sys/bus/pci/devices/0000:04:00.0/uio/uio0/maps/map0/目录下的文件和/sys/bus/pci/devices/0000:04:00.0/resource文件,获取UIO设备的map resource。并将其记录在struct pci_map数据结构中;



d、将所有UIO设备的resource信息都记录在struct mapped_pci_resource数据结构中,并挂到全局链表pci_res_list上。


4.4.3初始化PCI设备
在driver与device的match成功,并且做完PCI资源空间地址映射后,调用rte_eth_dev_init()初始化PCI设备;


a、首先,组装网口名称,然后调用rte_eth_dev_allocate()在全局数组rte_eth_devices[]中分配一个网卡设备。并在全局数组rte_eth_dev_data[]中为网卡设备的数据域分配内存空间。
网口名字组装:

初始化网口数据结构:

为网卡设备的私有数据结构分配空间:

b、调用eth_igb_dev_init()初始化网卡设备。首先设置网卡设备的操作函数集,以及收包、发包函数。



初始化网卡设备的硬件相关数据结构struct e1000_hw,包括设备ID、硬件操作函数集、在内存地址总线上映射的地址、MAC地址等等。

c、注册中断处理函数。

中断的具体实现在另一篇总结文档再详细介绍;

DPDK网卡驱动流程总结相关推荐

  1. USB 3G网卡驱动流程

    USB 3G网卡驱动流程 简介 首先介绍一下linux下的整体驱动模式: 本文基于的linux kernel版本为2.6.36 (并且华为EM770W驱动,是由FriendlyARM公司定制的. 所以 ...

  2. DPDK — 网卡初始化流程(Intel 82599 ixgbe 网卡驱动示例)

    目录 文章目录 目录 总览 rte_eth_dev/rte_eth_dev_data 数据结构 rte_eth_dev_count 函数 rte_eth_dev_configure 函数 ixgbe_ ...

  3. DPDK — 网卡驱动初始化流程

    目录 文章目录 目录 Intel 82599 ixgbe 网卡驱动示例 rte_eth_dev/rte_eth_dev_data 数据结构 rte_eth_dev_count 函数 rte_eth_d ...

  4. DPDK 网卡驱动学习

    DPDK版本19.02 初始化: /* Launch threads, called at application init(). */ int rte_eal_init(int argc, char ...

  5. DPDK 网卡收包流程

    Table of Contents 1.Linux网络收发包流程 1.1 网卡与liuux驱动交互 1.2  linux驱动与内核协议栈交互 题外1: 中断处理逻辑 题外2:中断的弊端 2.linux ...

  6. dpdk报文收发流程--理解dma控制器、UIO驱动、描述符空间、mbuf空间、KNI

    1. dpdk报文收发流程 1.1 报文接收流程 传统方式接收报文时,当网卡接收到报文后会产生硬件中断,进而报文会通过协议栈,最后到达应用层,这个过程需要内核协议栈的处理. 和传统报文接收不同,当应用 ...

  7. DPDK网卡PMD驱动

    以/home/user/dpdk-stable-18.11.11/drivers/net/i40e目录下的驱动为例 源代码文件有 # ls base i40e_ethdev_vf.c i40e_log ...

  8. matlab 读取voi文件,VOI虚拟化下的Windows网卡驱动加载方法与流程

    本发明涉及计算机技术领域,特别涉及VOI虚拟化下的Windows网卡驱动加载方法. 背景技术: 在VOI虚拟化方案中,首先需要确保网卡驱动能够提前加载到系统中,才能够实现通过网络来引导操作系统启动.现 ...

  9. udp数据报从网卡驱动到用户空间流程总结

    附有相关介绍资料 NAPI驱动流程:     中断发生     -->确定中断原因是数据接收完毕(中断原因也可能是发送完毕,DMA完毕,甚至是中断通道上的其他设备中断)     -->通过 ...

最新文章

  1. 使用Django开发REST 接口
  2. ubuntu 安装pytorch
  3. python访问网页时401_在python上访问带有基本身份验证的网页
  4. android air创建文件夹,安卓版Airdrop将上线:无需安装APP,轻松实现文件隔空投送...
  5. mysql 性能 比较好_MySQL性能优化的最佳20+条经验
  6. java rabbitmq topic_java rabbitmq 发送消息是topic模式, 消费者 怎么消费多个不同名字的队列?...
  7. 关于“因为数据库正在使用,所以无法获得对数据库的独占访问权”的最终解决方案...
  8. ZOJ 3735 Josephina and RPG
  9. Hello Blazor:(14)CSS隔离
  10. 天融信安全接入客户端_天融信提示您警惕物联网设备Ripple20漏洞风险
  11. timer.schedule定时器用法
  12. 关联Left Outer Join的第一条记录
  13. Report Service中报 RSClientController 未定义
  14. 安卓期末大作业(AndroidStudio开发),日记本app,代码注释详细,能正常运行
  15. PHP高并发场景的三种解决方案
  16. 2022最新Android项目导入过程(以Android studio2021.2.1为例)
  17. Home Assistant 家庭助理安装
  18. torch.nn.Embedding(num_embeddings, embedding_dim)的理解
  19. python进行批量图片文字识别
  20. 有功功率,无功功率,视在功率定义

热门文章

  1. 仙侠情缘传java下载_仙侠情缘传_仙侠情缘传app下载_仙侠情缘传安卓下载安装_好趣手游网...
  2. 蓝桥杯 试题 算法训练 逗志芃的危机
  3. 太狠了!佐治亚理工取消终身教授制度,启动全「末位淘汰」
  4. 什么是用户story?
  5. 【已解决】Appium+Java控制真机(小米12X),运行Appium Inspector或Java测试脚本时报错Error executing adbExec
  6. 马政委是怎样炼成的?
  7. 【普通人题解】LeetCode 纸牌游戏
  8. 火车票余票接口和火车票接口查询出来喽
  9. 【GTB-2022】First dubugging under coach‘s instruction
  10. 简单理解 GameFi:游戏行业的“范式转变”