一、dpdk uio驱动框架


在系统加载igb_uio驱动后,每当有网卡和igb_uio驱动进行绑定时, 就会在/dev目录下创建一个uio设备,例如/dev/uio1。uio设备是一个接口层,用于将pci网卡的内存空间以及网卡的io空间暴露给应用层。通过这种方式,应用层访问uio设备就相当于访问网卡。具体来说,当有网卡和uio驱动绑定时,被内核加载的igb_uio驱动, 会将pci网卡的内存空间,网卡的io空间保存在uio目录下,例如/sys/class/uio/uio1/maps文件中,同时也会保存到pci设备目录下的uio文件中。这样应用层就可以访问这2个文件中的任意一个文件里面保存的地址空间,然后通过mmap将文件中保存网卡的物理内存映射成虚拟地址, 应用层访问这个虚拟地址空间就相当于访问pci设备。

从图中可以看出,一共由用户态驱动pmd, 运行在内核态的igb_uio驱动,以及linux的uio框架组成。用户态驱动pmd通过轮询的方式,直接从网卡收发报文,将内核旁路了,绕过了协议栈,避免了内核和应用层之间的拷贝性能; 内核态驱动igb_uio,用于将pci网卡的内存空间,io空间暴露给应用层,供应用层访问,同时会处理在网卡的硬件中断;linux uio框架提供了一些给igb_uio驱动调用的接口,例如uio_open打开uio; uio_release关闭uio; uio_read从uio读取数据; uio_write往uio写入数据。linux uio框架的代码在内核源码drivers/uio/uio.c文件中实现。linux uio框架也会调用内核提供的其他api接口函数。

应用层pmd通过read系统调用来访问/dev/uiox设备,进而调用igb_uio驱动中的接口, igb_uio驱动最终会调用linux uio框架提供的接口。

二、用户态驱动pmd轮询与uio中断的关系

pmd用户态驱动是通过轮询的方式,直接从网卡收发报文,将内核旁路了,绕过了协议栈。那为什么还要实现uio呢? 在某些情况下应用层想要知道网卡的状态信息之类的,就需要网卡硬件中断的支持。因为硬件中断只能在内核上完成, 目前dpdk的实现方式是在内核态igb_uio驱动上实现小部分硬件中断,例如统计硬件中断的次数, 然后唤醒应用层注册到epoll中的/dev/uiox中断,进而由应用层来完成大部分的中断处理过程,例如获取网卡状态等。

有一个疑问,是不是网卡报文到来时,产生的硬件中断也会到/dev/uiox中断来呢? 肯定是不会的, 因为这个/dev/uiox中断只是控制中断,网卡报文收发的数据中断是不会触发到这里来的。为什么数据中断就不能唤醒epoll事件呢,dpdk是如何区分数据中断与控制中断的? 那是因为在pmd驱动中,调用igb_intr_enable接口开启uio中断功能,设置中断的时候,是可以指定中断掩码的, 例如指定E1000_ICR_LSC网卡状态改变中断掩码,E1000_ICR_RXQ0接收网卡报文中断掩码; E1000_ICR_TXQ0发送网卡报文中断掩码等。 如果某些掩码没指定,就不会触发相应的中断。dpdk的用户态pmd驱动中只指定了E1000_ICR_LSC网卡状态改变中断掩码,因此网卡收发报文中断是被禁用掉了的,只有网卡状态改变才会使得epoll事件触发。因此当有来自网卡的报文时,产生的硬件中断是不会唤醒epoll事件的。这些中断源码在e1000_defines.h文件中定义。

另一个需要注意的是,igb_uio驱动在注册中断处理回调时,会将中断处理函数设置为igbuio_pci_irqhandler,也就是将正常网卡的硬件中断给拦截了, 这也是用户态驱动pmd能够直接访问网卡的原因。得益于拦截了网卡的中断回调,因此在中断发生时,linux uio框架会唤醒epoll事件,进而应用层能够读取网卡中断事件,或者对网卡进行一些控制操作。拦截硬件中断处理回调只是对网卡的控制操作才有效, 对于pmd用户态驱动轮询网卡报文是没有影响的。也就是说igb_uio驱动不管有没拦截硬件中断回调,都不影响pmd的轮询。 劫持硬件中断回调,只是为了应用层能够响应硬件中断,并对网卡做些控制操作。

