声明:此文档只做学习交流使用,请勿用作其他商业用途
author:朝阳_tony转载请注明出处:http://blog.csdn.net/linzhaolove
此文中源码可以去http://dpdk.org/dev 网页中下载;更多官方文档请访问http://dpdk.org
dpdk 提供了新的关于intel 网卡的驱动,我们在成功编译源码之后,就可以安装;
具体请看我的写的intel dpdk 安装部署的文章,
http://blog.csdn.net/linzhaolove/article/details/9251433
我们在安装了,标准内核的uio,还有 dpdk 提供的igb_uio.ko 驱动后,(x86_64-default-linuxapp-gcc/kmod/igb_uio.ko )
会在/dev/目录下产生几个uio的设备文件;
# ls /dev/uio*
/dev/uio0 /dev/uio1 /dev/uio2 /dev/uio3
由于我有4个intel 的网卡,因此下面产生的4个设备文件描述符;
具体igb_uio.ko 和 uio 之间的关系,如遇机会我会在后期讲解,如果你急于想了解uio,请上网自己学习,再自己看igb的 驱动源码,dpdk很多数据处理都是在用户态空间完成,因此它的驱动很简单;
我们用dpdk 提供的实例程序l2fwd 做参考,我们去看他是怎样调用的;
打开main.c 文件;vim examples/l2fwd/main.c
首先会在main函数中调用rte_eal_init()进行dpdk的底层结构初始化在,在这个函数中会调用到rte_eal_pci_init()函数,去扫描/sys/bus/pci/devices/下的所以设备,并将有效信息保存到device_list 列表中;
struct pci_device_list device_list; /**< Global list of PCI devices. */
rte_eal_pci_init()代码的详细介绍请看我写的文章http://blog.csdn.net/linzhaolove/article/details/9358085
在l2fwd 实例code中,调用rte_igb_pmd_init()函数,去完成回调函数的初始化,底层网络数据包处理函数的管理等;我用的是千兆网卡,因此用这个函数,如果是10G的网卡那就要用到rte_ixgbe_pmd_init()这个函数,我这只讲千兆网卡,其实他们的初始化流程大致相同;
这个函数在dpdk/lib/librte_pmd_igb/e1000_ethdev.c文件中定义;
rte_eth_driver_register(&rte_igb_pmd);
函数中调用dirver注册函数将 rte_igb_pmd 这个结构体传递进去,我们看一下这个结构体的赋值;
static struct eth_driver rte_igb_pmd = {
{
.name = "rte_igb_pmd",
.id_table = pci_id_igb_map,
.drv_flags = RTE_PCI_DRV_NEED_IGB_UIO,
},
.eth_dev_init = eth_igb_dev_init,
.dev_private_size = sizeof(struct e1000_adapter),
};
其中的pci_id_igb_map保存的是设备厂商的id和设备id;会在后面的驱动和设备匹配时用到;
eth_drv->pci_drv.devinit = rte_eth_dev_init;
rte_eal_pci_register(ð_drv->pci_drv);
它的源码也很简单,将rte_eth_dev_init函数的指针赋值给来的devinit,这个函数会在pci这个嗅探的时候调用,进行初始化;
而下面的rte_eal_pci_register函数,是将pci_drv这个设备驱动相关的结构体,插入到driver_list列表中,这个驱动列表是在rte_eal_pci_init()函数中,与deivce_list列表,一块初始化的;
接下来l2fwd 中,调用rte_eal_pci_probe()函数,去完成驱动与设备之间的加载;
TAILQ_FOREACH(dev, &device_list, next)
pci_probe_all_drivers(dev);
通过便利设备链表去查询对应的设备;
TAILQ_FOREACH(dr, &driver_list, next) {
if (rte_eal_pci_probe_one_driver(dr, dev))
查看一下设备列表的中的设备与驱动列表的中的驱动,是否想匹配,在rte_eal_pci_probe_one_driver函数中去完成;
函数在dpdk/lib/librte_eal/linuxapp/eal/eal_pci.c文件中定义;
uio_status = pci_uio_check_module(module_name);
代码开始先核查igb_uio驱动是不安装,module_name = IGB_UIO_NAME=igb_uio ; 这个驱动,就是文章开始我们安装的igb_uio.ko驱动;
所谓的核查驱动是否安装,其实就是打开/proc/modules文件,去遍历查找看是否存在;
# cat /proc/modules
rte_kni 282604 0 - Live 0xffffffffa03b3000
igb_uio 6483 0 - Live 0xffffffffa0054000
接下来在核查id_table表,看是否有于设备想匹配的vendor_id,device_id,subsystem_vendor_id,subsystem_device_id等;
而这个id_table表是在文章上面初始化rte_igb_pmd这个结构体赋值的;
/* unbind current driver, bind ours */
if (pci_unbind_kernel_driver(dev) < 0)
一般系统是用于设备的驱动的,而且一般系统启动时,已经当驱动赋给了设备,而这个驱动并不是我们想用的饿,因此,当我们找到了设备,我们就要分开原来的驱动,绑定我们新安装的驱动;
其实是打开/sys/bus/pci/devices目录下相应设备的目录下的/driver/unbind 文件,将设备信息写进去;
例如我的一个设备是0000:15:00.0,就去打开设备目录的驱动目录下的unbind 文件,
/sys/bus/pci/devices/0000:15:00.0/driver/unbind
将设备的,loc->domain, loc->bus, loc->devid, loc->function , 信息写进去;对应的就是domain=0000 bus =15 ,devid=00,function=0;
if (pci_uio_bind_device(dev, module_name) < 0)
调用pci_uio_bind_device函数去绑定新的设备驱动 ;
打开/sys/bus/pci/drivers/igb_uio/new_id 文件, 将dev->id.vendor_id, dev->id.device_id 设备厂商id很设备id写入这个文件;
打开/sys/bus/pci/drivers/igb_uio/bind 文件, 将loc->domain, loc->bus, loc->devid, loc->function 这个几个参数写入;完成驱动的绑定;
驱动绑定好后,我们要怎样使用这些资源呢?
接下来的代码是调用pci_uio_map_resource()函数去映射设备资源;
/* map the NIC resources */
if (pci_uio_map_resource(dev) < 0)
介绍一下pci_uio_map_resource(),这个函数用来映射pci设备虚拟内存的资源;
首先查找设备中包含的uio目录,例如我的几个设备的对应的目录为
/sys/bus/pci/devices/0000\:15\:00.0/uio/uio0/
对应该的两个设备是也对应着两个uio的设备描述符;找到上面的目录后,然后再分别打开目录下 maps/map0/ 子目录下的的size 和offset 两个文件,取出设备可以映射的的尺寸大小,和对应的映射偏移量;
/sys/bus/pci/devices/0000\:15\:00.0/uio/uio0/maps/map0/offset
/sys/bus/pci/devices/0000\:15\:00.0/uio/uio0/maps/map0/size
/* open and mmap /dev/uioX */
rte_snprintf(devname, sizeof(devname), "/dev/uio%u", uio_num);
mapaddr = pci_map_resource(dev, NULL, devname, offset, size);
这调用的pci_map_resource函数,在其内部完成资源映射的同时,还要将中断设备的文件描述符赋值给对应的中断句柄;这个中断句柄会在中断处理时用到,这一点很重要;
关于dpdk中断模块的介绍请看http://blog.csdn.net/linzhaolove/article/details/9309855
dev->intr_handle.fd = fd;
dev->intr_handle.type = RTE_INTR_HANDLE_UIO;
最后将映射到的uio的信息保存到一个新分配的uio_res结构体重,然后保存到uio_res_list链表中; 这个链表是在rte_eal_pci_init()中提前预留创建的;
memcpy(&uio_res->pci_addr, &dev->addr, sizeof(uio_res->pci_addr));
TAILQ_INSERT_TAIL(uio_res_list, uio_res, next);
调用devinit这个函数指针去初始化驱动,这个函数指针指的是 文章第3节小2 部分,赋值给他的函数rte_eth_dev_init;
/* call the driver devinit() function */
return dr->devinit(dr, dev);
eth_dev = rte_eth_dev_allocate();
获取一个全局的rte_eth_devices[nb_ports] 地址空间,并将nb_ports目前设备端口计数加1;
/* init user callbacks */
TAILQ_INIT(&(eth_dev->callbacks));
初始一个中断回调函数列表;
/* Invoke PMD device initialization function */
diag = (*eth_drv->eth_dev_init)(eth_drv, eth_dev);
去初始化一个 PMD 模式的pci设备驱动,这调用的函数指针eth_dev_init 是在文章开始初始化的结构体中赋值的eth_igb_dev_init()函数;
下面介绍eth_igb_dev_init()函数;
函数在dpdk/lib/librte_pmd_igb/e1000_ethdev.c 文件中定义;
eth_dev->dev_ops = ð_igb_ops;
eth_dev->rx_pkt_burst = ð_igb_recv_pkts;
eth_dev->tx_pkt_burst = ð_igb_xmit_pkts;
将eth_igb_ops这个结构体赋值给了dev_ops , 里面主要包含很多的函数调用指针,如eth_igb_configure,eth_igb_start,eth_igb_stop,等一个pci设备操作函数都提前初始化在eth_igb_ops 这个结构体中;这个结构体定义在dpdk/lib/librte_pmd_igb/e1000_ethdev.c 文件中;
接下来将网卡数据的接收和发送函数也赋值给了eth_dev这个结构体中eth_igb_recv_pkts(),eth_igb_xmit_pkts();
if (e1000_setup_init_funcs(hw, TRUE) != E1000_SUCCESS) {
调用这函数去完成剩余函数指针的初始化,为后期的设备运行做准备;
if (e1000_read_mac_addr(hw) != 0)
获取实际的mac地址,
最后将中断处理函数,注册进入中断链表;
rte_intr_callback_register(&(pci_dev->intr_handle),
eth_igb_interrupt_handler, (void *)eth_dev);
技术水平有待提高,如果文章有错误的地方希望读者指正,相互交流,互相学习;O(∩_∩)O~