一、中文讲解
这两个函数是Linux内核模块中对于Mellanox InfiniBand 驱动程序初始化和清理的函数。
mlx4_ib_init()函数是模块初始化函数,使用__init宏标注,表示该函数只在模块加载时运行一次。
函数执行的步骤如下:
1. 通过alloc_ordered_workqueue创建一个有序的工作队列wq。这个队列用于将工作项按顺序排队执行,WQ_MEM_RECLAIM表示如果系统内存紧张,允许回收。
如果工作队列创建失败,返回-ENOMEM错误码,表示内存不足。
2. 在编译时,如果定义了CONFIG_MLX4_IB_DEBUG_FS,则会调用mlx4_ib_register_debugfs函数注册调试文件系统(debugfs),这有助于查询和操作驱动程序的内部信息。
3. 调用mlx4_ib_mcg_init函数初始化多播组(multi-cast group)的一些资源,如果初始化失败,则跳转到标签clean_wq进行清理。
4. 执行init_dev_assign函数,这个函数虽然没有代码显示,但从名称上看,它可能用于初始化设备或分配相应资源。
5. 注册InfiniBand接口到Mellanox的InfiniBand核心驱动,使用的函数为mlx4_register_interface。如果注册失败,会跳转到标签clean_mcg进行清理。
6. 如果所有初始化步骤都成功,函数返回0,表示初始化成功。
如果在任何一步发生错误,函数会执行清理操作:
- clean_mcg: 清除通过mlx4_ib_mcg_init初始化的多播组资源。
- clean_wq: 销毁之前创建的工作队列wq。
mlx4_ib_cleanup()函数是模块清理函数,使用__exit宏标注,表示这个函数在模块卸载时调用。
函数的步骤如下:
1. 调用mlx4_unregister_interface来注销之前注册的Mellanox InfiniBand接口。
2. 如果CONFIG_MLX4_IB_DEBUG_FS被定义,则调用mlx4_ib_unregister_debugfs注销调试文件系统。
3. 调用mlx4_ib_mcg_destroy销毁在模块初始化时创建的多播组资源。
4. 销毁之前创建的工作队列wq。
5. 释放dev_num_str_bitmap,这个变量可能用于跟踪设备编号的分配,但代码段中没有展示它的分配过程。
module_init(mlx4_ib_init);和module_exit(mlx4_ib_cleanup);是宏定义,它们告诉内核模块初始化和退出时应该调用哪些函数。
在 Linux 驱动开发中,mlx4_ib_interface 是一个 mlx4_interface 类型的结构体,它实现了 InfiniBand (IB) 协议栈接口的特定函数。这个结构定义了几个回调函数,这些函数是由底层的 Mellanox 网络设备驱动(通常是 mlx4_core 驱动)在特定事件发生时被调用的。
这些回调函数包括:
- add - 当新设备被添加时调用(例如,当一个新的网络适配器被系统探测到时)。
- remove - 用于当设备被移除时做清理工作。
- event - 当设备关联的事件发生时被调用,例如链接状态改变、硬件错误等。
- protocol - 指定这个接口支持哪种协议(在此示例中是 MLX4_PROT_IB_IPV6,代表 InfiniBand 协议的 IPv6 over IB 版本)。
- flags - 提供额外的信息标志,本例中使用了 MLX4_INTFF_BONDING 标志,表示支持网络接口绑定(bonding)功能。
这个结构体被注册到低层的 mlx4_core 驱动,它是 Mellanox 网络设备的一个核心驱动,提供了基础硬件抽象层的功能。注册后,它成为 mlx4_core 驱动和高层驱动(本例中的 mlx4_ib)之间沟通的桥梁。
mlx4_ib_interface 被注册的地方是在 mlx4_ib_init() 函数,这通常是作为模块初始化的一部分。当 mlx4_ib_init() 函数成功执行后,mlx4_ib 成为 mlx4_core 驱动感知到的一个客户端。
以下是 mlx4_ib_interface 回调函数被调用的时机:
- mlx4_ib_add - 当注册 mlx4_ib_interface 到 mlx4_core 后,如果 mlx4_core 驱动发现一个新的设备,mlx4_core 将调用 mlx4_ib 的 add 回调函数。
- mlx4_ib_remove - 当一个设备需要被移除,可能是因为硬件被物理移除或是模块正在被卸载,remove 回调函数将被调用。
- mlx4_ib_event - 当 mlx4_core 检测到某个事件发生并需要通知 mlx4_ib 模块时,event 回调函数会被调用。
在 mlx4_core 的代码中,会在适当的位置调用 mlx4_ib_interface 结构体中指定的函数,以便在上述事件发生时,正确地通知 InfiniBand 协议栈进行相关操作。这种设计允许 mlx4_core 驱动与多个协议栈(如以太网和 InfiniBand)独立协作,同时保持模块间的松耦合。
在 Linux 内核中,mlx4_core 驱动发现新设备的过程通常涉及 Linux 的 PCI 子系统。mlx4_core` 驱动是 PCI 驱动的一个实现,它注册了一系列的回调函数来处理特定于 PCI 设备的事件。下面是一个简化的描述,解释了驱动程序如何发现新的 PCI 设备:
1. PCI 设备枚举: 当系统启动时,或当新的设备被添加到系统中时(如通过热插拔),PCI 子系统将识别并枚举所有的 PCI 设备。它创建代表这些设备的数据结构,并读取它们的配置空间来获取诸如供应商ID、设备ID、类别码等信息。
2. 驱动注册: 一个 PCI 驱动程序,比如 mlx4_core,会在加载时调用 pci_register_driver() 函数来注册自己。这个注册过程包括提供一个 pci_driver 类型的结构体,该结构体包含对应的供应商ID和设备ID,以及针对新设备的回调函数,如 .probe。
static struct pci_driver mlx4_pci_driver = {
.name = DRV_NAME,
.id_table = mlx4_pci_table,
.probe = mlx4_probe,
.remove = mlx4_remove,
// ... 其他回调函数
};
这里的 mlx4_pci_table 包含了 mlx4_core 驱动支持的设备对应的供应商ID和设备ID列表。mlx4_probe 和 mlx4_remove 是当设备被探测到或移除时要调用的函数。
3. 设备和驱动匹配: 一旦驱动注册了自己,PCI 子系统就会遍历所有的 PCI 设备,尝试找出设备ID和供应商ID与驱动中 id_table 匹配的设备。对于每个匹配的设备,PCI 子系统会调用对应驱动的 .probe 回调函数。
4. 设备初始化 (mlx4_probe): 用于 mlx4_core 驱动的 probe 函数通常名为 mlx4_probe。当一个与 mlx4_pci_table 匹配的 Mellanox 设备被探测到时,mlx4_probe 会被调用。在 mlx4_probe 函数内部,驱动程序会执行必要的初始化步骤,如申请资源、设置设备、注册网络设备接口,如果有 InfiniBand 部分,也会在这个时候注册 mlx4_ib_interface 接口。
5. 接口注册: 注册过程可能涉及调用 mlx4_register_interface() 函数,这是 mlx4_core 提供的接口,允许常见的 Mellanox InfiniBand 和以太网功能扩展。这个注册函数会存储一个指向 mlx4_interface 结构体的指针,使得在交互式事件中,mlx4_core 驱动能够调用必要的回调函数。
通过上述流程,mlx4_core 驱动能够发现新的设备并与相关的协议接口(如 mlx4_ib_interface)进行通信,完成设备初始化和事件通知。
二、中文注释
这段代码是一个Linux内核模块的初始化和清除函数,在kernel-4.9\drivers\infiniband\hw\mlx4\main.c文件中,涉及到Mellanox技术的InfiniBand驱动的mlx4子系统。
// Linux内核模块的初始化函数
static int __init mlx4_ib_init(void)
{
int err;
// 为名为"mlx4_ib"的workqueue申请有序工作队列,WQ_MEM_RECLAIM标志表示该工作队列支持内存回收
wq = alloc_ordered_workqueue("mlx4_ib", WQ_MEM_RECLAIM);
// 如果工作队列创建失败,则返回内存不足的错误代码
if (!wq)
return -ENOMEM;
// 如果定义了CONFIG_MLX4_IB_DEBUG_FS编译选项,注册调试文件系统
#ifdef CONFIG_MLX4_IB_DEBUG_FS
mlx4_ib_register_debugfs();
#endif
// 初始化多播组。如果初始化失败,则跳转到clean_wq标签进行清理工作。
err = mlx4_ib_mcg_init();
// 如果初始化多播组出错,跳到clean_wq的清理流程
if (err)
goto clean_wq;
// 初始化设备分配相关工作。
init_dev_assign();
// 注册接口到mlx4核心。如果注册失败,则跳转到clean_mcg标签进行多播组清理工作
err = mlx4_register_interface(&mlx4_ib_interface);
if (err)
goto clean_mcg;
// 初始化成功,返回0
return 0;
clean_mcg:
// 清理多播组
mlx4_ib_mcg_destroy();
clean_wq:
// 销毁之前创建的工作队列
destroy_workqueue(wq);
// 返回错误代码
return err;
}
// Linux内核模块的清除函数,用于在模块卸载时被调用
static void __exit mlx4_ib_cleanup(void)
{
// 注销mlx4接口
mlx4_unregister_interface(&mlx4_ib_interface);
// 如果定义了CONFIG_MLX4_IB_DEBUG_FS编译选项,注销调试文件系统
#ifdef CONFIG_MLX4_IB_DEBUG_FS
mlx4_ib_unregister_debugfs();
#endif
// 清理多播组
mlx4_ib_mcg_destroy();
// 销毁工作队列
destroy_workqueue(wq);
// 释放之前分配的设备号位图存储空间
kfree(dev_num_str_bitmap);
}
// 指定模块加载时调用的初始化函数
module_init(mlx4_ib_init);
// 指定模块卸载时调用的清除函数
module_exit(mlx4_ib_cleanup);
这些函数是驱动模块的生命周期钩子,module_init用于表明模块加载时应当调用的函数,而module_exit用于指定模块卸载时的清理函数。__init和__exit宏在内核模块编译时有特殊含义,它们用于优化模块的初始化和退出代码。__init标记的代码在模块加载后不再需要,可以被丢弃;__exit标记的代码在构建内核为非模块支持时丢弃。
三、Linux内核"接口(interface)"机制
在Linux内核中,通过一种称为"接口(interface)"的机制允许不同的驱动程序之间相互沟通。对于Mellanox设备,这可能涉及到mlx4_core模块和mlx4_ib模块之间的交互,分别对应于以太网(Ethernet)和InfiniBand子系统。
在这个上下文中,一个mlx4_core的实例在被PCI子系统探测到后会进行初始化,并且注册到mlx4_core模块的内部数据结构中。这个注册过程中,mlx4_core会公布一系列对于外部模块(如mlx4_ib InfiniBand实现)可以调用的回调函数。
这些回调函数定义在mlx4_interface结构中,如:
static struct mlx4_interface mlx4_ib_interface = {
.add = mlx4_ib_add,
.remove = mlx4_ib_remove,
.event = mlx4_ib_event,
.protocol = MLX4_PROT_IB_IPV6,
.flags = MLX4_INTFF_BONDING
};
核心思想是,这个结构会被注册到mlx4_core模块中。当mlx4_core模块中的一个设备完成基础初始化后,它会通知所有注册了的mlx4_interface实例。通过这种方式,当设备被添加(add)或被移除(remove)时,能够反过来触发mlx4_ib_add或mlx4_ib_remove函数的调用。
在添加(add)事件发生时,Ethernet驱动调用mlx4_ib_add,这个函数会负责为InfiniBand功能初始化必要的资源,如设置队列对、分配内存等。相应地,当移除(remove)事件发生时,会调用mlx4_ib_remove以清理InfiniBand相关的资源。
这种机制不仅仅限于设备的添加和移除,也可用于处理其他类型的事件,例如设备状态改变、错误处理等。最终,这允许不同的驱动模块能够同步设备的状态,并在多协议硬件上提供一致的服务。
在Linux内核中,"接口"是指允许不同组件或驱动程序之间沟通和交互的一种机制。这种机制通常包括一系列的函数指针、数据结构和协议,定义了组件之间的通信方式。以下是一些Linux内核中用于不同驱动程序之间相互沟通的接口机制:
1. 平台设备和驱动注册: 平台驱动和设备使用平台设备结构struct platform_device和平台驱动结构struct platform_driver,这两者都包含指向描述它们如何交互的数据结构的指针。设备通常注册它们自己的资源,如内存区域、DMA和中断。当平台设备注册时,核心代码会为匹配的驱动程序调用probe()函数,此时可以建立接口。
2. 设备模型(Device Model): Linux内核的设备模型是一种抽象层,允许内核代码以统一的方式来处理硬件设备。它定义了设备、驱动程序和总线(bus)之间的关系,并通过结构体和方法来描述它们的关系。例如,每个设备结构struct device可以关联一组用于设备操作的方法。
3. 文件操作接口:字符和块设备驱动程序使用文件操作结构`truct file_operations,该结构包含指向不同操作的函数指针(如open(), read(), write(), ioctl(), 等)。这样,用户空间应用可以通过标准的系统调用来与内核中的设备文件进行交互。
4. 网络层接口:网络设备使用网络设备结构struct net_device,通过这种结构,它们可以注册自己给网络子系统,并提供一系列操作函数,以进行网络数据包的发送和接收。
5. 总线类型: 内核定义了多种总线类型,比如PCI, USB, I2C等。每种总线类型都有自己的一套方法和协议,用于发现设备、匹配驱动程序、配置资源等。
6. 内核模块与符号导出: 如果驱动程序或内核模块需要让其它部分的内核代码使用它们提供的功能,它们可以通过EXPORT_SYMBOL或EXPORT_SYMBOL_GPL宏来导出符号(函数、变量的名字)。
7. 回调函数: 驱动程序与硬件通信时通常需要中断处理,内核提供注册中断处理函数的接口,允许驱动编写自己的中断服务例程。
通常,在专业的环境下,这些接口的使用都遵循严格的编程接口(API)和编程约定(ABI),确保了内核的模块和组件可以正确地与彼此通信。