前言
我們都知道(或是我知道),Linux modules的入口點在module_init()
,透過module_init()
把我們的程式掛載到kernel去。
不過有時候會看到有些module用的是late_initcall()
或是arch_initcall()
這類macro,他們跟一般的module_init()
又差在哪裡?
本文會透過trace linux source code介紹以下幾個概念
module_init()
這個macro背後做了什麼事情- 不同的init macro背後的差異是什麼
- 怎麼透過不同的init macro來控制modules的載入先後順序,以及為何要控制載入的順序
(本文的linux code 版本為linux4.14.201)
module_init code trace
首先我們來看一下每個module的entry,module_init()
到底在幹嘛,如果不想跟我一起追code的可以追我喔…不是,我下面有懶人包總結,可以直接跳下去看。
module_init()
的macro被定義在linux/include/module.h中
89 | /* linux/include/module.h, line=85 */ |
85 | /* linux/include/init.h, line=85 */ |
發現實際上是調用了device_initcall(fn)
,所以來追一下這個部分會發現有許多相關的func都同樣地呼叫了__define_initcall(fn, id)
176 | /* linux/include/init.h, line=176 */ |
懶人包總結: module_init到底做了什麼
好,先到這裡停住,先講結論,講完有興趣的可以繼續往下追。
簡單說module_init()
透過實際上呼叫__device_initcall(fn, id)
來將對應的init function放到linker script的某個section中,而kernel在載入的時候會根據數字的順序來依序init所有的init functions完成各個modules的初始化。
而我們可以發現我們常用的module_init()
實際上優先權是6(device_initcall(fn)
的id是6),所以如果有人用了比module_init()
還要前面的,那他就會比較早被載入,反之依然。
- 對於一些載入順序很重要的modules來說,透過不同的xxxx_initcall來控制載入順序很重要
繼續追__define_initcall(fn, id)
上面各個不同的initcall都都呼叫了__define_initcall()
,所以來看一下
172 | /* include/linux/init.h. line=172 */ |
這個一坨真的很可怕,舉個例子來個別解釋,如果fn=hello, id=6的話:
__initcall_##fn##id
就會變成__initcall_hello6
__attribute__(__section__)
是編譯器的編譯屬性,就是說將fn放到linker script的”.initcall6.init” section中
比方說,觀察arch/powerpc/kernel/vmlinux.lds.S的initcall.init section:
215 | /* arch/powerpc/kernel/vmlinux.lds.S, line=215 */ |
INIT_CALLS被定義在include/asm-generic/vmlinux.lds.h
744 | /* include/asm-generic/vmlinux.lds.h, line=749 */ |
發現他做的事情就是將*(.initcall0.init), *(.initcall0s.init)…依照順序去放在.initcall.init section中。
實際上,arch/powerpc/kernel/vmlinux.lds.S的initcall.init section展開後是長下面這樣:
215 | /* arch/powerpc/kernel/vmlinux.lds.S, line=215 */ |
好,到這裡回顧一下,__define_initcall(fn, id)
會根據你的id將你的init function放到linker script中對應的section內。
到這裡module_init()
終於全部追完了,比追20集的韓劇還累。
如果有跟我一起追到這邊的朋友,可以再回去回顧一下懶人包,是不是對module_init()
做的事情更加清楚了呢?
到這裡我們已經可以回答一開始的兩個問題了
module_init()
做的事情是將init func放到對應優先權的init section中,等kernel載入時依序呼叫- 不同
xxxx_initcall()
給了不同的優先順序,kernel載入時會根據id來依序載入,所以可以達到控制module載入先後順序的效果
為何要控制module載入的順序
引用一些網站上遇到的內容來說明模組先後順序會有什麼影響:
举个例子,在2.6.24的内核中:gianfar_device使用的是arch_initcall,而gianfar_driver使用的是module_init,因为arch_initcall的优先级大于module_init,所以gianfar设备驱动的device先于driver在总线上添加。
这是我在调背光的时候出现的问题,因为键盘驱动是会在一个遥控手柄之前加载,导致驱动出现冲突;把两者先后顺序换一下就可以了;
補充: Kernel載入init function的過程
在kernel載入的過程中,會依序呼叫start_kernel()
->rest_init()
->kernel_init()
->kernel_init_freeable()
->do_basic_setup()
->do_initcalls()
來依序載入init function
不過這個要trace下去也很痛苦,我就把東西放在這裡了,有興趣的自己去慢慢看: Linux内核很吊之 module_init解析 (下)
platform_device有多個module時加載的順序
以下引用linux 设备驱动加载的先后顺序的內容:
【問題】背光驱动初始化先于LCD驱动初始化,导致LCD驱动初始化时出现闪屏的现象。
考慮下面的platform_device array
0 | /* platform devices */ |
發現lcd_device在backlight_device前面,但加載順序並不一致
- 注意PM resume/suspend ops和這個順序是一致的
看了一下該module的Makefile: backlight在display前面obj-$(CONFIG_VT) += console/
obj-$(CONFIG_LOGO) += logo/
obj-y += backlight/ display/
...
obj-$(CONFIG_FB_xxx) += xxxfb.o ak_logo.o
obj-$(CONFIG_FB_AK88) += ak88-fb/
產生的System.map如下c001f540 t __initcall_pwm_backlight_init6
c001f544 t __initcall_display_class_init6
c001f548 t __initcall_xxxfb_init6
如果把Makefile順序改一下obj-$(CONFIG_VT) += console/
obj-$(CONFIG_LOGO) += logo/
obj-y += display/
...
obj-$(CONFIG_FB_xxx) += xxxfb.o ak_logo.o
obj-$(CONFIG_FB_AK88) += ak88-fb/
obj-y += backlight/
System.map順序就對了c001f53c t __initcall_display_class_init6
c001f540 t __initcall_xxxfb_init6
c001f544 t __initcall_genericbl_init6
c001f548 t __initcall_pwm_backlight_init6
加載後就會發現xxxpwm_backlight_device的probe会在xxx_lcd_device的probe之后执行,即LCD初始化先于PWM的初始化。
References
- linux驱动 之 module_init解析 (上)
- Linux内核很吊之 module_init解析 (下)
- linux 设备驱动加载的先后顺序
- late_initcall和module_init的区别