linux驅動啓動順序

首先,咱們能夠查看Linux內核編譯完成後的System.map文件,在這個文件中咱們能夠看到macb(dm9161驅動模塊)連接到了dm9000驅動以前,以下所示:linux

c03b6d40 t __initcall_tun_init6編程

c03b6d44 t __initcall_macb_init6vim

c03b6d48 t __initcall_dm9000_init6centos

c03b6d4c t __initcall_ppp_init6網絡

c03b6d50 t __initcall_ppp_async_init6dom

 

   我嘗試修改arch/arm/mach-at91/board-sam9260ek.c中DM9000和DM916設備添加的順序,即先添加 dm9000,後添加dm9161。編譯後運行發現,結果仍是同樣。本身想了想,這也在情理之中。由於這個出現這個問題的主要緣由是這兩個驅動加載的前後 順序,而不是設備添加的前後順序。async

 

 在Linux內核中維護着兩個鏈,一個設備鏈,一個驅動鏈,他們兩個就像情侶同樣互相 依賴,互相糾纏在一塊兒的。當咱們新添加一個設備時,他會被加入到設備鏈上,這時內核這個紅娘會就會到驅動鏈上給他找他的另一半(驅動),看是否有哪一個驅 動看上了他(這個驅動是否支持這個設備),若是找到了這個驅動,那麼設備就可以使用(你們糾纏到一塊了,該幹嗎就幹嗎去了)。而若是沒有找到,那麼設備就 只能默默地在那裏等待他的另外一半的出現。下面是arch/arm/mach-at91/board-sam9260ek.c添加設備的代碼:函數

static void __init ek_board_init(void){       /* Serial */      oop

  at91_add_device_serial();    /* USB Host */     post

 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.

相關文章
相關標籤/搜索