1. 通过Linux内核与模块之间的接口
定义静态全局变量,通过Linux内核与模块之间的接口,将全局变量的地址传递给kernel
以ixgbe驱动为例:模块中定义了static全局变量 ixgbe_driver:
static struct pci_driver ixgbe_driver = {
.name = ixgbe_driver_name,
.id_table = ixgbe_pci_tbl,
.probe = ixgbe_probe,
.remove = __devexit_p(ixgbe_remove),
#ifdef CONFIG_PM
#ifndef USE_LEGACY_PM_SUPPORT
.driver = {
.pm = &ixgbe_pm_ops,
},
#else
.suspend = ixgbe_suspend,
.resume = ixgbe_resume,
#endif /* USE_LEGACY_PM_SUPPORT */
#endif
#ifndef USE_REBOOT_NOTIFIER
.shutdown = ixgbe_shutdown,
#endif
#ifdef HAVE_SRIOV_CONFIGURE
.sriov_configure = ixgbe_pci_sriov_configure,
#endif
#ifdef HAVE_PCI_ERS
.err_handler = &ixgbe_err_handler
#endif
};
在module_init中,通过pci_register_driver向PCI subsystem注册该驱动,将上述全局变量的地址传给subsystem。
pci_register_driver实际上是一个宏,调用的真正函数为__pci_register_driver:
#define pci_register_driver(driver)
__pci_register_driver(driver, THIS_MODULE, KBUILD_MODNAME)
在该函数中,初始化driver的一系列字段,并向总线注册device_driver:
int __pci_register_driver(struct pci_driver *drv, struct module *owner,
const char *mod_name)
{
/* initialize common driver fields */
drv->driver.name = drv->name;
drv->driver.bus = &pci_bus_type;
drv->driver.owner = owner;
drv->driver.mod_name = mod_name;
drv->driver.groups = drv->groups;
drv->driver.dev_groups = drv->dev_groups;
spin_lock_init(&drv->dynids.lock);
INIT_LIST_HEAD(&drv->dynids.list);
/* register with core */
return driver_register(&drv->driver);
}
EXPORT_SYMBOL(__pci_register_driver);
2.向kernel传递一个函数指针,通过函数指针来修改模块内部的数据
在上述ixgbe_driver的定义中,将probe字段设置为ixgbe_probe函数,其定义如下:
static int __devinit ixgbe_probe(struct pci_dev *pdev,
const struct pci_device_id __always_unused *ent)
3. EXPORT_SYMBOL/EXPORT_SYMBOL_GPT导出全局变量
二者的区别在于后者仅支持MODULE_LICENSE为GPT的模块
默认情况下,模块与模块之间、模块与内核之间的全局变量是相互独立的,只有通过EXPORT_SYMBOL将模块导出才能对其他模块或内核可见。
首先介绍一下Linux kallsyms:内核符号表,其中会列出所有的Linux内核中的导出符号,在用户态下可以通过/proc/kallsyms访问,此时由于内核保护,看到的地址为0x0000000000000000,在root模式下可以看到真实地址。启用kallsyms需要编译内核时设置CONFIG_KALLSYMS为y。
Linux模块在编译时符号的查找顺序:
在本模块中符号表中,寻找符号(函数或变量实现)
在内核全局符号表中寻找
在模块目录下的Module.symvers文件中寻找
下面我们编写两个模块对EXPORT_SYMBOL的作用加以说明。
在通过内核模块定义一个名为_exported_symbol的全局变量前,首先查看kallsyms,此时并不存在这一符号:
创建一个新的内核模块,并定义:
//mod.c
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
int _exported_symbol = 0;
// EXPORT_SYMBOL(_exported_symbol);
...
编译并插入该模块,查看内核符号表,发现出现了地址在0xffffffffc09c0380
,类型为b的名为_exported_symbol的符号:
而编译产生的Module.symvers为空。在另一模块中通过extern链接此符号,并将上述Module.symvers拷贝到该目录下:
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
extern int _exported_symbol;
...
编译报错,该模块无法访问_exported_symbol这一符号:
当在第一个模块中加入EXPORT_SYMBOL(_exported_symbol)
这一语句,编译并重新插入后,再次查看kallsyms,发现其类型变为B:
其编译产生的Module.symvers为:
此时再次编译模块2,成功编译。
在不插入模块1的情况下插入模块2,会出现Unknown symbol in module,原因在于此时内核全局符号表中不存在该符号:
先插入模块1,再插入模块2,通过dmesg查看_exported_symbol的变化,此时模块1中定义的exported_symbol成功被模块2使用并修改:
将mod1中的EXPORT_SYMBOL注释掉,插入模块1,此时全局符号表中的符号类型为b,插入模块2报错,无法访问模块1中的符号:
原因在于,模块类型为小写字母b表示局部引用,定义在BSS,只能在模块内访问。模块类型为大写字母B表示全局引用,可以在模块外访问,其他类型类似。
Reference
1.intersvyaz/ixgbe: ixgbe driver mirror (github.com)
2.Inter-module communication in Linux kernel - Stack Overflow
3.c - How to call exported kernel module functions from another module? - Stack Overflow