首先,我們可以查看Linux內核編譯完成后的System.map文件,在這個文件中我們可以看到macb(dm9161驅動模塊)鏈接到了dm9000驅動之前,如下所示:
c03b6d40 t __initcall_tun_init6
c03b6d44 t __initcall_macb_init6
c03b6d48 t __initcall_dm9000_init6
c03b6d4c t __initcall_ppp_init6
c03b6d50 t __initcall_ppp_async_init6
我嘗試修改arch/arm/mach-at91/board-sam9260ek.c中DM9000和DM916設備添加的順序,即先添加 dm9000,后添加dm9161。編譯后運行發現,結果還是一樣。自己想了想,這也在情理之中。因為這個出現這個問題的主要原因是這兩個驅動加載的先后 順序,而不是設備添加的先后順序。
在Linux內核中維護着兩個鏈,一個設備鏈,一個驅動鏈,他們兩個就像情侶一樣互相 依賴,互相糾纏在一起的。當我們新添加一個設備時,他會被加入到設備鏈上,這時內核這個紅娘會就會到驅動鏈上給他找他的另外一半(驅動),看是否有哪個驅 動看上了他(這個驅動是否支持這個設備),如果找到了這個驅動,那么設備就能夠使用(大家糾纏到一塊了,該干嘛就干嘛去了)。而如果沒有找到,那么設備就 只能默默地在那里等待他的另一半的出現。下面是arch/arm/mach-at91/board-sam9260ek.c添加設備的代碼:
static void __init ek_board_init(void){ /* Serial */
at91_add_device_serial(); /* USB Host */
at91_add_device_usbh(&ek_usbh_data); /* USB Device */
at91_add_device_udc(&ek_udc_data); /* SPI */
at91_add_device_spi(ek_spi_devices, ARRAY_SIZE(ek_spi_devices)); /* NAND */
ek_add_device_nand(); /* Ethernet */ ek_add_device_dm9000(); /* Add dm9000 driver by guowenxue, 2012.04.11 */
at91_add_device_eth(&ek_macb_data); /* MMC */
at91_add_device_mmc(0, &ek_mmc_data); /* I2C */
at91_add_device_i2c(ek_i2c_devices, ARRAY_SIZE(ek_i2c_devices)); /* SSC (to AT73C213) */
#if defined(CONFIG_SND_AT73C213) || defined(CONFIG_SND_AT73C213_MODULE)
at73c213_set_clk(&at73c213_data); /* Modify by guowenxue, 2012.04.11 */
#endif
at91_add_device_ssc(AT91SAM9260_ID_SSC, ATMEL_SSC_TX);
#if 0 /* comment by guowenxue */ /* LEDs */
at91_gpio_leds(ek_leds, ARRAY_SIZE(ek_leds)); /* Push Buttons */
ek_add_device_buttons();
#endif
}
MACHINE_START(AT91SAM9260EK, "Atmel AT91SAM9260-EK") /* Maintainer: Atmel */
.timer = &at91sam926x_timer,
.map_io = at91_map_io,
.init_early = ek_init_early,
.init_irq = at91_init_irq_default,
.init_machine = ek_board_init,
MACHINE_END
MACHINE_START主要是定義了"struct machine_desc"的類型,放在 section(".arch.info.init"),是初始化數據,Kernel 起來之后將被丟棄。
其余各個成員函數在setup_arch()中被賦值到內核結構體,在不同時期被調用:
1. .init_machine 在 arch/arm/kernel/setup.c 中被 customize_machine 調用,放在 arch_initcall() 段里面,會自動按順序被調用。
2. .init_irq在start_kernel() --> init_IRQ() --> init_arch_irq()中被調用
3. .map_io 在 setup_arch() --> paging_init() --> devicemaps_init()中被調用
4. .timer是定義系統時鍾,定義TIMER4為系統時鍾,在arch/arm/mach-at91/at91sam926x_time.c中實現。在start_kernel() --> time_init()中被調用。
5. .boot_params是bootloader向內核傳遞的參數的位置,這要和bootloader中參數的定義要一致。
其他主要都在 setup_arch() 中用到。
當在Linux內核啟動調用ek_board_init()時,就會調用ek_add_device_dm9000()
和at91_add_device_eth(&ek_macb_data)來分別將dm9161和dm9000這兩個設備添加到設備鏈上去。然后,他們就開始在鏈表上苦苦等待他的另一半(相應驅動)的出現。
這里我們只是調整這兩個網絡設備在設備鏈上的位置,但問題的本質是驅動鏈接的位置是dm9161在前,dm9000在后,這樣dm9161驅動先加載后 就找到設備dm9161,這樣他使用了eth0這個設備;而dm9000的驅動后加載,這樣他對應的設備名就是eth1了。這里來分析為什么是先加載 dm9161,后加載dm9000這個驅動,只有了解了這個原因,我們才能調整他們的加載順序。
幾乎每個linux驅動都會調用module_init(它和module_exit一起定義在Init.h (/include/linux) 中。沒錯,驅動的加載就靠它。為什么需要這樣一個宏?原因是按照一般的編程想法,各部分的初始化函數會在一個固定的函數里調用比如:
void init(void)
{
init_a();
init_b();
}
如果再加入一個初始化函數呢,那么在init_b()后面再加一行init_c();這樣確實能完成我們的功能,但這樣有一定的問題,就是不能獨立的添加初始化函數,每次添加一個新的函數都要修改init函數。可以采用另一種方式來處理這個問題,只要用一個宏來修飾一下:
void init_a(void)
{
}
__initlist(init_a, 1);
它是怎么樣通過這個宏來實現初始化函數列表的呢?先來看__initlist的定義:
#define __init __attribute__((unused, __section__(".initlist")))
#define __initlist(fn, lvl) /
static initlist_t __init_##fn __init = { /
magic: INIT_MAGIC, /
callback: fn, /
level: lvl }
請 注意:__section__(".initlist"),這個屬性起什么作用呢?它告訴連接器這個變量存放在.initlist區段,這是 GNU/GCC的特性,關於這部分內容大家可以參考GNU連接器的說明文檔。如果所有的初始化函數都是用這個宏,那么每個函數會有對應的一個 initlist_t結構體變量存放在.initlist區段,也就是說我們可以在.initlist區段找到所有初始化函數的指針。怎么找 到.initlist區段的地址呢?
extern u32 __initlist_start;
extern u32 __initlist_end;
這 兩個變量起作用了,__initlist_start是.initlist區段的開始,__initlist_end是結束,通過這兩個變量我們就可以訪 問到所有的初始化函數了。這兩個變量在那定義的呢?在一個連接器腳本文件里(別告訴我說你不知道連接器腳本是啥,如果不知道,好好惡補一下)。
. = ALIGN(4); .initlist : { __initlist_start = .; *(.initlist) __initlist_end = .; }
這兩個變量的值正好定義在.initlist區段的開始和結束地址,所以我們能通過這兩個變量訪問到所有的初始化函數。
與 此類似,內核中也是用到這種方法,所以我們寫驅動的時候比較獨立,不用我們自己添加代碼在一個固定的地方來調用我們自己的初始化函數和退出函數,連接器已 經為我們做好了。先來分析一下module_init。他在include/linux/init.h文件中定義如下:
#define module_init(x) __initcall(x);#define __initcall(fn) device_initcall(fn)
#define pure_initcall(fn) __define_initcall("0",fn,0)
#define core_initcall(fn) __define_initcall("1",fn,1)
#define core_initcall_sync(fn) __define_initcall("1s",fn,1s)
#define postcore_initcall(fn) __define_initcall("2",fn,2)
#define postcore_initcall_sync(fn) __define_initcall("2s",fn,2s)
#define arch_initcall(fn) __define_initcall("3",fn,3)
#define arch_initcall_sync(fn) __define_initcall("3s",fn,3s)
#define subsys_initcall(fn) _define_initcall("4",fn,4)
#define subsys_initcall_sync(fn) __define_initcall("4s",fn,4s)
#define fs_initcall(fn) __define_initcall("5",fn,5)
#define fs_initcall_sync(fn) __define_initcall("5s",fn,5s)
#define rootfs_initcall(fn) __define_initcall("rootfs",fn,rootfs)
#define device_initcall(fn) __define_initcall("6",fn,6)
#define device_initcall_sync(fn) __define_initcall("6s",fn,6s)
#define late_initcall(fn) __define_initcall("7",fn,7)
#define late_initcall_sync(fn) __define_initcall("7s",fn,7s)
#define __define_initcall(level,fn,id) \
static initcall_t __initcall_##fn##id __used \
__attribute__((__section__(".initcall" level ".init"))) = fn
如 果某驅動想以func作為該驅動的入口,則可以如下聲明:module_init(func);被上面的宏處理過后,變成 __initcall_func6 __used加入到內核映像的".initcall"區(這就是我們上面System.map文件中__initcall_macb_init6和 __initcall_dm9000_init6的來歷)。內核的加載的時候,會搜索".initcall"中的所有條目,並按優先級加載它們,普通驅動 程序的優先級是6。其它模塊優先級列出如下:值越小,越先加載。從上可以看到,被聲明為pure_initcall的最先加載。
module_init除了初始化加載之外,還有后期釋放內存的作用。linux kernel中有很大一部分代碼是設備驅動代碼,這些驅動代碼都有初始化和反初始化函數,這些代碼一般都只執行一次,為了有更有效的利用內存,這些代碼所占用的內存可以釋放出來。
linux 就是這樣做的,對只需要初始化運行一次的函數都加上__init屬性,__init 宏告訴編譯器如果這個模塊被編譯到內核則把這個函數放到(.init.text)段,module_exit的參數卸載時同__init類似,如果驅動被 編譯進內核,則__exit宏會忽略清理函數,因為編譯進內核的模塊不需要做清理工作,顯然__init和__exit對動態加載的模塊是無效的,只支持 完全編譯進內核。
在kernel初始化后期,釋放所有這些函數代碼所占的內存空間。連接器把帶__init屬性的函數放在 同一個section里,在用完以后,把整個section釋放掉。當函數初始化完成后這個區域可以被清除掉以節約系統內存。Kenrel啟動時看到的消 息“Freeing unused kernel memory: xxxk freed”同它有關。
也就是在寫驅動的時候,通過module_init()宏,告訴我們的驅動函數入口放到.initcall節中的哪個部分,那么Linux內核在啟動的時候又是怎么調用我們的這些驅動入口函數的呢?
Linux 系統使用兩種方式去加載系統中的模塊:動態和靜態。這里我們dm9161和dm9000的驅動是以靜態的方式程序編譯到Linux內核中,Linux系統 啟動時會進入C函數入口(下面函數都在init/main.c文件 中)start_kernel()->rest_init()->kernel_thread(kernel_init, NULL, CLONE_FS | CLONE_SIGHAND)->kernel_init()->do_basic_setup()->do_initcalls().
下面是do_initcalls()的定義:
extern initcall_t __initcall_start[], __initcall_end[], __early_initcall_end[];
static void __init do_initcalls(void){
initcall_t *fn;
for (fn = __early_initcall_end; fn < __initcall_end; fn++)
do_one_initcall(*fn);
}
do_initcalls 函數中會將在__early_initcall_end 和__initcall_end之間定義的各個模塊依次加載。那么在__early_initcall_end 和 __initcall_end之間都有些什么呢?我們可以查看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 = .;
可 以看出在這兩個宏之間依次排列了14個等級的宏,由於這其中的宏是按先后順序鏈接的,所以也就表示,這14個宏有優先 級:0>0s>1>1s>2>2s………>7>7s,這里的優先級也就意味着誰的優先級高,那么誰就會被先加 載。關於這宏有什么具體的意義呢,這就要看我們之前提到的include/linux/init.h文件中的定義了:
#define module_init(x) __initcall(x);#define __initcall(fn) device_initcall(fn)
#define pure_initcall(fn) __define_initcall("0",fn,0)
#define core_initcall(fn) __define_initcall("1",fn,1)
#define core_initcall_sync(fn) __define_initcall("1s",fn,1s)
#define postcore_initcall(fn) __define_initcall("2",fn,2)
#define postcore_initcall_sync(fn) __define_initcall("2s",fn,2s)
#define arch_initcall(fn) __define_initcall("3",fn,3)
#define arch_initcall_sync(fn) __define_initcall("3s",fn,3s)
#define subsys_initcall(fn) __define_initcall("4",fn,4)
#define subsys_initcall_sync(fn) __define_initcall("4s",fn,4s)
#define fs_initcall(fn) __define_initcall("5",fn,5)
#define fs_initcall_sync(fn) __define_initcall("5s",fn,5s)
#define rootfs_initcall(fn) __define_initcall("rootfs",fn,rootfs)
#define device_initcall(fn) __define_initcall("6",fn,6)
#define device_initcall_sync(fn) __define_initcall("6s",fn,6s)
#define late_initcall(fn) __define_initcall("7",fn,7)
#define late_initcall_sync(fn) __define_initcall("7s",fn,7s)
從 上面分析,我們可以看到,我們的DM9000和DM9161都是使用module_init()來定義的,那么他們最終都是在同一個級別( __define_initcall("6",fn,6))中加載。對於這些函數指針的順序也是和鏈接的順序有關的,但具體是不確定的(不通目錄下的鏈接 順序),但我通過修改Makefile中的編譯順序,把DM9000的編譯放在DM9161之前就OK了。這樣可以看出,對於同一目錄下的驅動文件,我們 可以通過調整他們在Makefile中編譯的順序來解決這個問題:
[guowenxue@centos6 linux-3.3]$ vim drivers/net/ethernet/Makefile
obj-$(CONFIG_DM9000) += davicom/
obj-$(CONFIG_NET_CADENCE) += cadence/
編譯后再看System.map文件:
c03b6d40 t __initcall_tun_init6
c03b6d44 t __initcall_dm9000_init6
c03b6d48 t __initcall_macb_init6
c03b6d4c t __initcall_ppp_init6
c03b6d50 t __initcall_ppp_async_init
系統啟動打印:
......
bonding: Ethernet Channel Bonding Driver: v3.7.1 (April 27, 2011)
tun: Universal TUN/TAP device driver, 1.6
tun: (C) 1999-2004 Max Krasnyansky <maxk@qualcomm.com>
dm9000 Ethernet Driver, V1.31
eth0: dm9000a at c4896000,c489e044 IRQ 111 MAC: 00:30:c2:12:03:19 (chip)
macb macb: (unregistered net_device): invalid hw address, using random
MACB_mii_bus: probed
macb macb: eth1: Cadence MACB at 0xfffc4000 irq 21 (6e:cf:99:c4:e4:5b)
macb macb: eth1: attached PHY driver [Generic PHY] (mii_bus:phy_addr=macb-ffffffff:00, irq=-1)
PPP generic driver version 2.4.2
PPP BSD Compression module registered
PPP Deflate Compression module registered
PPP MPPE Compression module registered
NET: Registered protocol family 24
usbcore: registered new interface driver rt2800usb
ohci_hcd: USB 1.1 'Open' Host Controller (OHCI) Driver
at91_ohci at91_ohci: AT91 OHCI
at91_ohci at91_ohci: new USB bus registered, assigned bus number 1
at91_ohci at91_ohci: irq 20, io mem 0x00500000
hub 1-0:1.0: USB hub found
hub 1-0:1.0: 2 ports detected
Initializing USB Mass Storage driver...
........
如果這種方法不能解決的話,那么我們可以修改dm9161的驅動,將module_init宏改成device_initcall_sync,這樣加載的級別降低后也能改變他們的加載順序,如下。
[guowenxue@centos6 linux-3.3]$ vim drivers/net/ethernet/cadence/macb.c
....
//module_init(macb_init);
device_initcall_sync(macb_init);
module_exit(macb_exit);
這樣編譯后的System.map文件顯示:
c03b6d40 t __initcall_tun_init6
c03b6d44 t __initcall_dm9000_init6
c03b6d48 t __initcall_ppp_init6
c03b6d4c t __initcall_ppp_async_init6
c03b6d50 t __initcall_bsdcomp_init6
....
c03b6f6c t __initcall_macb_init6s
c03b6f70 t __initcall_at91_clock_reset7
c03b6f74 t __initcall_init_oops_id7
c03b6f78 t __initcall_printk_late_init7
c03b6f7c t __initcall_sched_init_debug7
c03b6f80 t __initcall_ubifs_init7
這時系統啟動的過程:
....
bonding: Ethernet Channel Bonding Driver: v3.7.1 (April 27, 2011)
tun: Universal TUN/TAP device driver, 1.6
tun: (C) 1999-2004 Max Krasnyansky <maxk@qualcomm.com>
dm9000 Ethernet Driver, V1.31
eth0: dm9000a at c4896000,c489e044 IRQ 111 MAC: 00:30:c2:12:03:19 (chip)
PPP generic driver version 2.4.2
PPP BSD Compression module registered
PPP Deflate Compression module registered
PPP MPPE Compression module registered
NET: Registered protocol family 24
.......
Bridge firewalling registered
lib80211: common routines for IEEE802.11 drivers
macb macb: (unregistered net_device): invalid hw address, using random
MACB_mii_bus: probed
macb macb: eth1: Cadence MACB at 0xfffc4000 irq 21 (b6:6c:eb:6a:dc:cc)
macb macb: eth1: attached PHY driver [Generic PHY] (mii_bus:phy_addr=macb-ffffffff:00, irq=-1)
rtc-ds1307 0-0068: setting system clock to 2000-01-01 00:00:00 UTC (946684800)
RAMDISK: gzip image found at block 0
EXT2-fs (ram0): warning: mounting unchecked fs, running e2fsck is recommended
VFS: Mounted root (ext2 filesystem) on device 1:0.
Freeing init memory: 140K
.....
device 先注冊,driver后注冊。因為device 對應的MACHINE_START 對應的arch_initcall優先級為3.
module_init對應的優先級是6.