2023年8月20日 星期日

Linux module programming

Source module_platform_driver 與 module_init_module_platform_driver module_init_嵌入式小胖的博客-CSDN博客

module_platform_driver 與 module_init


版權聲明:本文為CSDN博主「嵌入式小胖」的原創文章,遵循CC 4.0 BY-SA版權協議,轉載請附上原文出處連結及本聲明。
原文連結:https://blog.csdn.net/m0_37765662/article/details/106490792

在linux內核源碼中,我們經常看到module_platform_driver 與 module_init這兩個宏定義,有時候在這個驅動中用module_platform_driver,有時候用module_init,那這兩個宏定義之間有什麼差異嗎? 還是說可以隨便用呢? 這就需要我們旭跟蹤代碼,來看看這兩個宏定義到底什麼東西?

首先,介紹下
module_initmodule_init對於做驅動的人應該不陌生,linux內核是一個巨集內核,巨集內核就是驅動和內核打包在一起的。 由於驅動是作為內核模組掛載在內核上的,而內核對於模組的介面就是module_initmodule_exit,所以你要載入一個內核模組的時候,必須使用module_init來進行,而卸載一個內核模組的時候,必須使用module_exit來進行。 例如:

 

module_init(xxxx_init); 載入xxxx模組

 

module_exit(xxxx_exit); 卸載xxxx模組

 

那是不是就有一個疑問了,既然是所有模組都是通過這兩個函數來進行載入和卸載的,那應該所有驅動都用module_init函數啊,為什麼還會有module_platform_driver 這不是多此一舉嗎? 醒醒吧,少年,Linux社區那麼多大神在更新,會出現這種錯誤嗎?

 

那麼我們先看看module_platform_driver的宏定義是個啥?

 

#define module_platform_driver(__platform_driver) \

            module_driver(__platform_driver, platform_driver_register, \

                                    platform_driver_unregister)

#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_platform_driver 追根溯源,發現最終還是調用了module_init,但是,又不僅僅是調用了module_init,還調用了platform_driver_registerplatform_driver_unregister,這兩個函數的作用就是註冊和卸載平臺驅動。

 

一般某個設備或某個控制器掛載在處理器上時,肯定是通過某種總線來連接的,比喻說常見總線有SPI總線、IIC總線等等,但控制器一般是通過內部總線掛載處理器端的,對於這一類設備,linux抽象出了一個平臺總線,來包含所有的處理器總線掛載的設備。 而這類總線需要註冊到內核中去,就需要用platform_driver_register來實現,平臺總線是抽象出來的,所以所有通過總線直接連在處理器上的設備是不需要關心平臺總線怎麼運作的,因此這個平臺總線的註冊和註銷都是通用的,所以在載入總線設備驅動時,直接調用module_platform_driver 就可以將平臺驅動註冊函數和卸載函數、以及總線設備載入一次性運行完,避免了總線驅動在每次載入驅動時都需要手動註冊平臺總線。

 

其實說了這麼多,module_platform_driver就是對module_init進一步的封裝,在module_init之外添加了一些功能,對於平臺總線設備而言,直接調用module_platform_driver就可以避免在module_init函數中去註冊平臺驅動了,使得平臺設備驅動的載入變得更方便了。

 

那是不是說module_init可以與module_platform_driver就可以通用呢? 當然不是,能用module_platform_driver的地方肯定可以用module_init,但能用module_init卻不一定能用module_platform_driver,這主要看設備是不是通過平臺總線的方式掛載處理器上的。

 

函數名            非平臺總線設備        平臺總線設備

module_platform_driver        不可用            可用

module_init     可用    可用

對於平臺總線設備,可以用兩種方法來載入

 

static int __init xxxx_init(void)

{

            return platform_driver_register(&xxxx_driver);

}

 

static void __exit xxxx_exit(void)

{

            platform_driver_unregister(&xxxx_driver);

}

 

module_init(xxxx_init);

module_exit(xxxx_exit);

module_platform_driver(xxxx_driver);

這兩種方式的效果是一樣的,但明顯module_platform_driver比較簡潔。

 

但對於其他總線的設備,就不能使用module_platform_driver了,必須使用module_init,在module_init函數中再註冊一遍其他總線,例如SPI總線設備。

 

static int __init spidev_init(void)

{

            int status;

 

            /* Claim our 256 reserved device numbers.  Then register a class

             * that will key udev/mdev to add/remove /dev nodes.  Last, register

             * the driver which manages those device numbers.

             */

            BUILD_BUG_ON(N_SPI_MINORS > 256);

            status = register_chrdev(SPIDEV_MAJOR, "spi", &spidev_fops);

            if (status < 0)

                        return status;

 

            spidev_class = class_create(THIS_MODULE, "spidev");

            if (IS_ERR(spidev_class)) {

                        unregister_chrdev(SPIDEV_MAJOR, spidev_spi_driver.driver.name);

                        return PTR_ERR(spidev_class);

            }

 

            status = spi_register_driver(&spidev_spi_driver);

            if (status < 0) {

                        class_destroy(spidev_class);

                        unregister_chrdev(SPIDEV_MAJOR, spidev_spi_driver.driver.name);

            }

            return status;

}

module_init(spidev_init);

 

static void __exit spidev_exit(void)

{

            spi_unregister_driver(&spidev_spi_driver);

            class_destroy(spidev_class);

            unregister_chrdev(SPIDEV_MAJOR, spidev_spi_driver.driver.name);

}

module_exit(spidev_exit);

 

還有一種情況不能使用module_platform_driver,就是我們編寫的驅動程式要使用之前某個驅動程式的功能時,就不能使用module_platform_driver,因為如果都使用module_platform_driver,內核編譯連結時,並不知道哪個驅動程式會被連結再前面,也就不知道哪個驅動程式會先執行,如果使用自訂的驅動(使用其他驅動程式功能的程式)先執行,而其他驅動還未進行初始化,就會出現難以預料的問題,所以這種情況下就必須使用late_initcall

————————————————



沒有留言: