在编写 Linux 设备驱动时,尤其是 platform、I2C、SPI 等总线驱动,我们经常会看到类似下面的写法:- module_platform_driver(my_driver);
复制代码 这类宏看起来很“魔法”,但实际上它们只是 Linux 内核为了减少样板代码而提供的一种 driver helper macro,本文主要讲解这类宏的用法与机制
一、传统模块初始化方式
这里以platform驱动为例,传统的驱动写法通常是这样的:- 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_init和module_exit接口函数进行注册,这种写法的样板代码高度重复,几乎每一个platform驱动都是一模一样的,内核中存在大量这种固定模式的代码,非常适合使用宏来简化
二、模块定义宏的引入
为了解决上述问题,Linux 内核引入了一组 module driver helper macro,用于简化驱动的注册与注销过程。
以platform驱动为例,内核提供了- module_platform_driver(...)
复制代码 虽然接口看着像是函数,但是他是由宏来实现的,使用该宏之后,上面的代码就可以简化为:- 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_register和platform_driver_unregister接口函数了,与传统写法完全相同
当然这种写法不仅仅只有platform驱动有,内核为不同的总线都提供了对应的宏定义- 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找到对应的宏,如果为其他总线驱动,需要到对应的头文件中查找,如下所示- /* 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头文件中,定义代码如下- /**
- * 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 阶段还需要做额外初始化工作,使用模块宏的话,它的底层只能调用对应的register和unregister函数无法做其他操作
- 对初始化/退出顺序有精细控制需求
在这些场景下,手写 module_init / module_exit 反而更清晰。所以需要具体情况具体分析
来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作! |