Linux内核中模块定义宏机制解析

在编写 Linux 设备驱动时,尤其是 platform、I2C、SPI 等总线驱动,我们经常会看到类似下面的写法:

c 复制代码
module_platform_driver(my_driver);

这类宏看起来很"魔法",但实际上它们只是 Linux 内核为了减少样板代码而提供的一种 driver helper macro,本文主要讲解这类宏的用法与机制

一、传统模块初始化方式

这里以platform驱动为例,传统的驱动写法通常是这样的:

c 复制代码
static struct platform_driver my_platform_driver = {
    .probe  = my_probe,
    .remove = my_remove,
    .driver = {
        .name = "my_driver",
    },
};

static int __init my_init(void)
{
    return platform_driver_register(&my_platform_driver);
}

static void __exit my_exit(void)
{
    platform_driver_unregister(&my_platform_driver);
}

module_init(my_init);
module_exit(my_exit);
MODULE_LICENSE("GPL");

这是一个标准的驱动模板,有驱动的入口init函数与出口exit函数,并通过module_initmodule_exit接口函数进行注册,这种写法的样板代码高度重复,几乎每一个platform驱动都是一模一样的,内核中存在大量这种固定模式的代码,非常适合使用宏来简化

二、模块定义宏的引入

为了解决上述问题,Linux 内核引入了一组 module driver helper macro,用于简化驱动的注册与注销过程。

以platform驱动为例,内核提供了

c 复制代码
module_platform_driver(...)

虽然接口看着像是函数,但是他是由宏来实现的,使用该宏之后,上面的代码就可以简化为:

c 复制代码
static struct platform_driver my_platform_driver = {
    .probe  = my_probe,
    .remove = my_remove,
    .driver = {
        .name = "my_driver",
    },
};

module_platform_driver(my_platform_driver);
MODULE_LICENSE("GPL");

可以看见,使用该宏之后,就不需要再手写__init__exit注册与注销接口函数了,也不需要再显式调用platform_driver_registerplatform_driver_unregister接口函数了,与传统写法完全相同

当然这种写法不仅仅只有platform驱动有,内核为不同的总线都提供了对应的宏定义

c 复制代码
module_i2c_driver(my_i2c_driver);
module_spi_driver(my_spi_driver);
module_usb_driver(my_usb_driver);
module_pci_driver(my_pci_driver);
.......

他们遵循完全相同的设计思想:一个模块,只注册一个驱动,用一行宏搞定

三、本质解析

这里还是以platform驱动为例,module_platform_driver 是一个宏封装。我们可以打开内核源码kernel/include/linux/platform_device.h找到对应的宏,如果为其他总线驱动,需要到对应的头文件中查找,如下所示

c 复制代码
/* module_platform_driver() - Helper macro for drivers that don't do
* anything special in module init/exit. This eliminates a lot of
* boilerplate. Each module may only use this macro once, and
* calling it replaces module_init() and module_exit()
*/

#define module_platform_driver(__platform_driver) \
module_driver(__platform_driver, platform_driver_register, \
platform_driver_unregister)

可以看见module_platform_driver宏中又使用了module_driver这个宏定义,这个宏定在kernel/include/linux/device/driver.h头文件中,定义代码如下

c 复制代码
/**

* module_driver() - Helper macro for drivers that don't do anything
* special in module init/exit. This eliminates a lot of boilerplate.
* Each module may only use this macro once, and calling it replaces
* module_init() and module_exit().
*
* @__driver: driver name
* @__register: register function for this driver type
* @__unregister: unregister function for this driver type
* @...: Additional arguments to be passed to __register and __unregister.
*
* Use this macro to construct bus specific macros for registering
* drivers, and do not use it on its own.
*/
#define module_driver(__driver, __register, __unregister, ...) \
static int __init __driver##_init(void) \
{ \
return __register(&(__driver) , ##__VA_ARGS__); \
} \
module_init(__driver##_init); \
static void __exit __driver##_exit(void) \
{ \
__unregister(&(__driver) , ##__VA_ARGS__); \
} \

module_exit(__driver##_exit);

module_driver宏中就可以看见对应的驱动注册与注销的模板了,这里主要就是通过宏拼接以及可变参数宏来实现,读者可以自行宏展开进行分析,所有的模块宏platform驱动、pci驱动、usb驱动等等,底层都是调用了module_driver进行宏替换与拼接组成最后的模块注册模板,这里就不再赘述了

四、使用场景

在主线内核中,这种写法已经成为事实标准,原因主要有:

  • *减少样板代码
  • 统一驱动风格
  • 降低出错概率
  • 代码审查更友好

对于维护者来说,一眼看到module_platform_driver(xxx_driver); 就能立刻知道这是一个"标准的 platform 模块驱动。

虽然 helper macro 很方便,但并非所有场景都适合。不建议使用的情况包括:

  • 一个模块中注册 多个 driver
  • 模块 init 阶段还需要做额外初始化工作,使用模块宏的话,它的底层只能调用对应的registerunregister函数无法做其他操作
  • 对初始化/退出顺序有精细控制需求

在这些场景下,手写 module_init / module_exit 反而更清晰。所以需要具体情况具体分析

相关推荐
BlueBirdssh19 天前
时钟相位差
linux驱动
蓝天居士1 个月前
Linux设备驱动之gpio-keys(2)
linux驱动·gpio-keys
ton_tom3 个月前
设备驱动程序编程-Linux2.6.10-kdb安装
linux驱动
淮北也生橘123 个月前
Linux驱动知识点:容器嵌入机制(Container Embedding)
linux驱动·嵌入式linux
yan123688 个月前
Linux 驱动之设备树
android·linux·驱动开发·linux驱动
liuluyang5309 个月前
linux sysfs的使用
linux驱动·sysfs
m0_747124531 年前
基于 IMX6ULL 的环境监测自主调控系统
linux驱动
m0_747124531 年前
Linux 驱动入门(5)—— DHT11(温湿度传感器)驱动
linux·linux驱动
御风_211 年前
Linux——字符设备驱动控制LED
linux·ubuntu·linux驱动