三、dpdk uio驱动的实现过程

先来整体看下igb_uio驱动做了哪些操作。

(1) 针对uio设备本身的操作,例如创建uio设备结构,并注册一个uio设备。此时将会在/dev/目录下创建一个uio文件,例如/dev/uiox。同时也会在/sys/class/uio目录下创建一个uio目录,例如/sys/class/uio/uio1; 并将这个uio目录拷贝到网卡目录下,例如/sys/bus/pci/devices/0000:02:06.0/uio。

(2) 为pci网卡预留物理内存与io空间,同时将这些空间保存到uio设备上,相当于将这些物理空间与io空间暴露给uio设备。应用层访问uio设备就相当于访问网卡设备

(3) 在idb_uio驱动注册硬件中断回调, 驱动层的硬件中断代码越少越好,大部分硬件中断由应用层来实现。

1、igb_uio驱动初始化

在执行insmod命令加载igb_uio驱动时,会进行uio驱动的初始化操作, 注册一个uio驱动到内核。注册uio驱动的时候,会指定一个驱动操作接口igbuio_pci_driver,其中的probe是在网卡绑定uio驱动的时候 ,uio驱动探测到有网卡进行绑定操作,这个时候probe会被调度执行; 同理当网卡卸载uio驱动时,uio驱动检测到有网卡卸载了,则remove会被调度执行。

static struct pci_driver igbuio_pci_driver =
{.name = "igb_uio",.id_table = NULL,.probe = igbuio_pci_probe, //为pci设备绑定uio驱动时会被调用.remove = igbuio_pci_remove,//为pci设备卸载uio驱动时会被调用
};
//igb_uio驱动初始化
static int __init igbuio_pci_init_module(void)
{return pci_register_driver(&igbuio_pci_driver);
}

2、驱动探测probe

上面已经提到过这个接口被调用的时间,也就是在网卡绑定igb_uio驱动的时候会被调度执行,现在来分析下这个接口的执行过程。需要注意的是,这个接口内部调用了linux uio框架的接口以及调用了一堆linux内核的api接口, 读者在分析这部分代码的时候,关注重点流程就好了,不要被内核的这些接口干扰。

2.1 激活pci设备

在igb_uio驱动能够操作pci网卡之前,需要将pci设备给激活, 唤醒pci设备。在驱动程序可以访问PCI设备的任何设备资源之前(I/O区域或者中断),驱动程序必须调用该函数。也就是说只有激活了pci设备, igb_uio驱动以及应用层调用者,才能够访问pci网卡的内存或者io空间。

int igbuio_pci_probe(struct pci_dev *dev, const struct pci_device_id *id)
{//激活PCI设备,在驱动程序可以访问PCI设备的任何设备资源之前(I/O区域或者中断),驱动程序必须调用该函数err = pci_enable_device(dev);
}
int pci_enable_device(struct pci_dev *dev)
{//使得驱动能够访问pci设备的内存与io空间return __pci_enable_device_flags(dev, IORESOURCE_MEM | IORESOURCE_IO);
}

2.2 为pci设备预留内存与io空间

igb_uio驱动会根据网卡目录下的resource文件, 例如/sys/bus/pci/devices/0000:02:06.0文件记录的io空间的大小,开始位置; 内存空间的大小,开始位置。在内存中为pci设备预留这部分空间。分配好后空间后,这个io与内存空间就被该pci网卡独占,应用层可以通过访问/dev/uiox设备,其实也就是访问网卡的这部分空间。

int igbuio_pci_probe(struct pci_dev *dev, const struct pci_device_id *id)
{//在内存中申请pci设备的内存区域err = pci_request_regions(dev, "igb_uio");
}
int _kc_pci_request_regions(struct pci_dev *dev, char *res_name)
{int i;//根据网卡目录下的/sys/bus/pci/devices/0000:02:06.0文件记录的网卡内存与io空间的大小,在内存中申请这些空间for (i = 0; i < 6; i++) {if (pci_resource_flags(dev, i) & IORESOURCE_IO) {//在内存中为网卡申请io空间request_region(pci_resource_start(dev, i), pci_resource_len(dev, i), res_name);}else if (pci_resource_flags(dev, i) & IORESOURCE_MEM) {//在内存中为网卡申请物理内存空间request_mem_region(pci_resource_start(dev, i), pci_resource_len(dev, i), res_name);}}
}

