前期学习了一些DPDK的demo,了解了一些基础DPDK的应用。对dpdk的一些底层原理没做过多分析,今天主要学习用户态驱动程序的实现情况。
0x02 用户态驱动程序UIO
UIO(Userspace I/O)是运行在用户空间的I/O技术。Linux系统中一般的驱动设备都是运行在内核空间,而在用户空间用应用程序调用即可。
UIO的内核部分和用户空间的工作
内核空间
UIO的少量运行在内核空间的驱动所做的工作有哪些呢?
(1)分配和记录设备需要的资源和注册uio设备
在设备的探测函数中:
-使能PCI 设备
-申请资源
-读取并记录配置信息
-注册uio设备// uio_register_device()
// uio_8139d_pci_probe & uio_8139d_handler
(2)必须在内核空间实现的小部分中断应答函数
用户空间的关键操作
(1)关键操作
(2)响应硬件中断
有什么优势?
1、用户空间驱动程序的优点
1、可以和整个C库链接。
2、在驱动中可以使用浮点数,在某些特殊的硬件中,可能需要使用浮点数,而linux内核并不提供浮点数的支持。如果能在用户态实现驱动,就可以轻松解决这一问题。
3、驱动问题不会导致整个系统挂起。内核态驱动的一些错误常常导致整个系统挂起。
4、用户态的驱动调试方便。
5、可以给出封闭源码的驱动程序,不必采用GPL,更为灵活。
我的理解为将以前内核模块驱动所需要做的工作移到了用户态空间操作。dpdk实现一个uio驱动,然后实现e1000e驱动的相关操作。
0x03 源码简单分析
与其他内核PCI模块开发一样代码结构,UIO驱动实现部分:
关键数据结构:
//dpdk定义的uio pci设备描述结构
struct rte_uio_pci_dev {
struct uio_info info; //uio 通用结构
struct pci_dev *pdev; //pci设备描述结构
enum rte_intr_mode mode; //中断模式
};
struct uio_info {
struct uio_device *uio_dev; //uio设备属于
const char *name; //名称
const char *version; //版本号
struct uio_mem mem[MAX_UIO_MAPS];//可映射的内存区域列表,size == 0表示列表结束
struct uio_port port[MAX_UIO_PORT_REGIONS]; //网口区域列表
long irq; //UIO_IRQ_CUSTOM 中断号
unsigned long irq_flags; //请求中断号的标志
void *priv; //可选的私有数据
irqreturn_t (*handler)(int irq, struct uio_info *dev_info); //中断信息处理
int (*mmap)(struct uio_info *info, struct vm_area_struct *vma);//内存映射操作
int (*open)(struct uio_info *info, struct inode *inode); //打开
int (*release)(struct uio_info *info, struct inode *inode); //释放
int (*irqcontrol)(struct uio_info *info, s32 irq_on); //中断控制操作 关闭/打开 当向/dev/uioX中写入值时
};
关键处理函数:
static int __init
igbuio_pci_init_module(void)
{
int ret;
ret = igbuio_config_intr_mode(intr_mode); //内核insmod时带的参数,中断模式
if (ret < 0)
return ret;
return pci_register_driver(&igbuio_pci_driver);//注册PCI设备,实际调用pci_module_init。
}
关键的pci驱动操作函数,主要是探测和删除
static struct pci_driver igbuio_pci_driver = {
.name = "igb_uio", //名称
.id_table = NULL,
.probe = igbuio_pci_probe, //探测回调函数
.remove = igbuio_pci_remove,//删除回调函数
};
关键看下igbuio_pci_probe:
//根据内核版本不同,返回类型不同
#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 8, 0)
static int __devinit
#else
static int
#endif
igbuio_pci_probe(struct pci_dev *dev, const struct pci_device_id *id)
{
struct rte_uio_pci_dev *udev;
struct msix_entry msix_entry;
int err;
//分配内核空间内存,rte_uio_pci_dev一个设备类型大小
udev = kzalloc(sizeof(struct rte_uio_pci_dev), GFP_KERNEL);
if (!udev)
return -ENOMEM;
/*
* 使能设备: 调用更底层的PCI代码使能设备的内存和I/O区域
*/
err = pci_enable_device(dev);
if (err != 0) {
dev_err(&dev->dev, "Cannot enable PCI device\n");
goto fail_free;
}
/*
预留PCI设备的i/o或内存区域,pci_request_regions这个函数封装了一些PCI驱动相关的内存操作,不深入理解;
*/
err = pci_request_regions(dev, "igb_uio");
if (err != 0) {
dev_err(&dev->dev, "Cannot request regions\n");