在編寫 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 反而更清晰。所以需要具體情況具體分析