Linux那些事儿 之 戏说USB(8)从这里开始
生活随笔
收集整理的這篇文章主要介紹了
Linux那些事儿 之 戏说USB(8)从这里开始
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
USB core從USB子系統的初始化開始,我們也需要從那里開始,它們在文件drivers/usb/core/usb.c
對__init的定義在include/linux/init.h里
‘section ("section-name")' Normally, the compiler places the code it generates in the `text'
section. Sometimes, however, you need additional sections, or you
need certain particular functions to appear in special sections.
The `section' attribute specifies that a function lives in a
particular section. For example, the declaration:
extern void foobar (void) __attribute__ ((section ("bar")));
puts the function ‘foobar' in the ‘bar' section.
Some file formats do not support arbitrary sections so the
‘section' attribute is not available on all platforms. If you
need to map the entire contents of a module to a particular
section, consider using the facilities of the linker instead.
通常編譯器將函數放在.text節,變量放在.data 或 .bss 節,使用section屬性,可以讓編譯器將函數或變量放在指定的節中。那么前面對__init的定義便表示將它修飾的代碼放在 .init.text 節。連接器可以把相同節的代碼或數據安排在一起,比如 __init 修飾的所有代碼都會被放在 .init.text 節里,初始化結束后就可以釋放這部分內存。
那內核又是如何調用到這些 __init 修飾的初始化函數那?
要回答這個問題,還需要回顧一下上面938行的代碼,那里已經提到subsys_initcall也是一個宏,它也在include/linux/init.h里定義
內核可執行文件由許多鏈接在一起的對象文件組成。對象文件有許多節,如文本、數據、init 數據、bass 等等。這些對象文件都是由一個稱為鏈接器腳本的文件鏈接并裝入的。這個鏈接器腳本的功能是將輸入對象文件的各節映射到輸出文件中;換句話說,它將所有輸入對象文件都鏈接到單一的可執行文件中,將該可執行文件的各節裝入到指定地址處。 vmlinux.lds是存在于 arch/<target>/ 目錄中的內核鏈接器腳本,它負責鏈接內核的各個節并將它們裝入內存中特定偏移量處。
涉及到的東西越來越多了是吧,先深呼吸,平靜一下,堅定而又勇敢的打開arch/arm/kernel/vmlinux.lds文件,你就會見到前所未見的景象。我可以負責任的說,要看懂這個文件是需要一番功夫的,不過大家都是聰明人,聰明人做聰明事,所以你需要做的只是搜索initcall.init,然后便會看到似曾相識的內容
.initcall1.init
.initcall2.init
.initcall3.init
.initcall4.init
.initcall5.init
.initcall6.init
.initcall7.init
我們的subsys_initcall宏便是將指定的函數指針放在了 .initcall4.init 子節。其它的比如 core_initcall 將函數指針放在 .initcall1.init 子節,device_initcall 將函數指針放在了 .initcall6.init 子節等等,都可以從include/linux/init.h文件找到它們的定義。各個字節的順序是確定的,即先調用.initcall1.init中的函數指針再調用.initcall2.init中的函數指針,等等。__init 修飾的初始化函數在內核初始化過程中調用的順序和.initcall.init節里函數指針的順序有關,不同的初始化函數被放在不同的子節中,因此也就決定了它們的調用順序。
至于實際執行函數調用的地方,就在/init/main.c文件里,內核的初始化么,不在那里還能在哪里,里面的do_initcalls函數會直接用到這里的__initcall_start、__initcall_end來進行判斷,不多說了。我們的思念已經入滔滔江水泛濫成災了,還是回到久違的usb_init函數吧。
subsys_initcall(usb_init);
module_exit(usb_exit);我們看到一個subsys_initcall,復旦人甲說它也是一個宏,我們可以把它理解為module_init,只不過因為這部分代碼比較核心,開發者們把它看作一個子系統,而不僅僅是一個模塊,這也很好理解,usbcore這個模塊它代表的不是某一個設備,而是所有usb設備賴以生存的模塊,Linux中,像這樣一個類別的設備驅動被歸結為一個子系統。比如pci子系統,比如scsi子系統,基本上,內核源碼中drivers/目錄下面第一層的每個目錄都算一個子系統,因為它們代表了一類設備。subsys_initcall(usb_init)的意思就是告訴我們usb_init是我們真正的初始化函數,而usb_exit()將是整個usb子系統的結束時的清理函數,于是我們就從usb_init開始看起。
既然復旦人甲都這么說了,那咱們就從usb_init開始看起吧。至于子系統在內核里具體的描述,牽涉到linux設備模型了,可以去看ldd3,或者更詳細的。目前來說,我們只需要知道子系統通常顯示在sysfs分層結構中的頂層,比如塊設備子系統對應/sys/block,當然也不一定,usb子系統對應的就是/sys/bus/usb。
drivers/usb/core/usb.c
static int __init usb_init(void)
{int retval;if (nousb) {pr_info("%s: USB support disabled\n", usbcore_name);return 0;}retval = usb_debugfs_init();if (retval)goto out;usb_acpi_register();retval = bus_register(&usb_bus_type);if (retval)goto bus_register_failed;retval = bus_register_notifier(&usb_bus_type, &usb_bus_nb);if (retval)goto bus_notifier_failed;retval = usb_major_init();if (retval)goto major_init_failed;retval = usb_register(&usbfs_driver);if (retval)goto driver_register_failed;retval = usb_devio_init();if (retval)goto usb_devio_init_failed;retval = usb_hub_init();if (retval)goto hub_init_failed;retval = usb_register_device_driver(&usb_generic_driver, THIS_MODULE);if (!retval)goto out;usb_hub_cleanup();
hub_init_failed:usb_devio_cleanup();
usb_devio_init_failed:usb_deregister(&usbfs_driver);
driver_register_failed:usb_major_cleanup();
major_init_failed:bus_unregister_notifier(&usb_bus_type, &usb_bus_nb);
bus_notifier_failed:bus_unregister(&usb_bus_type);
bus_register_failed:usb_acpi_unregister();usb_debugfs_cleanup();
out:return retval;
}看到上面定義里的__init標記沒,寫過驅動的應該不會陌生,它對內核來說就是一種暗示,表明這個函數僅在初始化期間使用,在模塊被裝載之后,它占用的資源就會釋放掉用作它處。
對__init的定義在include/linux/init.h里
#define __init __section(.init.text) __cold notrace好像這里的疑問要更多,不過與 __init 相比,這點辛苦算什么,我會在它強大的精神支持下盡量說清楚的。Linux內核代碼使用了大量的GNU C擴展,以至于GNU C成為能夠編譯內核的唯一編譯器,GNU C的這些擴展對代碼優化、目標代碼布局、安全檢查等方面也提供了很強的支持。GNU C支持十幾個屬性,section是其中的一個,我們查看gcc的手冊可以看到下面的描述
‘section ("section-name")' Normally, the compiler places the code it generates in the `text'
section. Sometimes, however, you need additional sections, or you
need certain particular functions to appear in special sections.
The `section' attribute specifies that a function lives in a
particular section. For example, the declaration:
extern void foobar (void) __attribute__ ((section ("bar")));
puts the function ‘foobar' in the ‘bar' section.
Some file formats do not support arbitrary sections so the
‘section' attribute is not available on all platforms. If you
need to map the entire contents of a module to a particular
section, consider using the facilities of the linker instead.
通常編譯器將函數放在.text節,變量放在.data 或 .bss 節,使用section屬性,可以讓編譯器將函數或變量放在指定的節中。那么前面對__init的定義便表示將它修飾的代碼放在 .init.text 節。連接器可以把相同節的代碼或數據安排在一起,比如 __init 修飾的所有代碼都會被放在 .init.text 節里,初始化結束后就可以釋放這部分內存。
那內核又是如何調用到這些 __init 修飾的初始化函數那?
要回答這個問題,還需要回顧一下上面938行的代碼,那里已經提到subsys_initcall也是一個宏,它也在include/linux/init.h里定義
#define subsys_initcall(fn) __define_initcall(fn, 4)這里又出現了一個宏__define_initcall,它是用來將指定的函數指針fn放到initcall.init節里,也在include/linux/init.h文件里定義,這里就不多說了,有那點意思就可以了。而對于具體的subsys_initcall宏,則是把fn放到.initcall.init的子節.initcall4.init里。要弄清楚.initcall.init、.init.text和.initcall4.init這樣的東東,我們還需要了解一點內核可執行文件相關的概念。
內核可執行文件由許多鏈接在一起的對象文件組成。對象文件有許多節,如文本、數據、init 數據、bass 等等。這些對象文件都是由一個稱為鏈接器腳本的文件鏈接并裝入的。這個鏈接器腳本的功能是將輸入對象文件的各節映射到輸出文件中;換句話說,它將所有輸入對象文件都鏈接到單一的可執行文件中,將該可執行文件的各節裝入到指定地址處。 vmlinux.lds是存在于 arch/<target>/ 目錄中的內核鏈接器腳本,它負責鏈接內核的各個節并將它們裝入內存中特定偏移量處。
涉及到的東西越來越多了是吧,先深呼吸,平靜一下,堅定而又勇敢的打開arch/arm/kernel/vmlinux.lds文件,你就會見到前所未見的景象。我可以負責任的說,要看懂這個文件是需要一番功夫的,不過大家都是聰明人,聰明人做聰明事,所以你需要做的只是搜索initcall.init,然后便會看到似曾相識的內容
__initcall_start = .;*(.initcallearly.init) __early_initcall_end = .;*(.initcall0.init) *(.initcall0s.init)*(.initcall1.init) *(.initcall1s.init)*(.initcall2.init) *(.initcall2s.init)*(.initcall3.init) *(.initcall3s.init)*(.initcall4.init) *(.initcall4s.init)*(.initcall5.init) *(.initcall5s.init)*(.initcallrootfs.init)*(.initcall6.init) *(.initcall6s.init)*(.initcall7.init) *(.initcall7s.init)__initcall_end = .;這里的 __initcall_start 指向.initcall.init節的開始,__initcall_end指向它的結尾。而.initcall.init節又被分為了7個子節,分別是
.initcall1.init
.initcall2.init
.initcall3.init
.initcall4.init
.initcall5.init
.initcall6.init
.initcall7.init
我們的subsys_initcall宏便是將指定的函數指針放在了 .initcall4.init 子節。其它的比如 core_initcall 將函數指針放在 .initcall1.init 子節,device_initcall 將函數指針放在了 .initcall6.init 子節等等,都可以從include/linux/init.h文件找到它們的定義。各個字節的順序是確定的,即先調用.initcall1.init中的函數指針再調用.initcall2.init中的函數指針,等等。__init 修飾的初始化函數在內核初始化過程中調用的順序和.initcall.init節里函數指針的順序有關,不同的初始化函數被放在不同的子節中,因此也就決定了它們的調用順序。
至于實際執行函數調用的地方,就在/init/main.c文件里,內核的初始化么,不在那里還能在哪里,里面的do_initcalls函數會直接用到這里的__initcall_start、__initcall_end來進行判斷,不多說了。我們的思念已經入滔滔江水泛濫成災了,還是回到久違的usb_init函數吧。
總結
以上是生活随笔為你收集整理的Linux那些事儿 之 戏说USB(8)从这里开始的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Linux那些事儿 之 戏说USB(7)
- 下一篇: Linux那些事儿 之 戏说USB(9)