2.3 为pci网卡设置dma模式

将网卡设置为dma模式, 用户态pmd驱动就可以轮询的从dma直接接收网卡报文,或者将报文交给dma来发送

int igbuio_pci_probe(struct pci_dev *dev, const struct pci_device_id *id)
{//设置dma总线模式,使得pmd驱动可以直接从dma收发报文pci_set_master(dev);//设置可以访问的地址范围0-64地址空间err = pci_set_dma_mask(dev,  DMA_BIT_MASK(64));err = pci_set_consistent_dma_mask(dev, DMA_BIT_MASK(64));
}

2.4 将pci网卡的物理空间以及io空间暴露给uio设备

将pci网卡的物理内存空间以及io空间保存在uio设备结构struct uio_info中的mem成员以及port成员中,uio设备就知道了网卡的物理以及io空间。应用层访问这个uio设备的物理空间以及io空间,就相当于访问pci设备的物理以及io空间。本质上就是将pci网卡的空间暴露给uio设备。

int igbuio_pci_probe(struct pci_dev *dev, const struct pci_device_id *id)
{//将pci内存,端口映射给uio设备struct rte_uio_pci_dev *udev;err = igbuio_setup_bars(dev, &udev->info);
}
static int igbuio_setup_bars(struct pci_dev *dev, struct uio_info *info)
{//pci内存,端口映射给uio设备for (i = 0; i != sizeof(bar_names) / sizeof(bar_names[0]); i++) {if (pci_resource_len(dev, i) != 0 && pci_resource_start(dev, i) != 0) {flags = pci_resource_flags(dev, i);if (flags & IORESOURCE_MEM) {//暴露pci的内存空间给uio设备ret = igbuio_pci_setup_iomem(dev, info, iom,  i, bar_names[i]);} else if (flags & IORESOURCE_IO) {//暴露pci的io空间给uio设备ret = igbuio_pci_setup_ioport(dev, info, iop,  i, bar_names[i]);}}}
}

将pci设备的物理内存空间以及io空间保存在uio设备结构struct uio_info中的mem成员以及port成员中,之后在下面调用uio_register_device注册一个uio设备时。内部就将mem以及port成员保存的信息分别保存到/sys/class/uio/uiox目录下的maps以及portio, 这样应用层访问这两个目录里面文件记录的内容,就可以访问的pci设备的物理以及地址空间,真正的暴露给应用层。

可以简单查看内核的源码,__uio_register_device会调用uio_dev_add_attributes接口来完成将网卡的物理内存空间以及io空间保存到文件中

static int uio_dev_add_attributes(struct uio_device *idev)
{for (mi = 0; mi < MAX_UIO_MAPS; mi++) {//将pci物理内存保存到/sys/class/uio/uio1/maps目录下mem = &idev->info->mem[mi];idev->map_dir = kobject_create_and_add("maps", &idev->dev->kobj);}for (pi = 0; pi < MAX_UIO_PORT_REGIONS; pi++) {//将pci设备io空间保存到/sys/class/uio/uio1/portio目录下port = &idev->info->port[pi];idev->portio_dir = kobject_create_and_add("portio", &idev->dev->kobj);}
}

2.5 设置uio设备的中断

注册uio设备的中断回调,也就是上面提到的拦截硬件中断回调。拦截硬件中断后,当硬件中断触发时,就不会一直触发内核去执行中断回调。也就是通过这种方式,才能在应用层实现硬件中断处理过程。再次注意下,这里说的中断仅是控制中断,而不是报文收发的数据中断,数据中断是不会走到这里来的,因为在pmd开启中断时,没有设置收发报文的中断掩码,只注册了网卡状态改变的中断掩码。

int igbuio_pci_probe(struct pci_dev *dev, const struct pci_device_id *id)
{//填充uio信息udev->info.name = "igb_uio";udev->info.version = "0.1";udev->info.handler = igbuio_pci_irqhandler;        //硬件控制中断的入口,劫持原来的硬件中断udev->info.irqcontrol = igbuio_pci_irqcontrol;  //应用层开关中断时被调用,用于是否开始中断
}
static irqreturn_t igbuio_pci_irqhandler(int irq, struct uio_info *info)
{if (udev->mode == RTE_INTR_MODE_LEGACY && !pci_check_and_mask_intx(udev->pdev)){return IRQ_NONE;}//返回IRQ_HANDLED时,linux uio框架会唤醒等待uio中断的进程。注册到epoll的uio中断事件就会被调度/* Message signal mode, no share IRQ and automasked */return IRQ_HANDLED;
}
static int igbuio_pci_irqcontrol(struct uio_info *info, s32 irq_state)
{//调用内核的api来开关中断if (udev->mode == RTE_INTR_MODE_LEGACY){pci_intx(pdev, !!irq_state);}else if (udev->mode == RTE_INTR_MODE_MSIX)\{list_for_each_entry(desc, &pdev->msi_list, list)igbuio_msix_mask_irq(desc, irq_state);}
}

在下面调用uio_register_device注册uio设备的时候,会注册一个linux uio框架下的硬件中断入口回调uio_interrupt。这个回调里面会调用igb_uio驱动注册的硬件中断回调igbuio_pci_irqhandler。通常igbuio_pci_irqhandler直接返回IRQ_HANDLED,因此会唤醒阻塞在uio设备中断的进程,应用层注册到epoll的uio中断事件就会被调度,例如e1000用户态pmd驱动在eth_igb_dev_init函数中注册的uio设备中断处理函数eth_igb_interrupt_handler就会被调度执行,来获取网卡的状态信息。

总结下中断调度流程:linux uio硬件中断回调被触发 ----> igb_uio驱动的硬件中断回调被调度 ----> 唤醒用户态注册的uio中断回调。

int __uio_register_device(struct module *owner,struct device *parent, struct uio_info *info)
{//注册uio框架的硬件中断入口ret = request_irq(idev->info->irq, uio_interrupt,idev->info->irq_flags, idev->info->name, idev);
}
static irqreturn_t uio_interrupt(int irq, void *dev_id)
{struct uio_device *idev = (struct uio_device *)dev_id;//调度igb_uio驱动注册的中断回调irqreturn_t ret = idev->info->handler(irq, idev->info);//唤醒所有阻塞在uio设备中断的进程,注册到epoll的uio中断事件就会被调度if (ret == IRQ_HANDLED)uio_event_notify(idev->info);
}

2.6 uio设备的注册

最后执行uio设备的注册,在/dev/目录下创建uio文件,例如/dev/uio1; 同时也会在/sys/class/uio目录下创建一个uio目录,例如/sys/class/uio/uio1; 最后将/sys/class/uio/uio1目录下的内容拷贝到网卡目录下,例如/sys/bus/pci/devices/0000:02:06.0/uio

另外还会执行上面提到过的,将uio设备保存的网卡的内存空间,io空间保存到文件中,以便应用层能够访问这个网卡空间。同时也会注册一个linux uio框架下的网卡硬件中断。

int __uio_register_device(struct module *owner,struct device *parent, struct uio_info *info)
{//创建uio设备/dev/uioxidev->dev = device_create(uio_class->class, parent,MKDEV(uio_major, idev->minor), idev, "uio%d", idev->minor);//将uio设备保存的网卡的内存空间,io空间保存到文件中ret = uio_dev_add_attributes(idev);//注册uio框架的硬件中断入口ret = request_irq(idev->info->irq, uio_interrupt,idev->info->irq_flags, idev->info->name, idev);
}

到此为止uio设备驱动与pmd的关系也就分析完成了。接下来的文章将详细分析用户态驱动pmd是如何收发网卡报文的。

原文链接:https://blog.csdn.net/ApeLife/article/details/100751359

DPDK uio驱动实现(二十)相关推荐

  1. 嵌入式Linux驱动笔记(二十四)------framebuffer之使用spi-tft屏幕(上)

    你好!这里是风筝的博客, 欢迎和我一起交流. 最近入手了一块spi接口的tft彩屏,想着在我的h3板子上使用framebuffer驱动起来. 我们知道: Linux抽象出FrameBuffer这个设备 ...

  2. DPDK pci驱动探测(十八)

    上一篇文章已经介绍了pci设备的背景知识, 现在我们来分析下pci设备是如何探测到支持的驱动,进而与驱动进行关联:pci与驱动的解除绑定:pci设备与uio设备的关联. 一.pci驱动注册 网卡驱动的 ...

  3. DPDK KNI实现(二十五)

    一.为什么要用kni 通常情况下dpdk用于二三层报文转发,接收到来自网卡的报文后,如果是二层报文则查找fdb表: 如果是三层报文,则进行dnat, snat处理后,查找路由表, 将报文转发给下一跳路 ...

  4. i.MX 6ULL 驱动开发 二十九:向 Linux 内核中添加自己编写驱动

    一.概述 Linux 内核编译流程如下: 1.配置 Linux 内核. 2.编译 Linux 内核. 说明:进入 Linux 内核源码,使用 make help 参看相关配置. 二.make menu ...

  5. DPDK pmd驱动初始化(十九)

    在没有引入pmd用户态网卡驱动之前, 网卡在收到报文后,网卡驱动会将报文从网卡缓冲区拷贝到内核, 接着内核在把报文拷贝到应用层,整个过程需要2次的拷贝以及系统调用.当应用层需要发送数据时,应用层将报文 ...

  6. 嵌入式Linux驱动笔记(二十五)------Input子系统框架

    你好!这里是风筝的博客, 欢迎和我一起交流. 一.Input子系统概述 二.Input子系统架构 三.Input子系统工作机制 3.1 核心层(input.c) 3.1.1 input_init函数 ...

  7. 从零开始之驱动发开、linux驱动(二十五、framebuffer 子系统框架)

    一.概念 Framebuffer,也叫帧缓冲,其内容对应于屏幕上的界面显示,可以将其简单理解为屏幕上显示内容对应的缓存,修改Framebuffer中的内容,即表示修改屏幕上的内容,所以,直接操作Fra ...

  8. 嵌入式Linux驱动笔记(二十九)------内存管理之伙伴算法(Buddy)分析

    你好!这里是风筝的博客, 欢迎和我一起交流. 我们知道,在一个通用操作系统里,频繁申请内存释放内存都会出现一个非常著名的内存管理问题:内存碎片. 学过操作系统的都知道,有很多行之有效的方法(比如:记录 ...

  9. Window XP驱动开发(二十四)虚拟串口设备驱动

    转载请标明是引用于 http://blog.csdn.net/chenyujing1234 欢迎大家拍砖 在我的一篇文章<<winCE中实现虚拟串口的方法 >>中,讲到在win ...

最新文章

  1. 手把手重现Science的主图Maptree
  2. 8月数据库排行榜:SQL Server分数下降最多
  3. Android studio JNI jni实例
  4. 谷歌浏览器怎么重发请求_Googel 浏览器 模拟发送请求工具--Advanced REST Client
  5. d3js绘制y坐标轴_【ggplot2】 设置坐标轴
  6. codeigniter mysql查询_php – CodeIgniter MySQL查询不起作用
  7. 稳健估计/M估计/最小二乘法
  8. 计算机视觉CV中特征点提取SURF算法的学习笔记
  9. zookeeper OOM问题排查
  10. Linux可执行文件
  11. 高考成绩将于6月23日发布 查分可通过这5种方式
  12. 如何将base64码保存为图片
  13. python猴子分桃子的数学题_小学奥数猴子分桃练习及答案【三篇】
  14. Mobaxterm终端工具和Neokylin7基础
  15. 怎样在线将图片制作成圆角图样式
  16. 计算机上无线网络开关在哪里,台式电脑wifi开关在哪
  17. k8s UAT改环境
  18. 主从服务器 php分配,MYSQL 主从服务器配置
  19. 微信小程序图片验证组件封装
  20. 单频阻抗匹配:采用四分之一波长变换器

热门文章

  1. python3安装哪个版本-python2和python3哪个版本新
  2. 精通python要多久-精通python 或者R语言大约需要多长时间?怎样算精通?
  3. python在中小学教学中的应用-在python程序中的进程操作
  4. python基础语法第10关作业-Python基础作业一
  5. python用于什么-python用于什么
  6. python是不是特别垃圾-Python里的垃圾回收机制是什么意思,搞不懂?
  7. python3读取excel数据-python3 读取Excel表格中的数据
  8. CompletableFuture框架
  9. LeetCode Convert Sorted List to Binary Search Tree(有序单链表转为平衡二叉树)
  10. win8.1升级到win10后 vmware不能连网的问题